Files

165 lines
3.8 KiB
Go

package main
import (
"context"
"crypto"
_ "crypto/sha1"
"encoding/base64"
"fmt"
"math/rand"
"os"
"runtime"
"strings"
"time"
)
func main() {
g := Generator{
Difficulty: 20,
Timeout: 10 * time.Second,
}
c := g.RandomChallenge()
c.Solve()
if c.Validate() {
fmt.Println(c.String())
}
}
type Challenge struct {
Input [48]byte
Date [20]byte
Solution int64
Generator
}
type Generator struct {
Difficulty byte
Timeout time.Duration
}
func (g *Generator) RandomChallenge() Challenge {
bytes := make([]byte, 48)
for i, _ := range bytes {
bytes[i] = byte(rand.Int())
}
today := time.Now().UTC()
date := today.Format("2006-01-02")
hash := crypto.SHA1.New().Sum([]byte(date))
return Challenge{
Input: [48]byte(bytes),
Date: [20]byte(hash),
Generator: *g,
}
}
// using a lookup table for this is super goofy but i'm lazy and it's 6am
var prefixLookup = []int{
8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
}
func (c *Challenge) Solve() {
timer := time.Now()
solution := make(chan int64)
ctx, cancel := context.WithCancel(context.Background())
count := runtime.NumCPU()
for i := range count {
go func(id int, ctx context.Context) {
i := int64(id)
defer cancel()
for {
select {
case <-ctx.Done():
return
default:
s := c.CheckValue(i)
if s {
solution <- i
fmt.Fprintf(os.Stderr, "[thread %d] ", id)
cancel()
break
}
if c.Solution > 0xfffffff {
fmt.Fprintf(os.Stderr, "%s (thread %d): could not find solution!\n", c.String(), id)
cancel()
break
}
if time.Since(timer) > c.Timeout {
fmt.Fprintf(os.Stderr, "%s (thread %d): timed out!\n", c.String(), id)
cancel()
break
}
i += int64(count)
}
}
}(i, ctx)
}
select {
case c.Solution = <-solution:
cancel()
fmt.Fprintf(os.Stderr, "%s: found solution %d in %dms\n", c.String(), c.Solution, time.Since(timer).Milliseconds())
break
case <-ctx.Done():
cancel()
break
}
}
func (c *Challenge) CheckValue(v int64) bool {
var sb strings.Builder
sb.WriteString(c.StringNoSolution())
sb.WriteString(StringSolution(v))
sum := make([]byte, 20)
hash := crypto.SHA1.New()
hash.Write([]byte(sb.String()))
sum = hash.Sum(nil)
count := 0
for _, c := range sum {
p := prefixLookup[c]
count += p
if p != 8 {
break
}
}
return count >= int(c.Difficulty)
}
func (c *Challenge) Validate() bool {
return c.CheckValue(c.Solution)
}
func (c *Challenge) String() string {
var sb strings.Builder
sb.WriteString(c.StringNoSolution())
sb.WriteString(c.StringThisSolution())
return sb.String()
}
func (c *Challenge) StringNoSolution() string {
var sb strings.Builder
bytes := make([]byte, 0)
bytes = append(bytes, c.Input[:]...)
bytes = append(bytes, c.Date[:]...)
bytes = append(bytes, c.Difficulty)
sb.WriteString(base64.URLEncoding.EncodeToString(bytes))
return sb.String()
}
func (c *Challenge) StringThisSolution() string {
return StringSolution(c.Solution)
}
func StringSolution(v int64) string {
var sb strings.Builder
sb.WriteRune(';')
sb.WriteString(fmt.Sprintf("%07x", v))
return sb.String()
}