diff --git a/crypto/fnv.go b/crypto/fnv.go index ac0e16bfc..3fb99b28e 100644 --- a/crypto/fnv.go +++ b/crypto/fnv.go @@ -1,56 +1,80 @@ package crypto -// Taken and modified from https://golang.org/src/hash/fnv/fnv.go -// TODO: This implementation uses big ints and is probably horrendously slow. - // Implements FNV-1 and FNV-1a, non-cryptographic hash functions // created by Glenn Fowler, Landon Curt Noll, and Phong Vo. // See https://en.wikipedia.org/wiki/Fowler-Noll-Vo_hash_function. -import ( - "hash" - "math/big" -) +import "hash" // Hash128 is the common interface implemented by all 128-bit hash functions. type Hash128 interface { hash.Hash - Sum128() []byte + Sum128() (uint64, uint64) } type sum128a struct { - *big.Int + v0, v1, v2, v3 uint64 } var _ Hash128 = &sum128a{} -var offset128 = &big.Int{} -var prime128 = &big.Int{} - -func init() { - offset128.SetString("144066263297769815596495629667062367629", 0) - prime128.SetString("309485009821345068724781371", 0) -} - // New128a returns a new 128-bit FNV-1a hash.Hash. func New128a() Hash128 { - i := &big.Int{} - i.Set(offset128) - return &sum128a{i} + s := &sum128a{} + s.Reset() + return s } -func (s *sum128a) Reset() { s.Set(offset128) } +func (s *sum128a) Reset() { + s.v0 = 0x6295C58D + s.v1 = 0x62B82175 + s.v2 = 0x07BB0142 + s.v3 = 0x6C62272E +} -func (s *sum128a) Sum128() []byte { return s.Bytes() } +func (s *sum128a) Sum128() (uint64, uint64) { + return s.v3<<32 | s.v2, s.v1<<32 | s.v0 +} func (s *sum128a) Write(data []byte) (int, error) { - for _, c := range data { - s.Xor(s.Int, big.NewInt(int64(c))) - s.Mul(s.Int, prime128) + var t0, t1, t2, t3 uint64 + // Taken and slightly modified from github.com/romain-jacotin/quic + const fnv128PrimeLow = 0x0000013B + const fnv128PrimeShift = 24 - // Truncate the bigint to 128 bits - s.SetBytes(s.Bytes()[len(s.Bytes())-16 : len(s.Bytes())]) + for _, v := range data { + // xor the bottom with the current octet + s.v0 ^= uint64(v) + + // multiply by the 128 bit FNV magic prime mod 2^128 + // fnv_prime = 309485009821345068724781371 (decimal) + // = 0x0000000001000000000000000000013B (hexadecimal) + // = 0x00000000 0x01000000 0x00000000 0x0000013B (in 4*32 words) + // = 0x0 1<> 32) + t2 += (t1 >> 32) + t3 += (t2 >> 32) + + s.v0 = t0 & 0xffffffff + s.v1 = t1 & 0xffffffff + s.v2 = t2 & 0xffffffff + s.v3 = t3 // & 0xffffffff + // Doing a s.v3 &= 0xffffffff is not really needed since it simply + // removes multiples of 2^128. We can discard these excess bits + // outside of the loop when writing the hash in Little Endian. } + return len(data), nil } @@ -59,6 +83,5 @@ func (s *sum128a) Size() int { return 16 } func (s *sum128a) BlockSize() int { return 1 } func (s *sum128a) Sum(in []byte) []byte { - b := s.Bytes() - return append(in, b[len(b)-s.Size():]...) + panic("FNV: not supported") } diff --git a/crypto/fnv_test.go b/crypto/fnv_test.go index 40311c3b5..0f6cfa2aa 100644 --- a/crypto/fnv_test.go +++ b/crypto/fnv_test.go @@ -9,13 +9,18 @@ import ( var _ = Describe("FNV", func() { It("gives proper null hash", func() { - h := crypto.New128a() - Expect(h.Sum128()).To(Equal([]byte{0x6c, 0x62, 0x27, 0x2e, 0x07, 0xbb, 0x01, 0x42, 0x62, 0xb8, 0x21, 0x75, 0x62, 0x95, 0xc5, 0x8d})) + hash := crypto.New128a() + h, l := hash.Sum128() + Expect(l).To(Equal(uint64(0x62b821756295c58d))) + Expect(h).To(Equal(uint64(0x6c62272e07bb0142))) }) - It("gives hashes", func() { - h := crypto.New128a() - h.Write([]byte("foobar")) - Expect(h.Sum128()).To(Equal([]byte{0x34, 0x3e, 0x16, 0x62, 0x79, 0x3c, 0x64, 0xbf, 0x6f, 0xd, 0x35, 0x97, 0xba, 0x44, 0x6f, 0x18})) + It("calculates hash", func() { + hash := crypto.New128a() + _, err := hash.Write([]byte("foobar")) + Expect(err).ToNot(HaveOccurred()) + h, l := hash.Sum128() + Expect(l).To(Equal(uint64(0x6f0d3597ba446f18))) + Expect(h).To(Equal(uint64(0x343e1662793c64bf))) }) })