Skip to content
Merged
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
10 changes: 6 additions & 4 deletions integration/map.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,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, pubKey crypto.PublicKey, client trillian.TrillianMapClient) error {
{
Expand Down Expand Up @@ -127,8 +129,8 @@ func RunMapIntegration(ctx context.Context, mapID int64, pubKey crypto.PublicKey
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 @@ -157,8 +159,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
123 changes: 123 additions & 0 deletions merkle/coniks/coniks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2017 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"
"fmt"

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

func init() {
hashers.RegisterMapHasher(trillian.HashStrategy_CONIKS_SHA512_256, Default)
}

// 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 merkle tree hashing algorithm specified in the CONIKS paper.
type hasher struct {
crypto.Hash
}

// New creates a new hashers.TreeHasher using the passed in hash function.
func New(h crypto.Hash) hashers.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 height.
// A height 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(m.maskIndex(index, depth))
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(m.maskIndex(index, depth))
binary.Write(h, binary.BigEndian, uint32(depth))
h.Write(leaf)
return h.Sum(nil)
}

// HashChildren returns the internal 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
}

// leftmask contains bitmasks indexed such that the left x bits are set. It is
// indexed by byte position from 0-7 0 is special cased to 0xFF since 8 mod 8
// is 0. leftmask is only used to mask the last byte.
var leftmask = [8]byte{0xFF, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE}

// maskIndex returns index with only the left depth bits set.
// index must be of size m.Size() and 0 <= depth <= m.BitLen().
// e.g.
func (m *hasher) maskIndex(index []byte, depth int) []byte {
if got, want := len(index), m.Size(); got != want {
panic(fmt.Sprintf("index len: %d, want %d", got, want))
}
if got, want := depth, m.BitLen(); got < 0 || got > want {
panic(fmt.Sprintf("depth: %d, want <= %d && > 0", got, want))
}

// Create an empty index Size() bytes long.
ret := make([]byte, m.Size())
if depth > 0 {
// Copy the first depthBytes.
depthBytes := (depth + 7) >> 3
copy(ret, index[:depthBytes])
// Mask off unwanted bits in the last byte.
ret[depthBytes-1] = ret[depthBytes-1] & leftmask[depth%8]
}
return ret
}
85 changes: 85 additions & 0 deletions merkle/coniks/coniks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2017 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"
"crypto"
"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
}{
{0, h2b("0000000000000000000000000000000000000000000000000000000000000000"), 128, []byte(""), h2b("5f4bf72f8175e8db7b58c96354d870b60fb98ce7e4fdde7d601a4d3e5b5d1f20")},
{1, h2b("0000000000000000000000000000000000000000000000000000000000000000"), 128, []byte(""), h2b("a5f5d0c1e86a15c1ab9c8b88f7e8b7ef17b246350c141c6f21ab81e51d5a6ef2")},
{0, h2b("1111111111111111111111111111111111111111111111111111111111111111"), 128, []byte(""), h2b("f7ab5ae11bdea50c293a59c0399f5704fd3401ab4144b3ce6230a6866efe2304")},
{0, h2b("0000000000000000000000000000000000000000000000000000000000000000"), 127, []byte(""), h2b("8a8170ff167d7dcdf1b580c89b2f6a6cc3a085c957d1d637d6314e38b83732a0")},
{0, h2b("0000000000000000000000000000000000000000000000000000000000000000"), 128, []byte("foo"), h2b("0d394ddaca7acbf2ad6f9bede5f652be966e3c9e94eaccc472c9b2ca139d06ec")},
// Test vector from Key Transparency
{0, h2b("1111111111111111111111111111111111111111111111111111111111111111"), 128, []byte("leaf"), h2b("d77b4bb8e8fdd941976d285a8a0cd8db27b6f7e889e51134e1428224306b6f52")},
} {
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)
}
}
}

func TestMaskIndex(t *testing.T) {
h := &hasher{crypto.SHA1} // Use a shorter hash for shorter test vectors.
for _, tc := range []struct {
index []byte
depth int
want []byte
}{
{index: h2b("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), depth: 0, want: h2b("0000000000000000000000000000000000000000")},
{index: h2b("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), depth: 1, want: h2b("8000000000000000000000000000000000000000")},
{index: h2b("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), depth: 2, want: h2b("C000000000000000000000000000000000000000")},
{index: h2b("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), depth: 3, want: h2b("E000000000000000000000000000000000000000")},
{index: h2b("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), depth: 4, want: h2b("F000000000000000000000000000000000000000")},
{index: h2b("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), depth: 5, want: h2b("F800000000000000000000000000000000000000")},
{index: h2b("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), depth: 6, want: h2b("FC00000000000000000000000000000000000000")},
{index: h2b("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), depth: 7, want: h2b("FE00000000000000000000000000000000000000")},
{index: h2b("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), depth: 8, want: h2b("FF00000000000000000000000000000000000000")},
{index: h2b("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), depth: 9, want: h2b("FF80000000000000000000000000000000000000")},
{index: h2b("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), depth: 10, want: h2b("FFC0000000000000000000000000000000000000")},
{index: h2b("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), depth: 159, want: h2b("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE")},
{index: h2b("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"), depth: 160, want: h2b("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF")},
{index: h2b("000102030405060708090A0B0C0D0E0F10111213"), depth: 1, want: h2b("0000000000000000000000000000000000000000")},
{index: h2b("000102030405060708090A0B0C0D0E0F10111213"), depth: 17, want: h2b("0001000000000000000000000000000000000000")},
{index: h2b("000102030405060708090A0B0C0D0E0F10111213"), depth: 159, want: h2b("000102030405060708090A0B0C0D0E0F10111212")},
{index: h2b("000102030405060708090A0B0C0D0E0F10111213"), depth: 160, want: h2b("000102030405060708090A0B0C0D0E0F10111213")},
} {
if got, want := h.maskIndex(tc.index, tc.depth), tc.want; !bytes.Equal(got, want) {
t.Errorf("maskIndex(%x, %v): %x, want %x", tc.index, tc.depth, got, want)
}
}
}
7 changes: 5 additions & 2 deletions merkle/hashers/tree_hasher.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,18 @@ type LogHasher interface {
type MapHasher interface {
// HashEmpty returns the hash of an empty branch at a given depth.
// A height of 0 indicates an empty leaf. The maximum height is Size*8.
HashEmpty(height int) []byte
// TODO(gbelvin) fully define index.
HashEmpty(treeID int64, index []byte, height int) []byte
// HashLeaf computes the hash of a leaf that exists.
HashLeaf(leaf []byte) []byte
HashLeaf(treeID int64, index []byte, height int, leaf []byte) []byte
// HashChildren computes interior nodes.
HashChildren(l, r []byte) []byte
// Size is the number of bits in the underlying hash function.
// It is also the height of the merkle tree.
// TODO(gbelvin): Replace Size() with BitLength().
Size() int
// BitLen returns the number of bits in the underlying hash function.
BitLen() int
}

var (
Expand Down
23 changes: 17 additions & 6 deletions merkle/hstar2.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,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 hashers.MapHasher
}

// NewHStar2 creates a new HStar2 tree calculator based on the passed in MapHasher.
func NewHStar2(hasher hashers.MapHasher) HStar2 {
func NewHStar2(treeID int64, hasher hashers.MapHasher) HStar2 {
return HStar2{
treeID: treeID,
hasher: hasher,
}
}
Expand All @@ -56,7 +58,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, PaddedBytes(index, s.hasher.Size()), depth), nil
},
func(int, *big.Int, []byte) error { return nil })
}
Expand Down Expand Up @@ -98,7 +100,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, PaddedBytes(index, s.hasher.Size()), depth+treeLevelOffset), nil
},
func(depth int, index *big.Int, hash []byte) error {
return set(treeDepth-depth, index, hash)
Expand All @@ -121,13 +123,12 @@ func (s *HStar2) hStar2b(n int, values []HStar2LeafHash, offset *big.Int, get Sp
}
return values[0].LeafHash, nil
}

split := new(big.Int).Lsh(smtOne, uint(n-1))
split.Add(split, offset)
if len(values) == 0 {
return get(n, offset)
}

split := new(big.Int).Lsh(smtOne, uint(n-1))
split.Add(split, offset)
i := sort.Search(len(values), func(i int) bool { return values[i].Index.Cmp(split) >= 0 })
lhs, err := s.hStar2b(n-1, values[:i], offset, get, set)
if err != nil {
Expand Down Expand Up @@ -176,3 +177,13 @@ func (s *valueSorter) Less(i, j int) bool {
func indexLess(a, b *HStar2LeafHash) bool {
return a.Index.Cmp(b.Index) < 0
}

// PaddedBytes takes a big.Int and returns it's value, left padded with zeros.
// e.g. 1 -> 0000000000000000000000000000000000000001
func PaddedBytes(i *big.Int, size int) []byte {
b := i.Bytes()
ret := make([]byte, size)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Does this work if there is no padding needed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added at test for this.

padBytes := len(ret) - len(b)
copy(ret[padBytes:], b)
return ret
}
Loading