Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The packages in this library implement the various components of the CONIKS syst
- ``client``: Client-side protocol cgo hooks
- ``crypto``: Cryptographic algorithms and operations
- ``merkletree``: Merkle prefix tree and related data structures
- ``storage``: DB hooks for storage backend (currently the library supports key-value db only)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like this is duplicate (see L27)

- ``utils``: Utility functions
- ``storage``: DB hooks for storage backend (currently the library supports key-value db only)

Expand Down
6 changes: 3 additions & 3 deletions client/cgo/cgotest.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ func testVerifyHashChain(t *testing.T) {
if err != nil {
t.Fatal(err)
}
pad, err := merkletree.NewPAD(merkletree.NewPolicies(2, vrfPrivKey), signKey, 10)
pad, err := merkletree.NewPAD(merkletree.NewPolicies(2, vrfPrivKey), nil, signKey, 10)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -133,7 +133,7 @@ func testVerifyAuthPath(t *testing.T) {
if err != nil {
t.Fatal(err)
}
pad, err := merkletree.NewPAD(merkletree.NewPolicies(2, vrfPrivKey), signKey, 10)
pad, err := merkletree.NewPAD(merkletree.NewPolicies(2, vrfPrivKey), nil, signKey, 10)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -209,7 +209,7 @@ func testVerifyProofOfAbsenceSamePrefix(t *testing.T) {
if err != nil {
t.Fatal(err)
}
pad, err := merkletree.NewPAD(merkletree.NewPolicies(2, vrfPrivKey), signKey, 10)
pad, err := merkletree.NewPAD(merkletree.NewPolicies(2, vrfPrivKey), nil, signKey, 10)
if err != nil {
t.Fatal(err)
}
Expand Down
12 changes: 9 additions & 3 deletions merkletree/merkletree.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,18 @@ import (
)

var (
ErrorInvalidTree = errors.New("[merkletree] invalid tree")
ErrorInvalidTree = errors.New("[merkletree] Invalid tree")
)

const (
EmptyBranchIdentifier = 'E'
LeafIdentifier = 'L'
EmptyBranchIdentifier = 'E'
LeafIdentifier = 'L'
InteriorNodeIdentifier = 'I'
NodeKeyIdentifier = 'N'
STRIdentifier = 'S'
PoliciesIdentifier = 'P'
EpochIdentifier = 'O'
TreeNonceIdentifier = 'T'
)

type MerkleTree struct {
Expand Down
104 changes: 104 additions & 0 deletions merkletree/merkletreekv.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package merkletree

import (
"errors"

"github.com/coniks-sys/coniks-go/crypto"
"github.com/coniks-sys/coniks-go/storage/kv"
"github.com/coniks-sys/coniks-go/utils"
)

var (
ErrorBadTreeNonce = errors.New("[merkletree] Bad tree nonce")
)

func NewMerkleTreeFromKV(db kv.DB, epoch uint64) (*MerkleTree, error) {
nonceKey := append([]byte{TreeNonceIdentifier}, util.ULongToBytes(epoch)...)
val, err := db.Get(nonceKey)
var nonce []byte
if err != nil {
return nil, err
} else if len(val) != crypto.HashSizeByte {
return nil, ErrorBadTreeNonce
} else {
nonce = val
}
m := new(MerkleTree)
m.nonce = nonce
root, err := m.reconstructTree(db, nil, epoch, []bool{})
if err != nil {
return nil, err
}
m.root = root.(*interiorNode)
m.hash = m.root.Hash(m)
return m, nil
}

func (m *MerkleTree) reconstructTree(db kv.DB, parent MerkleNode, epoch uint64, prefixBits []bool) (MerkleNode, error) {
Copy link
Copy Markdown
Member

@masomel masomel Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why reconstructTree needs to be a method of MerkleTree? None of m's fields are used in this function.

n, err := loadNode(db, epoch, prefixBits)
if err != nil {
return nil, err
}

if _, ok := n.(*emptyNode); ok {
n.(*emptyNode).parent = parent
return n, nil
}
if _, ok := n.(*userLeafNode); ok {
n.(*userLeafNode).parent = parent
return n, nil
}
n.(*interiorNode).parent = parent
n.(*interiorNode).leftChild, err = m.reconstructTree(db, n, epoch, append(prefixBits, false))
if err != nil {
return nil, err
}
n.(*interiorNode).rightChild, err = m.reconstructTree(db, n, epoch, append(prefixBits, true))
if err != nil {
return nil, err
}
return n, nil
}

func ReconstructBranch(db kv.DB, epoch uint64, index []byte) (*MerkleTree, error) {
indexBits := util.ToBits(index)
m := new(MerkleTree)
root, err := loadNode(db, epoch, []bool{})
if err != nil {
return nil, err
}
m.root = root.(*interiorNode)
var parent = root
loadingLoop:
for depth := 1; depth < len(indexBits); depth++ {
n, err := loadNode(db, epoch, indexBits[:depth])
if err != nil {
return nil, err
}
if indexBits[depth-1] {
parent.(*interiorNode).rightChild = n
} else {
parent.(*interiorNode).leftChild = n
}
switch n.(type) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this switch needs a default case to throw an error in case we encounter some malformed data.

case *userLeafNode:
n.(*userLeafNode).parent = parent
break loadingLoop
case *emptyNode:
n.(*emptyNode).parent = parent
break loadingLoop
case *interiorNode:
n.(*interiorNode).parent = parent
}
parent = n
}

return m, nil
}

func (m *MerkleTree) StoreToKV(epoch uint64, wb kv.Batch) {
// store tree nodes
m.root.storeToKV(epoch, []bool{}, wb)
// store tree nonce
wb.Put(append([]byte{TreeNonceIdentifier}, util.ULongToBytes(epoch)...), m.nonce)
}
128 changes: 128 additions & 0 deletions merkletree/merkletreekv_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package merkletree

import (
"bytes"
"testing"

"github.com/coniks-sys/coniks-go/storage/kv"
"github.com/coniks-sys/coniks-go/utils"
)

func TestTreeStore(t *testing.T) {
util.WithDB(func(db kv.DB) {
key1 := "key1"
val1 := []byte("value1")
key2 := "key2"
val2 := []byte("value2")

m1, err := NewMerkleTree()
if err != nil {
t.Fatal(err)
}
index1 := vrfPrivKey1.Compute([]byte(key1))
if err := m1.Set(index1, key1, val1); err != nil {
t.Fatal(err)
}
index2 := vrfPrivKey1.Compute([]byte(key2))
if err := m1.Set(index2, key2, val2); err != nil {
t.Fatal(err)
}
m1.recomputeHash()

wb := db.NewBatch()
m1.StoreToKV(1, wb)
err = db.Write(wb)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why db.Write(wb) isn't called from within m.StoreToKV()?

if err != nil {
t.Fatal(err)
}

m2, err := NewMerkleTreeFromKV(db, 1)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(m2.nonce, m1.nonce) ||
!bytes.Equal(m2.root.Hash(m1), m1.hash) {
t.Fatal("Bad tree construction")
}

ap := m2.Get(index1)
if ap.Leaf.Value() == nil {
Copy link
Copy Markdown
Member

@masomel masomel Sep 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the only condition that indicates that key1 cannot be found in the tree? I'm wondering if we should also be checking the leaf node type and the leaf's index here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same goes for L58, L105, and L120.

t.Error("Cannot find key:", key1)
return
}
if !bytes.Equal(ap.Leaf.Value(), val1) {
t.Error(key1, "value mismatch")
}

ap = m2.Get(index2)
if ap.Leaf.Value() == nil {
t.Error("Cannot find key:", key2)
return
}
if !bytes.Equal(ap.Leaf.Value(), val2) {
t.Error(key2, "value mismatch")
}
})
}

func TestReconstructBranch(t *testing.T) {
util.WithDB(func(db kv.DB) {
key1 := "key1"
val1 := []byte("value1")
key2 := "key3"
val2 := []byte("value2")

m1, err := NewMerkleTree()
if err != nil {
t.Fatal(err)
}
index1 := vrfPrivKey1.Compute([]byte(key1))
if err := m1.Set(index1, key1, val1); err != nil {
t.Fatal(err)
}
index2 := vrfPrivKey1.Compute([]byte(key2))
if err := m1.Set(index2, key2, val2); err != nil {
t.Fatal(err)
}
m1.recomputeHash()

wb := db.NewBatch()
m1.StoreToKV(1, wb)
err = db.Write(wb)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as L34.

if err != nil {
t.Fatal(err)
}

if err != nil {
t.Fatal(err)
}

m2_1, err := ReconstructBranch(db, 1, index1)
if err != nil {
t.Fatal(err)
}
ap := m2_1.Get(index1)
if ap.Leaf.Value() == nil {
t.Error("Cannot find key:", key1)
return
}
if !bytes.Equal(ap.Leaf.Value(), val1) {
t.Error(key1, "value mismatch",
"want", val1,
"get", ap.Leaf.Value())
}

m2_2, err := ReconstructBranch(db, 1, index2)
if err != nil {
t.Fatal(err)
}
ap = m2_2.Get(index2)
if ap.Leaf.Value() == nil {
t.Error("Cannot find key:", key2)
return
}
if !bytes.Equal(ap.Leaf.Value(), val2) {
t.Error(key2, "value mismatch")
}
})
}
6 changes: 4 additions & 2 deletions merkletree/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package merkletree

import (
"github.com/coniks-sys/coniks-go/crypto"
"github.com/coniks-sys/coniks-go/storage/kv"
"github.com/coniks-sys/coniks-go/utils"
)

Expand Down Expand Up @@ -70,6 +71,7 @@ type MerkleNode interface {
isEmpty() bool
Hash(*MerkleTree) []byte
Clone(*interiorNode) MerkleNode
storeToKV(epoch uint64, prefixBits []bool, wb kv.Batch)
}

var _ MerkleNode = (*userLeafNode)(nil)
Expand Down Expand Up @@ -147,11 +149,11 @@ func (n *emptyNode) Clone(parent *interiorNode) MerkleNode {
}
}

func (n *userLeafNode) isEmpty() bool {
func (n *interiorNode) isEmpty() bool {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason the node types were flipped between here and L156?

return false
}

func (n *interiorNode) isEmpty() bool {
func (n *userLeafNode) isEmpty() bool {
return false
}

Expand Down
Loading