diff --git a/merkletree/merkletree.go b/merkletree/merkletree.go index d20ee3d..9097917 100644 --- a/merkletree/merkletree.go +++ b/merkletree/merkletree.go @@ -2,7 +2,9 @@ package merkletree import ( "bytes" + "encoding/gob" "errors" + "io" "github.com/coniks-sys/coniks-go/crypto" "github.com/coniks-sys/coniks-go/utils" @@ -74,10 +76,10 @@ func (m *MerkleTree) Get(lookupIndex []byte) *AuthenticationPath { direction := lookupIndexBits[depth] var hashArr [crypto.HashSizeByte]byte if direction { - copy(hashArr[:], nodePointer.(*interiorNode).leftHash) + copy(hashArr[:], nodePointer.(*interiorNode).LeftHash) nodePointer = nodePointer.(*interiorNode).rightChild } else { - copy(hashArr[:], nodePointer.(*interiorNode).rightHash) + copy(hashArr[:], nodePointer.(*interiorNode).RightHash) nodePointer = nodePointer.(*interiorNode).leftChild } authPath.PrunedTree = append(authPath.PrunedTree, hashArr) @@ -91,13 +93,13 @@ func (m *MerkleTree) Get(lookupIndex []byte) *AuthenticationPath { case *userLeafNode: pNode := nodePointer.(*userLeafNode) authPath.Leaf = &ProofNode{ - Level: pNode.level, - Index: pNode.index, - Value: pNode.value, + Level: pNode.Level, + Index: pNode.Index, + Value: pNode.Value, IsEmpty: false, - Commitment: &crypto.Commit{pNode.commitment.Salt, pNode.commitment.Value}, + Commitment: &crypto.Commit{pNode.Commitment.Salt, pNode.Commitment.Value}, } - if bytes.Equal(nodePointer.(*userLeafNode).index, lookupIndex) { + if bytes.Equal(nodePointer.(*userLeafNode).Index, lookupIndex) { return authPath } // reached a different leaf with a matching prefix @@ -108,8 +110,8 @@ func (m *MerkleTree) Get(lookupIndex []byte) *AuthenticationPath { case *emptyNode: pNode := nodePointer.(*emptyNode) authPath.Leaf = &ProofNode{ - Level: pNode.level, - Index: pNode.index, + Level: pNode.Level, + Index: pNode.Index, Value: nil, IsEmpty: true, Commitment: nil, @@ -130,10 +132,10 @@ func (m *MerkleTree) Set(index []byte, key string, value []byte) error { return err } toAdd := userLeafNode{ - key: key, - value: append([]byte{}, value...), // make a copy of value - index: index, - commitment: commitment, + Key: key, + Value: append([]byte{}, value...), // make a copy of value + Index: index, + Commitment: commitment, } m.insertNode(index, &toAdd) return nil @@ -157,23 +159,23 @@ insertLoop: panic(ErrInvalidTree) } - if bytes.Equal(currentNodeUL.index, toAdd.index) { + if bytes.Equal(currentNodeUL.Index, toAdd.Index) { // replace the value toAdd.parent = currentNodeUL.parent - toAdd.level = currentNodeUL.level + toAdd.Level = currentNodeUL.Level *currentNodeUL = *toAdd return } newInteriorNode := newInteriorNode(currentNodeUL.parent, depth, indexBits[:depth]) - direction := utils.GetNthBit(currentNodeUL.index, depth) + direction := utils.GetNthBit(currentNodeUL.Index, depth) if direction { newInteriorNode.rightChild = currentNodeUL } else { newInteriorNode.leftChild = currentNodeUL } - currentNodeUL.level = depth + 1 + currentNodeUL.Level = depth + 1 currentNodeUL.parent = newInteriorNode if newInteriorNode.parent.(*interiorNode).leftChild == nodePointer { newInteriorNode.parent.(*interiorNode).leftChild = newInteriorNode @@ -185,20 +187,20 @@ insertLoop: currentNodeI := nodePointer.(*interiorNode) direction := indexBits[depth] if direction { // go right - currentNodeI.rightHash = nil + currentNodeI.RightHash = nil if currentNodeI.rightChild.isEmpty() { currentNodeI.rightChild = toAdd - toAdd.level = depth + 1 + toAdd.Level = depth + 1 toAdd.parent = currentNodeI break insertLoop } else { nodePointer = currentNodeI.rightChild } } else { // go left - currentNodeI.leftHash = nil + currentNodeI.LeftHash = nil if currentNodeI.leftChild.isEmpty() { currentNodeI.leftChild = toAdd - toAdd.level = depth + 1 + toAdd.Level = depth + 1 toAdd.parent = currentNodeI break insertLoop } else { @@ -250,3 +252,59 @@ func (m *MerkleTree) Clone() *MerkleTree { hash: append([]byte{}, m.hash...), } } + +// encodeTree writes the tree's nonce into buff first, +// following by all the nodes data. +func encodeTree(buff io.Writer, m *MerkleTree) error { + enc := gob.NewEncoder(buff) + if err := enc.Encode(m.nonce); err != nil { + return err + } + if err := encodeNode(enc, m.root); err != nil { + return err + } + return nil +} + +// decodeTree reconstructs the tree from the buffer +// that was written using encodeTree. +func decodeTree(buff io.Reader) (*MerkleTree, error) { + m := new(MerkleTree) + dec := gob.NewDecoder(buff) + if err := dec.Decode(&m.nonce); err != nil { + return nil, err + } + root, err := reconstructTree(dec, nil) + if err != nil { + return nil, err + } + m.root = root.(*interiorNode) + m.hash = m.root.hash(m) + return m, nil +} + +func reconstructTree(dec *gob.Decoder, parent merkleNode) (merkleNode, error) { + n, err := decodeNode(dec) + 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 = reconstructTree(dec, n) + if err != nil { + return nil, err + } + n.(*interiorNode).rightChild, err = reconstructTree(dec, n) + if err != nil { + return nil, err + } + return n, nil +} diff --git a/merkletree/merkletree_test.go b/merkletree/merkletree_test.go index a97e39a..6e211b5 100644 --- a/merkletree/merkletree_test.go +++ b/merkletree/merkletree_test.go @@ -39,10 +39,10 @@ func TestOneEntry(t *testing.T) { h.Write(utils.ToBytes([]bool{true})) h.Write(utils.UInt32ToBytes(1)) h.Read(expect[:]) - if !bytes.Equal(m.root.rightHash, expect[:]) { + if !bytes.Equal(m.root.RightHash, expect[:]) { t.Error("Wrong righ hash!", "expected", expect, - "get", m.root.rightHash) + "get", m.root.RightHash) } r := m.Get(index) @@ -70,10 +70,10 @@ func TestOneEntry(t *testing.T) { h.Write(commit[:]) h.Read(expect[:]) - if !bytes.Equal(m.root.leftHash, expect[:]) { + if !bytes.Equal(m.root.LeftHash, expect[:]) { t.Error("Wrong left hash!", "expected", expect, - "get", m.root.leftHash) + "get", m.root.LeftHash) } r = m.Get([]byte("abc")) diff --git a/merkletree/mock_ad.go b/merkletree/mock_ad.go new file mode 100644 index 0000000..0285a0b --- /dev/null +++ b/merkletree/mock_ad.go @@ -0,0 +1,15 @@ +package merkletree + +import "encoding/gob" + +func init() { + gob.Register(&MockAd{}) +} + +type MockAd struct { + Data string +} + +func (t *MockAd) Serialize() []byte { + return []byte(t.Data) +} diff --git a/merkletree/node.go b/merkletree/node.go index ea11171..f2d5b88 100644 --- a/merkletree/node.go +++ b/merkletree/node.go @@ -1,34 +1,38 @@ package merkletree import ( + "encoding/gob" + "github.com/coniks-sys/coniks-go/crypto" "github.com/coniks-sys/coniks-go/utils" ) type node struct { parent merkleNode - level uint32 } type interiorNode struct { node + Level uint32 leftChild merkleNode rightChild merkleNode - leftHash []byte - rightHash []byte + LeftHash []byte + RightHash []byte } type userLeafNode struct { node - key string - value []byte - index []byte - commitment *crypto.Commit + Level uint32 + Key string + Value []byte + Index []byte + Commitment *crypto.Commit } type emptyNode struct { node - index []byte + Level uint32 + Index []byte } func newInteriorNode(parent merkleNode, level uint32, prefixBits []bool) *interiorNode { @@ -37,27 +41,23 @@ func newInteriorNode(parent merkleNode, level uint32, prefixBits []bool) *interi prefixRight := append([]bool(nil), prefixBits...) prefixRight = append(prefixRight, true) leftBranch := &emptyNode{ - node: node{ - level: level + 1, - }, - index: utils.ToBytes(prefixLeft), + Level: level + 1, + Index: utils.ToBytes(prefixLeft), } rightBranch := &emptyNode{ - node: node{ - level: level + 1, - }, - index: utils.ToBytes(prefixRight), + Level: level + 1, + Index: utils.ToBytes(prefixRight), } newNode := &interiorNode{ node: node{ parent: parent, - level: level, }, + Level: level, leftChild: leftBranch, rightChild: rightBranch, - leftHash: nil, - rightHash: nil, + LeftHash: nil, + RightHash: nil, } leftBranch.parent = newNode rightBranch.parent = newNode @@ -76,22 +76,22 @@ var _ merkleNode = (*interiorNode)(nil) var _ merkleNode = (*emptyNode)(nil) func (n *interiorNode) hash(m *MerkleTree) []byte { - if n.leftHash == nil { - n.leftHash = n.leftChild.hash(m) + if n.LeftHash == nil { + n.LeftHash = n.leftChild.hash(m) } - if n.rightHash == nil { - n.rightHash = n.rightChild.hash(m) + if n.RightHash == nil { + n.RightHash = n.rightChild.hash(m) } - return crypto.Digest(n.leftHash, n.rightHash) + return crypto.Digest(n.LeftHash, n.RightHash) } func (n *userLeafNode) hash(m *MerkleTree) []byte { return crypto.Digest( []byte{LeafIdentifier}, // K_leaf []byte(m.nonce), // K_n - []byte(n.index), // i - []byte(utils.UInt32ToBytes(n.level)), // l - []byte(n.commitment.Value), // commit(key|| value) + []byte(n.Index), // i + []byte(utils.UInt32ToBytes(n.Level)), // l + []byte(n.Commitment.Value), // commit(key|| value) ) } @@ -99,8 +99,8 @@ func (n *emptyNode) hash(m *MerkleTree) []byte { return crypto.Digest( []byte{EmptyBranchIdentifier}, // K_empty []byte(m.nonce), // K_n - []byte(n.index), // i - []byte(utils.UInt32ToBytes(n.level)), // l + []byte(n.Index), // i + []byte(utils.UInt32ToBytes(n.Level)), // l ) } @@ -108,10 +108,10 @@ func (n *interiorNode) clone(parent *interiorNode) merkleNode { newNode := &interiorNode{ node: node{ parent: parent, - level: n.level, }, - leftHash: append([]byte{}, n.leftHash...), - rightHash: append([]byte{}, n.rightHash...), + Level: n.Level, + LeftHash: append([]byte{}, n.LeftHash...), + RightHash: append([]byte{}, n.RightHash...), } if n.leftChild == nil || n.rightChild == nil { @@ -126,12 +126,12 @@ func (n *userLeafNode) clone(parent *interiorNode) merkleNode { return &userLeafNode{ node: node{ parent: parent, - level: n.level, }, - key: n.key, - value: n.value, - index: append([]byte{}, n.index...), // make a copy of index - commitment: n.commitment, + Level: n.Level, + Key: n.Key, + Value: n.Value, + Index: append([]byte{}, n.Index...), // make a copy of index + Commitment: n.Commitment, } } @@ -139,9 +139,9 @@ func (n *emptyNode) clone(parent *interiorNode) merkleNode { return &emptyNode{ node: node{ parent: parent, - level: n.level, }, - index: append([]byte{}, n.index...), // make a copy of index + Level: n.Level, + Index: append([]byte{}, n.Index...), // make a copy of index } } @@ -156,3 +156,40 @@ func (n *interiorNode) isEmpty() bool { func (n *emptyNode) isEmpty() bool { return true } + +// gob encoding/decoding + +func init() { + gob.Register(&interiorNode{}) + gob.Register(&userLeafNode{}) + gob.Register(&emptyNode{}) +} + +// encodeNode encodes a merkleNode n using the gob.Encoder enc. +// If n is an interior node, this also encodes n's children recursively. +func encodeNode(enc *gob.Encoder, n merkleNode) error { + err := enc.Encode(&n) + if err != nil { + return err + } + if in, ok := n.(*interiorNode); ok { + err = encodeNode(enc, in.leftChild) + if err != nil { + return err + } + err = encodeNode(enc, in.rightChild) + if err != nil { + return err + } + } + return nil +} + +// decodeNode returns a merkleNode from the decoder. +func decodeNode(dec *gob.Decoder) (merkleNode, error) { + var get merkleNode + if err := dec.Decode(&get); err != nil { + return nil, err + } + return get, nil +} diff --git a/merkletree/pad.go b/merkletree/pad.go index 2754263..c19f899 100644 --- a/merkletree/pad.go +++ b/merkletree/pad.go @@ -2,7 +2,9 @@ package merkletree import ( "bytes" + "encoding/gob" "errors" + "io" "github.com/coniks-sys/coniks-go/crypto" "github.com/coniks-sys/coniks-go/crypto/sign" @@ -169,7 +171,7 @@ func (pad *PAD) reshuffle() { panic(err) } pad.tree.visitLeafNodes(func(n *userLeafNode) { - if err := newTree.Set(pad.Index(n.key), n.key, n.value); err != nil { + if err := newTree.Set(pad.Index(n.Key), n.Key, n.Value); err != nil { panic(err) } }) @@ -180,3 +182,60 @@ func (pad *PAD) computePrivateIndex(key string, vrfKey vrf.PrivateKey) (index, p index, proof = vrfKey.Prove([]byte(key)) return } + +// EncodePAD encodes the pad as following order: +// the signing key, the pad's current tree (possibly includes new bindings), +// the latest version of the STR and the current policies in place. +func EncodePAD(buff io.Writer, pad *PAD) error { + enc := gob.NewEncoder(buff) + // FIXME: Do we really want to save the private keys to db? + if err := enc.Encode(pad.signKey); err != nil { + return err + } + if err := enc.Encode(pad.vrfKey); err != nil { + return err + } + if err := encodeTree(buff, pad.tree); err != nil { + return err + } + if err := EncodeSTR(buff, pad.latestSTR); err != nil { + return err + } + if err := enc.Encode(&pad.ad); err != nil { + return err + } + return nil +} + +// DecodePAD reconstructs the PAD from the buff written by EncodePAD. +func DecodePAD(length uint64, buff io.Reader) (*PAD, error) { + var pad PAD + pad.snapshots = make(map[uint64]*SignedTreeRoot, length) + pad.loadedEpochs = make([]uint64, 0, length) + + dec := gob.NewDecoder(buff) + if err := dec.Decode(&pad.signKey); err != nil { + return nil, err + } + if err := dec.Decode(&pad.vrfKey); err != nil { + return nil, err + } + m, err := decodeTree(buff) + if err != nil { + return nil, err + } + pad.tree = m + + str, err := DecodeSTR(buff) + if err != nil { + return nil, err + } + pad.latestSTR = str + pad.snapshots[str.Epoch] = str + pad.loadedEpochs = append(pad.loadedEpochs, str.Epoch) + + if err := dec.Decode(&pad.ad); err != nil { + return nil, err + } + return &pad, nil +} diff --git a/merkletree/pad_test.go b/merkletree/pad_test.go index 3b4436d..4c69f9b 100644 --- a/merkletree/pad_test.go +++ b/merkletree/pad_test.go @@ -2,12 +2,12 @@ package merkletree import ( "bytes" - "testing" - "crypto/rand" "errors" "fmt" "io" + "reflect" + "testing" "github.com/coniks-sys/coniks-go/crypto/sign" ) @@ -22,14 +22,6 @@ func init() { } } -type TestAd struct { - data string -} - -func (t TestAd) Serialize() []byte { - return []byte(t.data) -} - // 1st: epoch = 0 (empty tree) // 2nd: epoch = 1 (key1) // 3rd: epoch = 2 (key1, key2) @@ -46,7 +38,7 @@ func TestPADHashChain(t *testing.T) { treeHashes := make(map[uint64][]byte) - pad, err := NewPAD(TestAd{""}, signKey, vrfPrivKey1, 10) + pad, err := NewPAD(&MockAd{""}, signKey, vrfPrivKey1, 10) if err != nil { t.Fatal(err) } @@ -152,7 +144,7 @@ func TestPADHashChain(t *testing.T) { func TestHashChainExceedsMaximumSize(t *testing.T) { var hashChainLimit uint64 = 4 - pad, err := NewPAD(TestAd{""}, signKey, vrfPrivKey2, hashChainLimit) + pad, err := NewPAD(&MockAd{""}, signKey, vrfPrivKey2, hashChainLimit) if err != nil { t.Fatal(err) } @@ -193,7 +185,7 @@ func TestAssocDataChange(t *testing.T) { key3 := "key3" val3 := []byte("value3") - pad, err := NewPAD(TestAd{""}, signKey, vrfPrivKey1, 10) + pad, err := NewPAD(&MockAd{""}, signKey, vrfPrivKey1, 10) if err != nil { t.Fatal(err) } @@ -202,7 +194,7 @@ func TestAssocDataChange(t *testing.T) { t.Fatal(err) } // TODO: key change between epoch 1 and 2: - pad.Update(TestAd{""}) + pad.Update(&MockAd{""}) if err := pad.Set(key2, val2); err != nil { t.Fatal(err) @@ -261,6 +253,66 @@ func TestNewPADMissingAssocData(t *testing.T) { } } +func TestPADEncodingDecoding(t *testing.T) { + key1 := "key" + val1 := []byte("value") + + key2 := "key2" + val2 := []byte("value2") + + key3 := "key3" + val3 := []byte("value3") + + pad, err := NewPAD(&MockAd{"data"}, signKey, vrfPrivKey1, 10) + if err != nil { + t.Fatal(err) + } + + if err := pad.Set(key1, val1); err != nil { + t.Fatal(err) + } + if err := pad.Set(key2, val2); err != nil { + t.Fatal(err) + } + pad.Update(&MockAd{"data"}) + // insert a new binding and don't update the pad + if err := pad.Set(key3, val3); err != nil { + t.Fatal(err) + } + index3 := vrfPrivKey1.Compute([]byte(key3)) + + // encode the pad + var buff bytes.Buffer + if err := EncodePAD(&buff, pad); err != nil { + t.Fatal(err) + } + + padGot, err := DecodePAD(10, &buff) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(padGot.ad, pad.ad) || + !bytes.Equal(padGot.LatestSTR().Signature, pad.LatestSTR().Signature) { + t.Fatal("Malformed encoding/decoding") + } + + ap, err := padGot.Lookup(key1) + if ap.Leaf.Value == nil || !bytes.Equal(ap.LookupIndex, ap.Leaf.Index) { + t.Fatalf("Cannot find key: %v", key1) + } + ap, err = padGot.Lookup(key2) + if ap.Leaf.Value == nil || !bytes.Equal(ap.LookupIndex, ap.Leaf.Index) { + t.Fatalf("Cannot find key: %v", key2) + } + + ap = padGot.tree.Get(index3) + if ap.Leaf.Value == nil || !bytes.Equal(ap.LookupIndex, ap.Leaf.Index) { + t.Fatalf("Cannot find key: %v", key3) + } + padGot.Update(nil) // just to make sure everything is okay +} + // TODO move the following to some (internal?) testutils package type testErrorRandReader struct{} @@ -282,7 +334,7 @@ func TestNewPADErrorWhileCreatingTree(t *testing.T) { origRand := mockRandReadWithErroringReader() defer unMockRandReader(origRand) - pad, err := NewPAD(TestAd{""}, signKey, vrfPrivKey1, 3) + pad, err := NewPAD(&MockAd{""}, signKey, vrfPrivKey1, 3) if err == nil || pad != nil { t.Fatal("NewPad should return an error in case the tree creation failed") } @@ -388,7 +440,7 @@ func benchPADLookup(b *testing.B, entries uint64) { // `updateEvery`. If `updateEvery > N` createPAD won't update the STR. func createPad(N uint64, keyPrefix string, valuePrefix []byte, snapLen uint64, updateEvery uint64) (*PAD, error) { - pad, err := NewPAD(TestAd{""}, signKey, vrfPrivKey1, snapLen) + pad, err := NewPAD(&MockAd{""}, signKey, vrfPrivKey1, snapLen) if err != nil { return nil, err } diff --git a/merkletree/str.go b/merkletree/str.go index 26d5982..25cb169 100644 --- a/merkletree/str.go +++ b/merkletree/str.go @@ -2,6 +2,8 @@ package merkletree import ( "bytes" + "encoding/gob" + "io" "github.com/coniks-sys/coniks-go/crypto" "github.com/coniks-sys/coniks-go/crypto/sign" @@ -75,3 +77,30 @@ func (str *SignedTreeRoot) VerifyHashChain(savedSTR *SignedTreeRoot) bool { str.Epoch == savedSTR.Epoch+1 && bytes.Equal(hash, str.PreviousSTRHash) } + +// EncodeSTR encodes the export fields of the +// SignedTreeRoot str following by the corresponding Merkle tree. +func EncodeSTR(buff io.Writer, str *SignedTreeRoot) error { + enc := gob.NewEncoder(buff) + if err := enc.Encode(&str); err != nil { + return err + } + if err := encodeTree(buff, str.tree); err != nil { + return err + } + return nil +} + +// DecodeSTR reconstructs the STR from the buff written by EncodeSTR. +func DecodeSTR(buff io.Reader) (*SignedTreeRoot, error) { + dec := gob.NewDecoder(buff) + var str SignedTreeRoot + var err error + if err = dec.Decode(&str); err != nil { + return nil, err + } + if str.tree, err = decodeTree(buff); err != nil { + return nil, err + } + return &str, nil +} diff --git a/merkletree/str_test.go b/merkletree/str_test.go index dbfff47..a211507 100644 --- a/merkletree/str_test.go +++ b/merkletree/str_test.go @@ -1,13 +1,17 @@ package merkletree -import "testing" +import ( + "bytes" + "reflect" + "testing" +) func TestVerifyHashChain(t *testing.T) { var N uint64 = 100 keyPrefix := "key" valuePrefix := []byte("value") - pad, err := NewPAD(TestAd{""}, signKey, vrfPrivKey1, 10) + pad, err := NewPAD(&MockAd{""}, signKey, vrfPrivKey1, 10) if err != nil { t.Fatal(err) } @@ -40,3 +44,26 @@ func TestVerifyHashChain(t *testing.T) { savedSTR = str } } + +func TestSTREncodingDecoding(t *testing.T) { + pad, err := NewPAD(&MockAd{"data"}, signKey, vrfPrivKey1, 10) + if err != nil { + t.Fatal(err) + } + + var buff bytes.Buffer + if err = EncodeSTR(&buff, pad.LatestSTR()); err != nil { + t.Fatal(err) + } + + strGot, err := DecodeSTR(&buff) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(strGot.Ad, pad.LatestSTR().Ad) || + !bytes.Equal(strGot.tree.hash, pad.LatestSTR().tree.hash) || + !bytes.Equal(strGot.Signature, pad.LatestSTR().Signature) { + t.Fatal("Malformed encoding/decoding") + } +} diff --git a/storage/kv/merkletreekv/doc.go b/storage/kv/merkletreekv/doc.go new file mode 100644 index 0000000..41d35b2 --- /dev/null +++ b/storage/kv/merkletreekv/doc.go @@ -0,0 +1,5 @@ +/* +Package merkletreekv implements the database backend hook +for package merkletree. +*/ +package merkletreekv diff --git a/storage/kv/merkletreekv/identifier.go b/storage/kv/merkletreekv/identifier.go new file mode 100644 index 0000000..15a3886 --- /dev/null +++ b/storage/kv/merkletreekv/identifier.go @@ -0,0 +1,8 @@ +package merkletreekv + +const ( + // STRIdentifier is the domain separation for STR. + STRIdentifier = 'S' + // PADIdentifier is the domain separation for PAD. + PADIdentifier = 'O' +) diff --git a/storage/kv/merkletreekv/padkv.go b/storage/kv/merkletreekv/padkv.go new file mode 100644 index 0000000..e44f6eb --- /dev/null +++ b/storage/kv/merkletreekv/padkv.go @@ -0,0 +1,33 @@ +package merkletreekv + +import ( + "bytes" + + "github.com/coniks-sys/coniks-go/merkletree" + "github.com/coniks-sys/coniks-go/storage/kv" +) + +// StorePAD stores pad to the db. +// StorePAD uses the same key to store the PAD, so the old PAD +// would be replaced by the newly stored PAD. +func StorePAD(db kv.DB, pad *merkletree.PAD) error { + wb := db.NewBatch() + buff := new(bytes.Buffer) + if err := merkletree.EncodePAD(buff, pad); err != nil { + return err + } + wb.Put([]byte{PADIdentifier}, buff.Bytes()) + return db.Write(wb) +} + +// LoadPAD reconstructs the PAD stored in the db. +// It requires the caller pass the maximum capacity +// for the snapshot cache length. +func LoadPAD(db kv.DB, length uint64) (*merkletree.PAD, error) { + bbuff, err := db.Get([]byte{PADIdentifier}) + if err != nil { + return nil, err + } + buff := bytes.NewBuffer(bbuff) + return merkletree.DecodePAD(length, buff) +} diff --git a/storage/kv/merkletreekv/padkv_test.go b/storage/kv/merkletreekv/padkv_test.go new file mode 100644 index 0000000..14cf07f --- /dev/null +++ b/storage/kv/merkletreekv/padkv_test.go @@ -0,0 +1,55 @@ +package merkletreekv + +import ( + "testing" + + "github.com/coniks-sys/coniks-go/crypto/sign" + "github.com/coniks-sys/coniks-go/crypto/vrf" + "github.com/coniks-sys/coniks-go/merkletree" + "github.com/coniks-sys/coniks-go/storage/kv" + "github.com/coniks-sys/coniks-go/utils" +) + +func TestPADStoreLoad(t *testing.T) { + utils.WithDB(func(db kv.DB) { + key1 := "key" + val1 := []byte("value") + + key2 := "key2" + val2 := []byte("value2") + + key3 := "key3" + val3 := []byte("value3") + vrfPrivKey1, _ := vrf.GenerateKey(nil) + signKey, _ := sign.GenerateKey(nil) + + pad, err := merkletree.NewPAD(&merkletree.MockAd{""}, signKey, vrfPrivKey1, 10) + if err != nil { + t.Fatal(err) + } + if err := pad.Set(key1, val1); err != nil { + t.Fatal(err) + } + if err := pad.Set(key2, val2); err != nil { + t.Fatal(err) + } + if err := pad.Set(key3, val3); err != nil { + t.Fatal(err) + } + pad.Update(nil) + + if err := StorePAD(db, pad); err != nil { + t.Fatal(err) + } + + padGot, err := LoadPAD(db, 10) + if err != nil { + t.Fatal(err) + } + ap, err := padGot.Lookup(key1) + if ap.Leaf.Value == nil { + t.Fatalf("Cannot find key: %v", key1) + } + padGot.Update(nil) // just to make sure everything is okay + }) +} diff --git a/storage/kv/merkletreekv/strkv.go b/storage/kv/merkletreekv/strkv.go new file mode 100644 index 0000000..950f528 --- /dev/null +++ b/storage/kv/merkletreekv/strkv.go @@ -0,0 +1,40 @@ +package merkletreekv + +import ( + "bytes" + + "github.com/coniks-sys/coniks-go/merkletree" + "github.com/coniks-sys/coniks-go/storage/kv" + "github.com/coniks-sys/coniks-go/utils" +) + +// StoreSTR stores str into the db under the key +// which is the combination of the STRIdentifier +// and the str's Epoch. +func StoreSTR(db kv.DB, str *merkletree.SignedTreeRoot) error { + wb := db.NewBatch() + buff := new(bytes.Buffer) + if err := merkletree.EncodeSTR(buff, str); err != nil { + return err + } + + wb.Put(strKey(str.Epoch), buff.Bytes()) + return db.Write(wb) +} + +// LoadSTR loads the STR corresponding to the specified epoch. +func LoadSTR(db kv.DB, epoch uint64) (*merkletree.SignedTreeRoot, error) { + bbuff, err := db.Get(strKey(epoch)) + if err != nil { + return nil, err + } + buff := bytes.NewBuffer(bbuff) + return merkletree.DecodeSTR(buff) +} + +func strKey(epoch uint64) []byte { + key := make([]byte, 0, 1+8) + key = append(key, STRIdentifier) + key = append(key, utils.ULongToBytes(epoch)...) + return key +} diff --git a/storage/kv/merkletreekv/strkv_test.go b/storage/kv/merkletreekv/strkv_test.go new file mode 100644 index 0000000..5d4c26c --- /dev/null +++ b/storage/kv/merkletreekv/strkv_test.go @@ -0,0 +1,81 @@ +package merkletreekv + +import ( + "bytes" + "testing" + + "github.com/coniks-sys/coniks-go/crypto/sign" + "github.com/coniks-sys/coniks-go/crypto/vrf" + "github.com/coniks-sys/coniks-go/merkletree" + "github.com/coniks-sys/coniks-go/storage/kv" + "github.com/coniks-sys/coniks-go/utils" +) + +func TestSTRStore(t *testing.T) { + utils.WithDB(func(db kv.DB) { + key1 := "key" + val1 := []byte("value") + + key2 := "key2" + val2 := []byte("value2") + + vrfPrivKey1, _ := vrf.GenerateKey(nil) + signKey, _ := sign.GenerateKey(nil) + + pad, err := merkletree.NewPAD(&merkletree.MockAd{""}, signKey, vrfPrivKey1, 10) + if err != nil { + t.Fatal(err) + } + if err := pad.Set(key1, val1); err != nil { + t.Fatal(err) + } + pad.Update(nil) + str1 := pad.LatestSTR() + if err := StoreSTR(db, str1); err != nil { + t.Fatal(err) + } + + if err := pad.Set(key2, val2); err != nil { + t.Fatal(err) + } + pad.Update(nil) + str2 := pad.LatestSTR() + if err := StoreSTR(db, str2); err != nil { + t.Fatal(err) + } + + // test loading STR from db + strGot1, err := LoadSTR(db, 1) + if err != nil { + t.Fatal(err) + } + strGot2, err := LoadSTR(db, 2) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(str1.Signature, strGot1.Signature) || + !bytes.Equal(str1.PreviousSTRHash, strGot1.PreviousSTRHash) || + str1.PreviousEpoch != strGot1.PreviousEpoch || + str1.Epoch != strGot1.Epoch { + t.Fatal("Bad STR loading/storing", + "expect", str1, + "got", strGot1) + } + + if !bytes.Equal(str2.Signature, strGot2.Signature) || + !bytes.Equal(str2.PreviousSTRHash, strGot2.PreviousSTRHash) || + str2.PreviousEpoch != strGot2.PreviousEpoch || + str2.Epoch != strGot2.Epoch { + t.Fatal("Bad STR loading/storing", + "expect", str2, + "got", strGot2) + } + + // test get non-exist str from db + if _, err = LoadSTR(db, 3); err != db.ErrNotFound() { + t.Fatal("Got unexpected STR from db") + } + + // test key lookup from old STRs - see tests in kv/directorykv + }) +}