Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create generic interface for hash functions #178

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
80 changes: 80 additions & 0 deletions crypto/hashers/coniks/coniks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// TODO(huyvq): Remove package import.
// Other package shouldn't need to import this package,
// instead they should insert `hashers` and import this
// package using a blank import name.
// Should be adressed in #06.

package coniks

import (
"crypto"

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

func init() {
hashers.RegisterHasher(CONIKS_Hash_SHA512_256, New)
}

const (
// CONIKS_Hash_SHA512_256 is the identity of the hashing strategy
// specified in the Coniks paper with SHA512_256 as the hash algorithm.
CONIKS_Hash_SHA512_256 = "CONIKS_Hash_SHA512_256"

emptyIdentifier = 'E'
leafIdentifier = 'L'
)

type hasher struct {
crypto.Hash
}

// New returns an instance of CONIKS_Hash_SHA512_256.
func New() hashers.PADHasher {
return &hasher{Hash: crypto.SHA512_256}
}

func (ch *hasher) Digest(ms ...[]byte) []byte {
h := ch.New()
for _, m := range ms {
h.Write(m)
}
return h.Sum(nil)
}

func (hasher) ID() string {
return CONIKS_Hash_SHA512_256
}

func (ch *hasher) Size() int {
return ch.Size()
}

// HashInterior computes the hash of an interior node as: H(left || right).
func (ch *hasher) HashInterior(left, right []byte) []byte {
return ch.Digest(left, right)
}

// HashLeaf computes the hash of a user leaf node as:
// H(Identifier || nonce || index || level || commit).
func (ch *hasher) HashLeaf(nonce []byte, index []byte, level uint32, commit []byte) []byte {
return ch.Digest(
[]byte{leafIdentifier},
nonce,
index,
utils.UInt32ToBytes(level),
commit,
)
}

// HashEmpty computes the hash of an empty leaf node as:
// H(Identifier || nonce || index || level).
func (ch *hasher) HashEmpty(nonce []byte, index []byte, level uint32) []byte {
return ch.Digest(
[]byte{emptyIdentifier},
nonce,
index,
utils.UInt32ToBytes(level),
)
}
57 changes: 57 additions & 0 deletions crypto/hashers/coniks/coniks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package coniks

import (
"encoding/hex"
"testing"

"github.com/coniks-sys/coniks-go/crypto/hashers"
)

// h2h converts a hex string into its Hash object.
func h2h(h string) hashers.Hash {
b, err := hex.DecodeString(h)
if err != nil {
panic("invalid hex string")
}
var ret hashers.Hash
copy(ret[:], b)
return ret
}

// s2h converts a byte slice into its Hash object.
func s2h(s []byte) hashers.Hash {
var ret hashers.Hash
copy(ret[:], s)
return ret
}

func TestHashLeafVectors(t *testing.T) {
for _, tc := range []struct {
treeNonce [32]byte // treeNonce is treeID in KT, it should be a 32-byte array for cross-project compatibility
index []byte
depth uint32
leaf []byte
want hashers.Hash
}{
{treeNonce: [32]byte{0}, index: []byte("foo"), depth: 128, leaf: []byte("leaf"), want: h2h("65e7f29787a6168affd016656bb1f4f03af91cf7416270f5015005f8594d3eb6")},
} {
if got, want := s2h(New().HashLeaf(tc.treeNonce[:], tc.index, tc.depth, tc.leaf)), tc.want; got != want {
t.Errorf("HashLeaf(%v, %s, %v, %s): %x, want %x", tc.treeNonce, tc.index, tc.depth, tc.leaf, got, want)
}
}
}

func TestHashEmptyVectors(t *testing.T) {
for _, tc := range []struct {
treeNonce [32]byte // treeNonce is treeID in KT, it should be a 32-byte array for cross-project compatibility
index []byte
depth uint32
want hashers.Hash
}{
{treeNonce: [32]byte{0}, index: []byte("foo"), depth: 128, want: h2h("1a6b0eb739b32a46e7d679a9be03f522e907f53423aacb82e550bf657d1afb10")},
} {
if got, want := s2h(New().HashEmpty(tc.treeNonce[:], tc.index, tc.depth)), tc.want; got != want {
t.Errorf("HashLeaf(%v, %s, %v): %x, want %x", tc.treeNonce, tc.index, tc.depth, got, want)
}
}
}
51 changes: 51 additions & 0 deletions crypto/hashers/hasher.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package hashers

import (
"fmt"

"github.com/coniks-sys/coniks-go/crypto"
)

// Hash represents the output of the used hash function.
type Hash [crypto.DefaultHashSizeByte]byte

// PADHasher provides hash functions for the PAD implementations,
// and defines the way empty / node / leaf hashes of the underlying tree
// are constructed.
type PADHasher interface {
// ID returns the name of the cryptographic hash function.
ID() string
// Size returns the size of the hash output in bytes.
Size() int
// Digest provides a universal hash function which
// hashes all passed byte slices. The passed slices won't be mutated.
Digest(ms ...[]byte) []byte

// HashInterior computes the hash of an interior node.
HashInterior(left, right []byte) []byte

// HashLeaf computes the hash of a user leaf node.
HashLeaf(nonce []byte, index []byte, level uint32, data []byte) []byte

// HashEmpty computes the hash of an empty leaf node.
HashEmpty(nonce []byte, index []byte, level uint32) []byte
}

var hashers = make(map[string]PADHasher)

// RegisterHasher registers a hasher for use.
func RegisterHasher(h string, f func() PADHasher) {
if _, ok := hashers[h]; ok {
panic(fmt.Sprintf("%s is already registered", h))
}
hashers[h] = f()
}

// NewPADHasher returns a registered PADHasher identified by the given string.
// If no such PADHasher exists, it returns an error.
func NewPADHasher(h string) (PADHasher, error) {
if f, ok := hashers[h]; ok {
return f, nil
}
return nil, fmt.Errorf("%s is an unknown hasher", h)
}
32 changes: 32 additions & 0 deletions crypto/hashers/hasher_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package hashers

import (
"testing"
)

var fakeHasherID = "fakeHasher"

func fakeHasher() PADHasher {
return nil
}

func TestHasherIsRegistered(t *testing.T) {
RegisterHasher(fakeHasherID, fakeHasher)
defer func() {
if r := recover(); r == nil {
t.Fatal("Expected RegisterHasher to panic.")
}
}()
RegisterHasher(fakeHasherID, fakeHasher)
}

func TestGetHasher(t *testing.T) {
if _, ok := hashers[fakeHasherID]; !ok {
RegisterHasher(fakeHasherID, fakeHasher)
}

_, err := NewPADHasher(fakeHasherID)
if err != nil {
t.Error("Expect a hasher.")
}
}
28 changes: 11 additions & 17 deletions crypto/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,20 @@ import (
"bytes"
"crypto/rand"

"golang.org/x/crypto/sha3"
"crypto/sha512"
)

const (
// HashSizeByte is the size of the hash output in bytes.
HashSizeByte = 32
// HashID identifies the used hash as a string.
HashID = "SHAKE128"
)
// DefaultHashSizeByte is the size of the hash function in bytes.
const DefaultHashSizeByte = sha512.Size256

// Digest hashes all passed byte slices.
// digest hashes all passed byte slices.
// The passed slices won't be mutated.
func Digest(ms ...[]byte) []byte {
h := sha3.NewShake128()
func digest(ms ...[]byte) []byte {
h := sha512.New512_256()
for _, m := range ms {
h.Write(m)
}
ret := make([]byte, HashSizeByte)
h.Read(ret)
return ret
return h.Sum(nil)
}

// MakeRand returns a random slice of bytes.
Expand All @@ -36,12 +30,12 @@ func Digest(ms ...[]byte) []byte {
// as unpredictable as desired).
// See https://trac.torproject.org/projects/tor/ticket/17694
func MakeRand() ([]byte, error) {
r := make([]byte, HashSizeByte)
r := make([]byte, DefaultHashSizeByte)
if _, err := rand.Read(r); err != nil {
return nil, err
}
// Do not directly reveal bytes from rand.Read on the wire
return Digest(r), nil
return digest(r), nil
}

// Commit can be used to create a cryptographic commit to some value (use
Expand All @@ -64,12 +58,12 @@ func NewCommit(stuff ...[]byte) (*Commit, error) {
}
return &Commit{
Salt: salt,
Value: Digest(append([][]byte{salt}, stuff...)...),
Value: digest(append([][]byte{salt}, stuff...)...),
}, nil
}

// Verify verifies that the underlying commit c was a commit to the passed
// byte slices stuff (which won't be mutated).
func (c *Commit) Verify(stuff ...[]byte) bool {
return bytes.Equal(c.Value, Digest(append([][]byte{c.Salt}, stuff...)...))
return bytes.Equal(c.Value, digest(append([][]byte{c.Salt}, stuff...)...))
}
16 changes: 2 additions & 14 deletions crypto/util_test.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,15 @@
package crypto

import (
"bytes"
"crypto/rand"
"errors"
"testing"
)

func TestDigest(t *testing.T) {
msg := []byte("test message")
d := Digest(msg)
if len(d) != HashSizeByte {
t.Fatal("Computation of Hash failed.")
}
if bytes.Equal(d, make([]byte, HashSizeByte)) {
t.Fatal("Hash is all zeros.")
}
}

type testErrorRandReader struct{}

func (er testErrorRandReader) Read([]byte) (int, error) {
return 0, errors.New("Not enough entropy!")
return 0, errors.New("not enough entropy")
}

func TestMakeRand(t *testing.T) {
Expand All @@ -30,7 +18,7 @@ func TestMakeRand(t *testing.T) {
t.Fatal(err)
}
// check if hashed the random output:
if len(r) != HashSizeByte {
if len(r) != DefaultHashSizeByte {
t.Fatal("Looks like Digest wasn't called correctly.")
}
orig := rand.Reader
Expand Down
13 changes: 2 additions & 11 deletions merkletree/merkletree.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"

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

Expand All @@ -14,16 +15,6 @@ var (
ErrInvalidTree = errors.New("[merkletree] Invalid tree")
)

const (
// EmptyBranchIdentifier is the domain separation prefix for
// empty node hashes.
EmptyBranchIdentifier = 'E'

// LeafIdentifier is the domain separation prefix for user
// leaf node hashes.
LeafIdentifier = 'L'
)

// MerkleTree represents the Merkle prefix tree data structure,
// which includes the root node, its hash, and a random tree-specific
// nonce.
Expand Down Expand Up @@ -72,7 +63,7 @@ func (m *MerkleTree) Get(lookupIndex []byte) *AuthenticationPath {
break
}
direction := lookupIndexBits[depth]
var hashArr [crypto.HashSizeByte]byte
var hashArr hashers.Hash
if direction {
copy(hashArr[:], nodePointer.(*interiorNode).leftHash)
nodePointer = nodePointer.(*interiorNode).rightChild
Expand Down
Loading