initial commit
This commit is contained in:
parent
86b384e9ca
commit
badaa411f2
|
@ -1,3 +1,7 @@
|
||||||
# hashcash
|
# hashcash
|
||||||
|
|
||||||
HTTP Hashcash impremented in Go.
|
HTTP Hashcash implemented in Go.
|
||||||
|
|
||||||
|
Explanation at https://therootcompany.com/blog/http-hashcash/
|
||||||
|
|
||||||
|
Go docs at https://godoc.org/git.rootprojects.org/root/hashcash
|
||||||
|
|
|
@ -0,0 +1,284 @@
|
||||||
|
package hashcash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrParse is returned when fewer than 6 or more than 7 segments are split
|
||||||
|
var ErrParse = errors.New("could not split the hashcash parts")
|
||||||
|
|
||||||
|
// ErrInvalidTag is returned when the Hashcash version is unsupported
|
||||||
|
var ErrInvalidTag = errors.New("expected tag to be 'H'")
|
||||||
|
|
||||||
|
// ErrInvalidDifficulty is returned when the difficulty is outside of the acceptable range
|
||||||
|
var ErrInvalidDifficulty = errors.New("the number of bits of difficulty is too low or too high")
|
||||||
|
|
||||||
|
// ErrInvalidDate is returned when the date cannot be parsed as a positive int64
|
||||||
|
var ErrInvalidDate = errors.New("invalid date")
|
||||||
|
|
||||||
|
// ErrExpired is returned when the current time is past that of ExpiresAt
|
||||||
|
var ErrExpired = errors.New("expired hashcash")
|
||||||
|
|
||||||
|
// ErrInvalidSubject is returned when the subject is invalid or does not match that passed to Verify()
|
||||||
|
var ErrInvalidSubject = errors.New("the subject is invalid or rejected")
|
||||||
|
|
||||||
|
// ErrInvalidNonce is returned when the nonce
|
||||||
|
//var ErrInvalidNonce = errors.New("the nonce has been used or is invalid")
|
||||||
|
|
||||||
|
// ErrUnsupportedAlgorithm is returned when the given algorithm is not supported
|
||||||
|
var ErrUnsupportedAlgorithm = errors.New("the given algorithm is invalid or not supported")
|
||||||
|
|
||||||
|
// ErrInvalidSolution is returned when the given hashcash is not properly solved
|
||||||
|
var ErrInvalidSolution = errors.New("the given solution is not valid")
|
||||||
|
|
||||||
|
// MaxDifficulty is the upper bound for all Solve() operations
|
||||||
|
var MaxDifficulty = 26
|
||||||
|
|
||||||
|
// Sep is the separator character to use
|
||||||
|
var Sep = ":"
|
||||||
|
|
||||||
|
// no milliseconds
|
||||||
|
//var isoTS = "2006-01-02T15:04:05Z"
|
||||||
|
|
||||||
|
// Hashcash represents a parsed Hashcash string
|
||||||
|
type Hashcash struct {
|
||||||
|
Tag string `json:"tag"` // Always "H" for "HTTP"
|
||||||
|
Difficulty int `json:"difficulty"` // Number of "partial pre-image" (zero) bits in the hashed code
|
||||||
|
ExpiresAt time.Time `json:"exp"` // The timestamp that the hashcash expires, as seconds since the Unix epoch
|
||||||
|
Subject string `json:"sub"` // Resource data string being transmitted, e.g., a domain or URL
|
||||||
|
Nonce string `json:"nonce"` // Unique string of random characters, encoded as url-safe base-64
|
||||||
|
Alg string `json:"alg"` // always SHA-256 for now
|
||||||
|
Solution string `json:"solution"` // Binary counter, encoded as url-safe base-64
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a Hashcash with reasonable defaults
|
||||||
|
func New(h Hashcash) *Hashcash {
|
||||||
|
h.Tag = "H"
|
||||||
|
|
||||||
|
if 0 == h.Difficulty {
|
||||||
|
// safe for WebCrypto
|
||||||
|
h.Difficulty = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.ExpiresAt.IsZero() {
|
||||||
|
h.ExpiresAt = time.Now().Add(5 * time.Minute)
|
||||||
|
}
|
||||||
|
h.ExpiresAt = h.ExpiresAt.UTC().Truncate(time.Second)
|
||||||
|
|
||||||
|
if "" == h.Subject {
|
||||||
|
h.Subject = "*"
|
||||||
|
}
|
||||||
|
|
||||||
|
if "" == h.Nonce {
|
||||||
|
nonce := make([]byte, 16)
|
||||||
|
if _, err := rand.Read(nonce); nil != err {
|
||||||
|
panic(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
h.Nonce = base64.RawURLEncoding.EncodeToString(nonce)
|
||||||
|
}
|
||||||
|
|
||||||
|
if "" == h.Alg {
|
||||||
|
h.Alg = "SHA-256"
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
if "SHA-256" != h.Alg {
|
||||||
|
// TODO error
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
return &h
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse will (obviously) parse the hashcash string, without verifying any
|
||||||
|
// of the parameters.
|
||||||
|
func Parse(hc string) (*Hashcash, error) {
|
||||||
|
parts := strings.Split(hc, Sep)
|
||||||
|
n := len(parts)
|
||||||
|
if n < 6 || n > 7 {
|
||||||
|
return nil, ErrParse
|
||||||
|
}
|
||||||
|
|
||||||
|
tag := parts[0]
|
||||||
|
if "H" != tag {
|
||||||
|
return nil, ErrInvalidTag
|
||||||
|
}
|
||||||
|
|
||||||
|
bits, err := strconv.Atoi(parts[1])
|
||||||
|
if nil != err || bits < 0 {
|
||||||
|
return nil, ErrInvalidDifficulty
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow empty ExpiresAt
|
||||||
|
var exp time.Time
|
||||||
|
if "" != parts[2] {
|
||||||
|
expAt, err := strconv.ParseInt(parts[2], 10, 64)
|
||||||
|
if nil != err || expAt < 0 {
|
||||||
|
return nil, ErrInvalidDate
|
||||||
|
}
|
||||||
|
exp = time.Unix(int64(expAt), 0).UTC()
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
exp, err := time.ParseInLocation(isoTS, parts[2], time.UTC)
|
||||||
|
if nil != err {
|
||||||
|
return nil, ErrInvalidDate
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
sub := parts[3]
|
||||||
|
|
||||||
|
nonce := parts[4]
|
||||||
|
|
||||||
|
alg := parts[5]
|
||||||
|
|
||||||
|
var solution string
|
||||||
|
if n > 6 {
|
||||||
|
solution = parts[6]
|
||||||
|
}
|
||||||
|
|
||||||
|
h := &Hashcash{
|
||||||
|
Tag: tag,
|
||||||
|
Difficulty: bits,
|
||||||
|
ExpiresAt: exp.UTC().Truncate(time.Second),
|
||||||
|
Subject: sub,
|
||||||
|
Nonce: nonce,
|
||||||
|
Alg: alg,
|
||||||
|
Solution: solution,
|
||||||
|
}
|
||||||
|
|
||||||
|
return h, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String will return the formatted Hashcash, omitting the solution if it has not be solved.
|
||||||
|
func (h *Hashcash) String() string {
|
||||||
|
var solution string
|
||||||
|
if "" != h.Solution {
|
||||||
|
solution = Sep + h.Solution
|
||||||
|
}
|
||||||
|
|
||||||
|
var expAt string
|
||||||
|
if !h.ExpiresAt.IsZero() {
|
||||||
|
expAt = strconv.FormatInt(h.ExpiresAt.UTC().Truncate(time.Second).Unix(), 10)
|
||||||
|
}
|
||||||
|
return strings.Join(
|
||||||
|
[]string{
|
||||||
|
"H",
|
||||||
|
strconv.Itoa(h.Difficulty),
|
||||||
|
//h.ExpiresAt.UTC().Format(isoTS),
|
||||||
|
expAt,
|
||||||
|
h.Subject,
|
||||||
|
h.Nonce,
|
||||||
|
h.Alg,
|
||||||
|
},
|
||||||
|
Sep,
|
||||||
|
) + solution
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the Hashcash based on Difficulty, Algorithm, ExpiresAt, Subject and,
|
||||||
|
// of course, the Solution and hash.
|
||||||
|
func (h *Hashcash) Verify(subject string) error {
|
||||||
|
if h.Difficulty < 0 {
|
||||||
|
return ErrInvalidDifficulty
|
||||||
|
}
|
||||||
|
|
||||||
|
if "SHA-256" != h.Alg {
|
||||||
|
return ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
if !h.ExpiresAt.IsZero() && h.ExpiresAt.Sub(time.Now()) < 0 {
|
||||||
|
return ErrExpired
|
||||||
|
}
|
||||||
|
|
||||||
|
if subject != h.Subject {
|
||||||
|
return ErrInvalidSubject
|
||||||
|
}
|
||||||
|
|
||||||
|
bits := h.Difficulty
|
||||||
|
hash := sha256.Sum256([]byte(h.String()))
|
||||||
|
n := bits / 8 // 10 / 8 = 1
|
||||||
|
m := bits % 8 // 10 % 8 = 2
|
||||||
|
if m > 0 {
|
||||||
|
n++ // 10 bits = 2 bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
if !verifyBits(hash[:n], bits, n) {
|
||||||
|
return ErrInvalidSolution
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyBits(hash []byte, bits, n int) bool {
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
if bits > 8 {
|
||||||
|
bits -= 8
|
||||||
|
if 0 != hash[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// (bits % 8) == bits
|
||||||
|
pad := 8 - bits
|
||||||
|
if 0 != hash[i]>>pad {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0 == bits
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solve will search for a solution, returning an error if the difficulty is
|
||||||
|
// above the local or global MaxDifficulty, the Algorithm is unsupported.
|
||||||
|
func (h *Hashcash) Solve(maxDifficulty int) error {
|
||||||
|
if "SHA-256" != h.Alg {
|
||||||
|
return ErrUnsupportedAlgorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.Difficulty < 0 {
|
||||||
|
return ErrInvalidDifficulty
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.Difficulty > maxDifficulty || h.Difficulty > MaxDifficulty {
|
||||||
|
return ErrInvalidDifficulty
|
||||||
|
}
|
||||||
|
|
||||||
|
if "" != h.Solution {
|
||||||
|
if nil == h.Verify(h.Subject) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
h.Solution = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
hashcash := h.String()
|
||||||
|
bits := h.Difficulty
|
||||||
|
n := bits / 8 // 10 / 8 = 1
|
||||||
|
m := bits % 8 // 10 % 8 = 2
|
||||||
|
if m > 0 {
|
||||||
|
n++ // 10 bits = 2 bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
var solution uint32 = 0
|
||||||
|
sb := make([]byte, 4)
|
||||||
|
for {
|
||||||
|
// Note: it's not actually important what method of change or encoding is used
|
||||||
|
// but incrementing by 1 on an int32 is good enough, and makes for a small base64 encoding
|
||||||
|
binary.LittleEndian.PutUint32(sb, solution)
|
||||||
|
h.Solution = base64.RawURLEncoding.EncodeToString(sb)
|
||||||
|
hash := sha256.Sum256([]byte(hashcash + Sep + h.Solution))
|
||||||
|
if verifyBits(hash[:n], bits, n) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
solution++
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,331 @@
|
||||||
|
package hashcash
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var never = time.Date(3006, time.January, 1, 15, 4, 5, 0, time.UTC)
|
||||||
|
|
||||||
|
func TestNew(t *testing.T) {
|
||||||
|
h1 := New(Hashcash{})
|
||||||
|
|
||||||
|
h2, err := Parse(h1.String())
|
||||||
|
if nil != err {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if h1.Tag != h2.Tag {
|
||||||
|
t.Fatal("wrong tag version")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if h1.Difficulty != h2.Difficulty {
|
||||||
|
t.Fatal("wrong difficulty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if h1.ExpiresAt != h2.ExpiresAt {
|
||||||
|
t.Fatal("wrong expiresat")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if h1.Subject != h2.Subject {
|
||||||
|
t.Fatal("wrong subject")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if h1.Nonce != h2.Nonce {
|
||||||
|
t.Fatal("wrong nonce")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if h1.Alg != h2.Alg {
|
||||||
|
t.Fatal("wrong algorithm")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if h1.Solution != h2.Solution {
|
||||||
|
t.Fatal("wrong solution")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExplicit(t *testing.T) {
|
||||||
|
nonce := make([]byte, 16)
|
||||||
|
_, _ = rand.Read(nonce)
|
||||||
|
h0 := Hashcash{
|
||||||
|
Tag: "H",
|
||||||
|
Difficulty: 1000,
|
||||||
|
ExpiresAt: time.Now().UTC().Truncate(time.Second),
|
||||||
|
Subject: "example.com",
|
||||||
|
Nonce: base64.RawURLEncoding.EncodeToString(nonce),
|
||||||
|
Alg: "FOOBAR",
|
||||||
|
Solution: "incorrect",
|
||||||
|
}
|
||||||
|
h1 := New(h0)
|
||||||
|
|
||||||
|
h2, err := Parse(h1.String())
|
||||||
|
if nil != err {
|
||||||
|
t.Fatal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h3 := New(*h2)
|
||||||
|
|
||||||
|
if h0.Tag != h3.Tag {
|
||||||
|
t.Fatal("wrong tag version")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if h0.Difficulty != h3.Difficulty {
|
||||||
|
t.Fatal("wrong difficulty")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if h0.ExpiresAt != h3.ExpiresAt {
|
||||||
|
t.Fatal("wrong expiresat")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if h0.Subject != h3.Subject {
|
||||||
|
t.Fatal("wrong subject")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if h0.Nonce != h3.Nonce {
|
||||||
|
t.Fatal("wrong nonce")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if h0.Alg != h3.Alg {
|
||||||
|
t.Fatal("wrong algorithm")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if h0.Solution != h3.Solution {
|
||||||
|
t.Fatal("wrong solution")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExpired(t *testing.T) {
|
||||||
|
h := New(Hashcash{
|
||||||
|
Alg: "SHA-256",
|
||||||
|
Difficulty: 10,
|
||||||
|
ExpiresAt: time.Now().Add(-5 * time.Second),
|
||||||
|
})
|
||||||
|
|
||||||
|
err := h.Verify(h.Subject)
|
||||||
|
if nil == err {
|
||||||
|
t.Error("verified expired token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != ErrExpired {
|
||||||
|
t.Error("expired token error is not ErrExpired")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoExpiry(t *testing.T) {
|
||||||
|
h := New(Hashcash{})
|
||||||
|
h.ExpiresAt = time.Time{}
|
||||||
|
|
||||||
|
if err := h.Solve(20); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.Verify(h.Subject); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSolve0(t *testing.T) {
|
||||||
|
h := New(Hashcash{
|
||||||
|
ExpiresAt: never,
|
||||||
|
Nonce: "DeadBeef",
|
||||||
|
})
|
||||||
|
h.Difficulty = 0
|
||||||
|
|
||||||
|
if err := h.Solve(1); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.Verify(h.Subject); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSolve1(t *testing.T) {
|
||||||
|
h := New(Hashcash{
|
||||||
|
Difficulty: 1,
|
||||||
|
ExpiresAt: never,
|
||||||
|
Nonce: "DeadBeef",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := h.Solve(2); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.Verify(h.Subject); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSolve2(t *testing.T) {
|
||||||
|
h := New(Hashcash{
|
||||||
|
Difficulty: 2,
|
||||||
|
ExpiresAt: never,
|
||||||
|
Nonce: "DeadBeef",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := h.Solve(3); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.Verify(h.Subject); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSolve7(t *testing.T) {
|
||||||
|
h := New(Hashcash{
|
||||||
|
Difficulty: 7,
|
||||||
|
ExpiresAt: never,
|
||||||
|
Nonce: "DeadBeef",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := h.Solve(8); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.Verify(h.Subject); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSolve8(t *testing.T) {
|
||||||
|
h := New(Hashcash{
|
||||||
|
Difficulty: 8,
|
||||||
|
ExpiresAt: never,
|
||||||
|
Nonce: "DeadBeef",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := h.Solve(9); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.Verify(h.Subject); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSolve9(t *testing.T) {
|
||||||
|
h := New(Hashcash{
|
||||||
|
Difficulty: 9,
|
||||||
|
ExpiresAt: never,
|
||||||
|
Nonce: "DeadBeef",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := h.Solve(10); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.Verify(h.Subject); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSolve15(t *testing.T) {
|
||||||
|
h := New(Hashcash{
|
||||||
|
Difficulty: 15,
|
||||||
|
ExpiresAt: never,
|
||||||
|
Nonce: "DeadBeef",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := h.Solve(16); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.Verify(h.Subject); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSolve16(t *testing.T) {
|
||||||
|
h := New(Hashcash{
|
||||||
|
Difficulty: 16,
|
||||||
|
ExpiresAt: never,
|
||||||
|
Nonce: "DeadBeef",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := h.Solve(17); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.Verify(h.Subject); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSolve17(t *testing.T) {
|
||||||
|
h := New(Hashcash{
|
||||||
|
Difficulty: 17,
|
||||||
|
ExpiresAt: never,
|
||||||
|
Nonce: "DeadBeef",
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := h.Solve(18); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.Verify(h.Subject); nil != err {
|
||||||
|
t.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if "H:17:32693036645:*:DeadBeef:SHA-256:R7sIAA" != h.String() {
|
||||||
|
t.Errorf("unexpected hashcash string: %s, has the implementation changed?", h.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTooHard(t *testing.T) {
|
||||||
|
h := New(Hashcash{
|
||||||
|
Alg: "SHA-256",
|
||||||
|
Difficulty: 24,
|
||||||
|
ExpiresAt: never,
|
||||||
|
Nonce: "DeadBeef",
|
||||||
|
})
|
||||||
|
|
||||||
|
err := h.Solve(20)
|
||||||
|
if nil == err {
|
||||||
|
t.Error(errors.New("the challenge is too hard, should've quite"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ErrInvalidDifficulty != err {
|
||||||
|
t.Error(errors.New("incorrect error"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue