From 29aa6a2b46cc706d0741900b53d39ee38e1f33ac Mon Sep 17 00:00:00 2001 From: Vu Quoc Huy Date: Thu, 22 Jun 2017 14:51:40 +0700 Subject: [PATCH] Create Hasher registry --- crypto/hasher/coniks/coniks.go | 75 ++++++++++++++++++++++ crypto/hasher/coniks/coniks_test.go | 57 +++++++++++++++++ crypto/hasher/hasher.go | 97 +++++++++-------------------- crypto/hasher/hasher_test.go | 59 ++++++------------ merkletree/node.go | 8 +-- merkletree/pad.go | 4 +- merkletree/proof.go | 9 +-- merkletree/str.go | 4 +- protocol/policy.go | 4 +- 9 files changed, 193 insertions(+), 124 deletions(-) create mode 100644 crypto/hasher/coniks/coniks.go create mode 100644 crypto/hasher/coniks/coniks_test.go diff --git a/crypto/hasher/coniks/coniks.go b/crypto/hasher/coniks/coniks.go new file mode 100644 index 0000000..dab49ea --- /dev/null +++ b/crypto/hasher/coniks/coniks.go @@ -0,0 +1,75 @@ +// TODO(huyvq): Remove package import. +// Other package shouldn't need to import this package, +// instead they should insert `hasher` 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/hasher" + "github.com/coniks-sys/coniks-go/utils" +) + +func init() { + hasher.RegisterHasher(CONIKSHasher, New) +} + +const ( + // CONIKSHasher is the identity of the hashing algorithm + // specified in the CONIKSHasher paper. + CONIKSHasher = "CONIKS Hasher" + + emptyIdentifier = 'E' + leafIdentifier = 'L' +) + +type coniksHasher struct { + crypto.Hash +} + +// New returns an instance of CONIKS hasher. +func New() hasher.PADHasher { + return &coniksHasher{Hash: crypto.SHA512_256} +} + +func (ch *coniksHasher) Digest(ms ...[]byte) []byte { + h := ch.New() + for _, m := range ms { + h.Write(m) + } + return h.Sum(nil) +} + +func (coniksHasher) ID() string { + return CONIKSHasher +} + +func (ch *coniksHasher) Size() int { + return ch.Size() +} + +func (ch *coniksHasher) HashInterior(left, right []byte) []byte { + return ch.Digest(left, right) +} + +func (ch *coniksHasher) HashLeaf(nonce []byte, index []byte, level uint32, commit []byte) []byte { + return ch.Digest( + []byte{leafIdentifier}, + nonce, + index, + utils.UInt32ToBytes(level), + commit, + ) +} + +func (ch *coniksHasher) HashEmpty(nonce []byte, index []byte, level uint32) []byte { + return ch.Digest( + []byte{emptyIdentifier}, + nonce, + index, + utils.UInt32ToBytes(level), + ) +} diff --git a/crypto/hasher/coniks/coniks_test.go b/crypto/hasher/coniks/coniks_test.go new file mode 100644 index 0000000..289374d --- /dev/null +++ b/crypto/hasher/coniks/coniks_test.go @@ -0,0 +1,57 @@ +package coniks + +import ( + "encoding/hex" + "testing" + + "github.com/coniks-sys/coniks-go/crypto/hasher" +) + +// h2h converts a hex string into its Hash object. +func h2h(h string) hasher.Hash { + b, err := hex.DecodeString(h) + if err != nil { + panic("invalid hex string") + } + var ret hasher.Hash + copy(ret[:], b) + return ret +} + +// s2h converts a byte slice into its Hash object. +func s2h(s []byte) hasher.Hash { + var ret hasher.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 hasher.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 hasher.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) + } + } +} diff --git a/crypto/hasher/hasher.go b/crypto/hasher/hasher.go index 902fd0a..57baf5e 100644 --- a/crypto/hasher/hasher.go +++ b/crypto/hasher/hasher.go @@ -1,94 +1,53 @@ package hasher import ( - "crypto" + "fmt" - ccrypto "github.com/coniks-sys/coniks-go/crypto" - "github.com/coniks-sys/coniks-go/utils" -) - -const ( - emptyIdentifier = 'E' - leafIdentifier = 'L' + "github.com/coniks-sys/coniks-go/crypto" ) // Hash represents the output of the used hash function. -type Hash [ccrypto.DefaultHashSizeByte]byte +type Hash [crypto.DefaultHashSizeByte]byte // PADHasher provides hash functions for the PAD implementations. 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 hashes all passed byte slices. The passed slices won't be mutated. Digest(ms ...[]byte) []byte - TreeHasher + treeHasher } -// TreeHasher provides hash functions for tree implementations. -type TreeHasher interface { +// treeHasher provides hash functions for tree implementations. +type treeHasher interface { + // HashInterior computes the hash of an interior node as: H(left || right) HashInterior(left, right []byte) []byte - HashLeaf(nonce []byte, index []byte, level uint32, data []byte) []byte - HashEmpty(nonce []byte, index []byte, level uint32) []byte -} -type coniksHasher struct { - crypto.Hash -} + // HashLeaf computes the hash of a user leaf node as: + // H(Identifier || nonce || index || level || commit) + HashLeaf(nonce []byte, index []byte, level uint32, data []byte) []byte -// New creates a new PADHasher using the passed in hash function. -func New(h crypto.Hash) PADHasher { - return &coniksHasher{Hash: h} + // HashEmpty computes the hash of an empty leaf node as: + // H(Identifier || nonce || index || level) + HashEmpty(nonce []byte, index []byte, level uint32) []byte } -// Default is the standard CONIKS hasher. -func Default() PADHasher { - return New(crypto.SHA512_256) -} +var hashers = make(map[string]PADHasher) -// Digest hashes all passed byte slices. -// The passed slices won't be mutated. -func (ch *coniksHasher) Digest(ms ...[]byte) []byte { - h := ch.New() - for _, m := range ms { - h.Write(m) +// RegisterHasher registers a hasher for use. +func RegisterHasher(h string, f func() PADHasher) { + if _, ok := hashers[h]; ok { + panic(fmt.Sprintf("RegisterHasher(%v) is already registered", h)) } - return h.Sum(nil) + hashers[h] = f() } -// ID returns the name of the cryptographic hash function in string. -func (coniksHasher) ID() string { - return "SHA-512/256" -} - -// Size returns the size of the hash output in bytes. -func (ch *coniksHasher) Size() int { - return ch.Size() -} - -// HashInterior computes the hash of an interior node: -// H(left || right) -func (ch *coniksHasher) HashInterior(left, right []byte) []byte { - return ch.Digest(left, right) -} - -// HashLeaf computes the hash of a user leaf node: -// H(Identifier || nonce || index || level || commit) -func (ch *coniksHasher) 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: -// H(Identifier || nonce || index || level) -func (ch *coniksHasher) HashEmpty(nonce []byte, index []byte, level uint32) []byte { - return ch.Digest( - []byte{emptyIdentifier}, - nonce, - index, - utils.UInt32ToBytes(level), - ) +// Hasher returns a PADHasher. +func Hasher(h string) (PADHasher, error) { + if f, ok := hashers[h]; ok { + return f, nil + } + return nil, fmt.Errorf("Hasher(%v) is unknown hasher", h) } diff --git a/crypto/hasher/hasher_test.go b/crypto/hasher/hasher_test.go index 94bdde3..0bd9d1c 100644 --- a/crypto/hasher/hasher_test.go +++ b/crypto/hasher/hasher_test.go @@ -1,55 +1,32 @@ package hasher import ( - "encoding/hex" "testing" ) -// h2h converts a hex string into its Hash object. -func h2h(h string) Hash { - b, err := hex.DecodeString(h) - if err != nil { - panic("invalid hex string") - } - var ret Hash - copy(ret[:], b) - return ret -} +var fakeHasherID = "fakeHasher" -// s2h converts a byte slice into its Hash object. -func s2h(s []byte) Hash { - var ret Hash - copy(ret[:], s) - return ret +func fakeHasher() PADHasher { + return nil } -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 Hash - }{ - {treeNonce: [32]byte{0}, index: []byte("foo"), depth: 128, leaf: []byte("leaf"), want: h2h("65e7f29787a6168affd016656bb1f4f03af91cf7416270f5015005f8594d3eb6")}, - } { - if got, want := s2h(Default().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 TestHasherIsRegistered(t *testing.T) { + RegisterHasher(fakeHasherID, fakeHasher) + defer func() { + if r := recover(); r == nil { + t.Fatal("Expected RegisterHasher to panic.") } - } + }() + RegisterHasher(fakeHasherID, fakeHasher) } -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 Hash - }{ - {treeNonce: [32]byte{0}, index: []byte("foo"), depth: 128, want: h2h("1a6b0eb739b32a46e7d679a9be03f522e907f53423aacb82e550bf657d1afb10")}, - } { - if got, want := s2h(Default().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) - } +func TestGetHasher(t *testing.T) { + if _, ok := hashers[fakeHasherID]; !ok { + RegisterHasher(fakeHasherID, fakeHasher) + } + + _, err := Hasher(fakeHasherID) + if err != nil { + t.Error("Expect a hasher.") } } diff --git a/merkletree/node.go b/merkletree/node.go index f010008..9893882 100644 --- a/merkletree/node.go +++ b/merkletree/node.go @@ -2,7 +2,7 @@ package merkletree import ( "github.com/coniks-sys/coniks-go/crypto" - "github.com/coniks-sys/coniks-go/crypto/hasher" + conikshasher "github.com/coniks-sys/coniks-go/crypto/hasher/coniks" "github.com/coniks-sys/coniks-go/utils" ) @@ -83,11 +83,11 @@ func (n *interiorNode) hash(m *MerkleTree) []byte { if n.rightHash == nil { n.rightHash = n.rightChild.hash(m) } - return hasher.Default().HashInterior(n.leftHash, n.rightHash) + return conikshasher.New().HashInterior(n.leftHash, n.rightHash) } func (n *userLeafNode) hash(m *MerkleTree) []byte { - return hasher.Default().HashLeaf( + return conikshasher.New().HashLeaf( m.nonce, n.index, n.level, @@ -96,7 +96,7 @@ func (n *userLeafNode) hash(m *MerkleTree) []byte { } func (n *emptyNode) hash(m *MerkleTree) []byte { - return hasher.Default().HashEmpty( + return conikshasher.New().HashEmpty( m.nonce, n.index, n.level, diff --git a/merkletree/pad.go b/merkletree/pad.go index aaab6c4..18a06a7 100644 --- a/merkletree/pad.go +++ b/merkletree/pad.go @@ -5,7 +5,7 @@ import ( "errors" "github.com/coniks-sys/coniks-go/crypto" - "github.com/coniks-sys/coniks-go/crypto/hasher" + conikshasher "github.com/coniks-sys/coniks-go/crypto/hasher/coniks" "github.com/coniks-sys/coniks-go/crypto/sign" "github.com/coniks-sys/coniks-go/crypto/vrf" ) @@ -64,7 +64,7 @@ func (pad *PAD) signTreeRoot(epoch uint64) { panic(err) } } else { - prevHash = hasher.Default().Digest(pad.latestSTR.Signature) + prevHash = conikshasher.New().Digest(pad.latestSTR.Signature) } pad.tree.recomputeHash() m := pad.tree.Clone() diff --git a/merkletree/proof.go b/merkletree/proof.go index e394fd4..ba137c5 100644 --- a/merkletree/proof.go +++ b/merkletree/proof.go @@ -6,6 +6,7 @@ import ( "github.com/coniks-sys/coniks-go/crypto" "github.com/coniks-sys/coniks-go/crypto/hasher" + conikshasher "github.com/coniks-sys/coniks-go/crypto/hasher/coniks" "github.com/coniks-sys/coniks-go/utils" ) @@ -40,13 +41,13 @@ type ProofNode struct { func (n *ProofNode) hash(treeNonce []byte) []byte { if n.IsEmpty { // empty leaf node - return hasher.Default().HashEmpty( + return conikshasher.New().HashEmpty( treeNonce, n.Index, n.Level, ) } else { - return hasher.Default().HashLeaf( + return conikshasher.New().HashLeaf( treeNonce, n.Index, n.Level, @@ -87,9 +88,9 @@ func (ap *AuthenticationPath) authPathHash() []byte { for depth > 0 { depth -= 1 if indexBits[depth] { // right child - hash = hasher.Default().Digest(ap.PrunedTree[depth][:], hash) + hash = conikshasher.New().Digest(ap.PrunedTree[depth][:], hash) } else { - hash = hasher.Default().Digest(hash, ap.PrunedTree[depth][:]) + hash = conikshasher.New().Digest(hash, ap.PrunedTree[depth][:]) } } return hash diff --git a/merkletree/str.go b/merkletree/str.go index b6554bd..2ad3466 100644 --- a/merkletree/str.go +++ b/merkletree/str.go @@ -3,7 +3,7 @@ package merkletree import ( "bytes" - "github.com/coniks-sys/coniks-go/crypto/hasher" + conikshasher "github.com/coniks-sys/coniks-go/crypto/hasher/coniks" "github.com/coniks-sys/coniks-go/crypto/sign" "github.com/coniks-sys/coniks-go/utils" ) @@ -79,7 +79,7 @@ func (str *SignedTreeRoot) SerializeInternal() []byte { // in the issued STR. The hash chain is valid if // these two hash values are equal and consecutive. func (str *SignedTreeRoot) VerifyHashChain(savedSTR *SignedTreeRoot) bool { - hash := hasher.Default().Digest(savedSTR.Signature) + hash := conikshasher.New().Digest(savedSTR.Signature) return str.PreviousEpoch == savedSTR.Epoch && str.Epoch == savedSTR.Epoch+1 && bytes.Equal(hash, str.PreviousSTRHash) diff --git a/protocol/policy.go b/protocol/policy.go index 5d5ae65..409cae1 100644 --- a/protocol/policy.go +++ b/protocol/policy.go @@ -1,7 +1,7 @@ package protocol import ( - "github.com/coniks-sys/coniks-go/crypto/hasher" + conikshasher "github.com/coniks-sys/coniks-go/crypto/hasher/coniks" "github.com/coniks-sys/coniks-go/crypto/vrf" "github.com/coniks-sys/coniks-go/merkletree" "github.com/coniks-sys/coniks-go/utils" @@ -29,7 +29,7 @@ var _ merkletree.AssocData = (*Policies)(nil) func NewPolicies(epDeadline Timestamp, vrfPublicKey vrf.PublicKey) *Policies { return &Policies{ Version: Version, - HashID: hasher.Default().ID(), + HashID: conikshasher.New().ID(), VrfPublicKey: vrfPublicKey, EpochDeadline: epDeadline, }