Skip to content

Commit

Permalink
CONIKS Hasher
Browse files Browse the repository at this point in the history
Add treeID, index, and depth to HashLeaf and HashEmtpy.
The fuffills the security requirements in #670.
  • Loading branch information
gdbelvin committed Jun 20, 2017
1 parent 2227dd6 commit 448544a
Show file tree
Hide file tree
Showing 19 changed files with 238 additions and 73 deletions.
10 changes: 6 additions & 4 deletions integration/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import (
"github.com/google/trillian/testonly"
)

const treeID = int64(0)

// RunMapIntegration runs a map integration test using the given map ID and client.
func RunMapIntegration(ctx context.Context, mapID int64, client trillian.TrillianMapClient) error {
{
Expand Down Expand Up @@ -117,8 +119,8 @@ func RunMapIntegration(ctx context.Context, mapID int64, client trillian.Trillia
if got, want := leaf.LeafValue, ev.LeafValue; !bytes.Equal(got, want) {
return fmt.Errorf("got value %s, want %s", got, want)
}
leafHash := h.HashLeaf(leaf.LeafValue)
if err := merkle.VerifyMapInclusionProof(leaf.Index, leafHash, r.GetMapRoot().GetRootHash(), incl.Inclusion, h); err != nil {
leafHash := h.HashLeaf(treeID, leaf.Index, h.BitLen(), leaf.LeafValue)
if err := merkle.VerifyMapInclusionProof(treeID, leaf.Index, leafHash, r.GetMapRoot().GetRootHash(), incl.Inclusion, h); err != nil {
return fmt.Errorf("verifyMapInclusionProof(%x): %v", leaf.Index, err)
}
}
Expand Down Expand Up @@ -147,8 +149,8 @@ func testForNonExistentLeaf(ctx context.Context, mapID int64,
if got, want := len(leaf.LeafValue), 0; got != want {
return fmt.Errorf("len(GetLeaves(%s).LeafValue): %v, want, %v", index1, got, want)
}
leafHash := h.HashLeaf(leaf.LeafValue)
if err := merkle.VerifyMapInclusionProof(leaf.Index, leafHash, latestRoot.RootHash, incl.Inclusion, h); err != nil {
leafHash := h.HashLeaf(treeID, leaf.Index, h.BitLen(), leaf.LeafValue)
if err := merkle.VerifyMapInclusionProof(treeID, leaf.Index, leafHash, latestRoot.RootHash, incl.Inclusion, h); err != nil {
return fmt.Errorf("VerifyMapInclusionProof(%x): %v", leaf.Index, err)
}
}
Expand Down
89 changes: 89 additions & 0 deletions merkle/coniks/coniks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package coniks provides hashing for maps.
package coniks

import (
"crypto"
"encoding/binary"

"github.com/google/trillian/merkle"
)

// Domain separation prefixes
var (
leafIdentifier = []byte("L")
emptyIdentifier = []byte("E")
)

// Default is the standard CONIKS hasher.
var Default = New(crypto.SHA512_256)

// hasher implements the sparse merkel tree hashing algorithm specified in the CONIKS paper.
type hasher struct {
crypto.Hash
}

// New creates a new merkle.TreeHasher using the passed in hash function.
func New(h crypto.Hash) merkle.MapHasher {
return &hasher{Hash: h}
}

// EmptyRoot returns the root of an empty tree.
func (m *hasher) EmptyRoot() []byte {
panic("EmptyRoot() not defined for coniks.Hasher")
}

// HashEmpty returns the hash of an empty branch at a given depth.
// A depth of 0 indicates the hash of an empty leaf.
// Empty branches within the tree are plain interior nodes e1 = H(e0, e0) etc.
func (m *hasher) HashEmpty(treeID int64, index []byte, height int) []byte {
depth := m.BitLen() - height

h := m.New()
h.Write(emptyIdentifier)
binary.Write(h, binary.BigEndian, uint64(treeID))
h.Write(index) // TODO block out the bits that are not part of the index.
binary.Write(h, binary.BigEndian, uint32(depth))
return h.Sum(nil)
}

// HashLeaf calculate the merkle tree leaf value:
// H(Identifier || treeID || depth || index || dataHash)
func (m *hasher) HashLeaf(treeID int64, index []byte, height int, leaf []byte) []byte {
depth := m.BitLen() - height

h := m.New()
h.Write(leafIdentifier)
binary.Write(h, binary.BigEndian, uint64(treeID))
h.Write(index) // TODO block out the bits that are not part of the index.
binary.Write(h, binary.BigEndian, uint32(depth))
h.Write(leaf)
return h.Sum(nil)
}

// HashChildren returns the inner Merkle tree node hash of the the two child nodes l and r.
// The hashed structure is H(l || r).
func (m *hasher) HashChildren(l, r []byte) []byte {
h := m.New()
h.Write(l)
h.Write(r)
return h.Sum(nil)
}

// BitLen returns the number of bits in the hash function.
func (m *hasher) BitLen() int {
return m.Size() * 8
}
47 changes: 47 additions & 0 deletions merkle/coniks/coniks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package coniks

import (
"bytes"
"encoding/hex"
"testing"
)

// h2b converts a hex string into a bytes string
func h2b(h string) []byte {
b, err := hex.DecodeString(h)
if err != nil {
panic("invalid hex string")
}
return b
}

func TestVectors(t *testing.T) {
for _, tc := range []struct {
treeID int64
index []byte
depth int
leaf []byte
want []byte
}{
{treeID: 0, index: []byte("foo"), depth: 128, leaf: []byte("leaf"), want: h2b("2d6c9f648b61e786e18bcba49d1dc62dee2020cec168f0d2c9e47a7bd4633f02")},
} {
height := Default.BitLen() - tc.depth
if got, want := Default.HashLeaf(tc.treeID, tc.index, height, tc.leaf), tc.want; !bytes.Equal(got, want) {
t.Errorf("HashLeaf(%v, %s, %v, %s): %x, want %x", tc.treeID, tc.index, tc.depth, tc.leaf, got, want)
}
}
}
8 changes: 5 additions & 3 deletions merkle/hstar2.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,14 @@ type HStar2LeafHash struct {
// HStar2 is a recursive implementation for calculating the root hash of a sparse
// Merkle tree.
type HStar2 struct {
treeID int64
hasher MapHasher
}

// NewHStar2 creates a new HStar2 tree calculator based on the passed in MapHasher.
func NewHStar2(hasher MapHasher) HStar2 {
func NewHStar2(treeID int64, hasher MapHasher) HStar2 {
return HStar2{
treeID: treeID,
hasher: hasher,
}
}
Expand All @@ -54,7 +56,7 @@ func (s *HStar2) HStar2Root(n int, values []HStar2LeafHash) ([]byte, error) {
offset := big.NewInt(0)
return s.hStar2b(n, values, offset,
func(depth int, index *big.Int) ([]byte, error) {
return s.hasher.HashEmpty(depth), nil
return s.hasher.HashEmpty(s.treeID, index.Bytes(), depth), nil
},
func(int, *big.Int, []byte) error { return nil })
}
Expand Down Expand Up @@ -96,7 +98,7 @@ func (s *HStar2) HStar2Nodes(treeDepth, treeLevelOffset int, values []HStar2Leaf
return h, nil
}
// otherwise just return the null hash for this level
return s.hasher.HashEmpty(depth + treeLevelOffset), nil
return s.hasher.HashEmpty(s.treeID, index.Bytes(), depth+treeLevelOffset), nil
},
func(depth int, index *big.Int, hash []byte) error {
return set(treeDepth-depth, index, hash)
Expand Down
31 changes: 16 additions & 15 deletions merkle/hstar2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ package merkle

import (
"bytes"
"crypto/sha256"
"fmt"
"math/big"
"testing"
Expand All @@ -25,6 +24,8 @@ import (
"github.com/google/trillian/testonly"
)

const treeID = int64(0)

// This root was calculated with the C++/Python sparse Merkle tree code in the
// github.com/google/certificate-transparency repo.
// TODO(alcutter): replace with hash-dependent computation. How is this computed?
Expand All @@ -34,21 +35,21 @@ var sparseEmptyRootHashB64 = testonly.MustDecodeBase64("xmifEIEqCYCXbZUz2Dh1KCFm
// passing into a the HStar2 sparse Merkle tree implementation.
// The map keys will be SHA256 hashed before being added to the returned
// structs.
func createHStar2Leaves(hasher MapHasher, values map[string]string) []HStar2LeafHash {
func createHStar2Leaves(treeID int64, hasher MapHasher, values map[string]string) []HStar2LeafHash {
r := []HStar2LeafHash{}
for k := range values {
khash := sha256.Sum256([]byte(k))
vhash := hasher.HashLeaf([]byte(values[k]))
khash := testonly.HashKey(k)
vhash := hasher.HashLeaf(treeID, khash, hasher.BitLen(), []byte(values[k]))
r = append(r, HStar2LeafHash{
Index: new(big.Int).SetBytes(khash[:]),
Index: new(big.Int).SetBytes(khash),
LeafHash: vhash[:],
})
}
return r
}

func TestHStar2EmptyRootKAT(t *testing.T) {
s := NewHStar2(maphasher.Default)
s := NewHStar2(treeID, maphasher.Default)
root, err := s.HStar2Root(s.hasher.Size()*8, []HStar2LeafHash{})
if err != nil {
t.Fatalf("Failed to calculate root: %v", err)
Expand All @@ -71,12 +72,12 @@ var simpleTestVector = []struct {
}

func TestHStar2SimpleDataSetKAT(t *testing.T) {
s := NewHStar2(maphasher.Default)
s := NewHStar2(treeID, maphasher.Default)

m := make(map[string]string)
for i, x := range simpleTestVector {
m[x.k] = x.v
values := createHStar2Leaves(maphasher.Default, m)
values := createHStar2Leaves(treeID, maphasher.Default, m)
root, err := s.HStar2Root(s.hasher.Size()*8, values)
if err != nil {
t.Errorf("Failed to calculate root at iteration %d: %v", i, err)
Expand All @@ -96,10 +97,10 @@ func TestHStar2GetSet(t *testing.T) {
cache := make(map[string][]byte)

for i, x := range simpleTestVector {
s := NewHStar2(maphasher.Default)
s := NewHStar2(treeID, maphasher.Default)
m := make(map[string]string)
m[x.k] = x.v
values := createHStar2Leaves(maphasher.Default, m)
values := createHStar2Leaves(treeID, maphasher.Default, m)
// ensure we're going incrementally, one leaf at a time.
if len(values) != 1 {
t.Fatalf("Should only have 1 leaf per run, got %d", len(values))
Expand All @@ -125,7 +126,7 @@ func TestHStar2GetSet(t *testing.T) {
// Checks that we calculate the same empty root hash as a 256-level tree has
// when calculating top subtrees using an appropriate offset.
func TestHStar2OffsetEmptyRootKAT(t *testing.T) {
s := NewHStar2(maphasher.Default)
s := NewHStar2(treeID, maphasher.Default)

for size := 1; size < 255; size++ {
root, err := s.HStar2Nodes(size, s.hasher.Size()*8-size, []HStar2LeafHash{},
Expand All @@ -145,7 +146,7 @@ func TestHStar2OffsetEmptyRootKAT(t *testing.T) {
// 256-prefixSize, and can be passed in as leaves to top-subtree calculation.
func rootsForTrimmedKeys(t *testing.T, prefixSize int, lh []HStar2LeafHash) []HStar2LeafHash {
var ret []HStar2LeafHash
s := NewHStar2(maphasher.Default)
s := NewHStar2(treeID, maphasher.Default)
for i := range lh {
prefix := new(big.Int).Rsh(lh[i].Index, uint(s.hasher.Size()*8-prefixSize))
b := lh[i].Index.Bytes()
Expand All @@ -167,7 +168,7 @@ func rootsForTrimmedKeys(t *testing.T, prefixSize int, lh []HStar2LeafHash) []HS
// (single top subtree of size n, and multipl bottom subtrees of size 256-n)
// still arrives at the same Known Answers for root hash.
func TestHStar2OffsetRootKAT(t *testing.T) {
s := NewHStar2(maphasher.Default)
s := NewHStar2(treeID, maphasher.Default)

m := make(map[string]string)

Expand All @@ -177,7 +178,7 @@ func TestHStar2OffsetRootKAT(t *testing.T) {
// requirement.
for size := 24; size < 256; size += 8 {
m[x.k] = x.v
intermediates := rootsForTrimmedKeys(t, size, createHStar2Leaves(maphasher.Default, m))
intermediates := rootsForTrimmedKeys(t, size, createHStar2Leaves(treeID, maphasher.Default, m))

root, err := s.HStar2Nodes(size, s.hasher.Size()*8-size, intermediates,
func(int, *big.Int) ([]byte, error) { return nil, nil },
Expand All @@ -194,7 +195,7 @@ func TestHStar2OffsetRootKAT(t *testing.T) {
}

func TestHStar2NegativeTreeLevelOffset(t *testing.T) {
s := NewHStar2(maphasher.Default)
s := NewHStar2(treeID, maphasher.Default)

_, err := s.HStar2Nodes(32, -1, []HStar2LeafHash{},
func(int, *big.Int) ([]byte, error) { return nil, nil },
Expand Down
14 changes: 8 additions & 6 deletions merkle/map_verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ import (
// append-only logs, but adds support for nil/"default" proof nodes.
//
// Returns nil on a successful verification, and an error otherwise.
func VerifyMapInclusionProof(index, leafHash, expectedRoot []byte, proof [][]byte, h MapHasher) error {
hBits := h.Size() * 8
func VerifyMapInclusionProof(treeID int64, index, leafHash, expectedRoot []byte, proof [][]byte, h MapHasher) error {
hBits := h.BitLen()

if got, want := len(proof), hBits; got != want {
return fmt.Errorf("invalid proof length %d, want %d", got, want)
Expand All @@ -40,6 +40,11 @@ func VerifyMapInclusionProof(index, leafHash, expectedRoot []byte, proof [][]byt
if got, want := len(leafHash)*8, hBits; got != want {
return fmt.Errorf("invalid leafHash length %d, want %d", got, want)
}
for i, element := range proof {
if got, wanta, wantb := len(element), 0, h.Size(); got != wanta && got != wantb {
return fmt.Errorf("invalid proof: len(proof[%v]) %d, want %d or %d", i, got, wanta, wantb)
}
}

// TODO(al): Remove this dep on storage, since clients will want to use this code.
nID := storage.NewNodeIDFromHash(index)
Expand All @@ -51,10 +56,7 @@ func VerifyMapInclusionProof(index, leafHash, expectedRoot []byte, proof [][]byt
proofIsRightHandElement := nID.Bit(bit) == 0
pElement := proof[bit]
if len(pElement) == 0 {
pElement = h.HashEmpty(bit)
}
if got, want := len(pElement)*8, hBits; got != want {
return fmt.Errorf("invalid proof: element has length %d, want %d", got, want)
pElement = h.HashEmpty(treeID, index, bit)
}
if proofIsRightHandElement {
runningHash = h.HashChildren(runningHash, pElement)
Expand Down
4 changes: 2 additions & 2 deletions merkle/map_verifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ func TestVerifyMap(t *testing.T) {
{"excess proof", tv.Key, tv.Value, tv.ExpectedRoot, make([][]byte, h.Size()*8+1), false},
} {
index := testonly.HashKey(test.key)
leafHash := h.HashLeaf(test.leaf)
err := VerifyMapInclusionProof(index, leafHash, test.root, test.proof, h)
leafHash := h.HashLeaf(treeID, index, h.BitLen(), test.leaf)
err := VerifyMapInclusionProof(treeID, index, leafHash, test.root, test.proof, h)
if got := err == nil; got != test.want {
t.Errorf("%v: VerifyMapInclusionProof(): %v, want %v", test.desc, err, test.want)
}
Expand Down
Loading

0 comments on commit 448544a

Please sign in to comment.