/
hashcash.go
109 lines (97 loc) · 2.38 KB
/
hashcash.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// Package hashcash provides an implementation of Hashcash version 1 algorithm.
package hashcash
import (
"crypto/rand"
"crypto/sha1"
"encoding/base64"
"encoding/binary"
"fmt"
"hash"
"math"
"strconv"
"strings"
"time"
)
// Hash provides an implementation of hashcash v1.
type Hash struct {
hasher hash.Hash // SHA-1
bits uint // Number of zero bits
zeros uint // Number of zero digits
saltLen uint // Random salt length
extra string // Extension to add to the minted stamp
}
// New creates a new Hash with specified options.
func New(bits uint, saltLen uint, extra string) *Hash {
h := &Hash{
hasher: sha1.New(),
bits: bits,
saltLen: saltLen,
extra: extra}
h.zeros = uint(math.Ceil(float64(h.bits) / 4.0))
return h
}
// NewStd creates a new Hash with 20 bits of collision and 8 bytes of salt chars.
func NewStd() *Hash {
return New(20, 8, "")
}
// Date field format
const dateFormat = "060102"
// Mint a new hashcash stamp for resource.
func (h *Hash) Mint(resource string) (string, error) {
salt, err := h.getSalt()
if err != nil {
return "", err
}
date := time.Now().Format(dateFormat)
counter := 0
var stamp string
for {
stamp = fmt.Sprintf("1:%d:%s:%s:%s:%s:%x",
h.bits, date, resource, h.extra, salt, counter)
if h.checkZeros(stamp) {
return stamp, nil
}
counter++
}
}
// Check whether a hashcash stamp is valid.
func (h *Hash) Check(stamp string) bool {
if h.checkDate(stamp) {
return h.checkZeros(stamp)
}
return false
}
// CheckNoDate checks whether a hashcash stamp is valid ignoring date.
func (h *Hash) CheckNoDate(stamp string) bool {
return h.checkZeros(stamp)
}
func (h *Hash) getSalt() (string, error) {
buf := make([]byte, h.saltLen)
_, err := rand.Read(buf)
if err != nil {
return "", err
}
salt := base64.StdEncoding.EncodeToString(buf)
return salt[:h.saltLen], nil
}
func (h *Hash) checkZeros(stamp string) bool {
h.hasher.Reset()
h.hasher.Write([]byte(stamp))
sum := h.hasher.Sum(nil)
sumUint64 := binary.BigEndian.Uint64(sum)
sumBits := strconv.FormatUint(sumUint64, 2)
zeroes := 64 - len(sumBits)
return uint(zeroes) >= h.bits
}
func (h *Hash) checkDate(stamp string) bool {
fields := strings.Split(stamp, ":")
if len(fields) != 7 {
return false
}
then, err := time.Parse(dateFormat, fields[2])
if err != nil {
return false
}
duration := time.Since(then)
return duration.Hours()*2 <= 48
}