1package fdh
2
3import (
4	"crypto"
5	"hash"
6	"math"
7	"strconv"
8	"sync"
9)
10
11// digest represents the partial evaluation of a Full Domain Hash checksum.
12type digest struct {
13	base  crypto.Hash
14	bits  int
15	parts []hash.Hash
16	final bool // Unlike many hash functions Full Domain Hashes are "finalized" after a call to Sum() and cannot be furthur written to.
17}
18
19// New returns a hash.Hash for computing a Full Domain Hash checksum, given a base hash function and a target bit length.
20// It will panic if the bitlen is not a multiple of the hash length or if the hash library is not imported.
21func New(h crypto.Hash, bitlen int) hash.Hash {
22	if !h.Available() {
23		panic("fdh: requested hash function #" + strconv.Itoa(int(h)) + " is unavailable. Make sure your hash function is proprely imported.")
24	} else if bitlen%8 != 0 {
25		panic("fdh: hash digest size should be a multiple of 8")
26	} else if bitlen <= 0 {
27		panic("fdh: hash digest size cannot be less or equal to zero")
28	}
29
30	numparts := int(math.Ceil(float64(bitlen) / float64((h.Size() * 8))))
31
32	d := digest{
33		base:  h,
34		bits:  bitlen,
35		parts: make([]hash.Hash, numparts, numparts),
36	}
37	d.Reset()
38	return &d
39}
40
41// Reset resets the Hash to its initial state.
42func (d *digest) Reset() {
43	for i := range d.parts {
44		d.parts[i] = d.base.New()
45	}
46	d.final = false
47}
48
49// BlockSize returns the hash's underlying block size.
50func (d *digest) BlockSize() int {
51	return d.parts[0].BlockSize()
52}
53
54// Size returns the number of bytes Sum will return. This will be the same as the bitlen (with conversion from bits to bytes)
55func (d *digest) Size() int {
56	return d.bits / 8
57}
58
59// Add more data to the running hash.
60// Once Sum() is called the hash is finalized and writing to the hash will panic
61func (d *digest) Write(p []byte) (int, error) {
62	if d.final {
63		panic("Cannot write to Full Domain Hash after a call has been made to Sum() and the hash has been finalized.")
64	}
65
66	// Write to each component hash asyncronously
67	var wg sync.WaitGroup
68	for i, h := range d.parts {
69		wg.Add(1)
70		go func(i int, h hash.Hash) {
71			h.Write(p) // Hashes in crypto library don't return errors
72			wg.Done()
73		}(i, h)
74	}
75	wg.Wait()
76
77	return len(p), nil
78}
79
80// Sum appends the current hash to b and returns the resulting slice.
81// Once Sum is called, the hash is finalized and can no longer be written to
82func (d *digest) Sum(in []byte) []byte {
83	hash := d.checkSum()
84	return append(in, hash[:]...)
85}
86
87func (d *digest) checkSum() []byte {
88	var sum []byte
89	for i, h := range d.parts {
90		if !d.final {
91			finalByte := byte(i)
92			h.Write([]byte{finalByte})
93		}
94		sum = append(sum, h.Sum(nil)...)
95	}
96	d.final = true
97	return sum[:d.bits/8]
98}
99
100// Sum returns the the Full Domain Hash checksum of the data.
101func Sum(h crypto.Hash, bitlen int, message []byte) []byte {
102	hash := New(h, bitlen)
103	hash.Write(message)
104	return hash.Sum(nil)
105}
106