initial commit
This commit is contained in:
		
							parent
							
								
									86b384e9ca
								
							
						
					
					
						commit
						badaa411f2
					
				| @ -1,3 +1,7 @@ | ||||
| # 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 | ||||
|  | ||||
							
								
								
									
										284
									
								
								hashcash.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										284
									
								
								hashcash.go
									
									
									
									
									
										Normal file
									
								
							| @ -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++ | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										331
									
								
								hashcash_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										331
									
								
								hashcash_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user