157 lines
3.6 KiB
Go
157 lines
3.6 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"crypto"
|
|
_ "crypto/sha1"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"runtime"
|
|
"time"
|
|
)
|
|
|
|
func main() {
|
|
g := Generator{
|
|
Difficulty: 25,
|
|
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
|
|
bytes []byte
|
|
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() {
|
|
c.bytes = make([]byte, 76)
|
|
c.bytes = append(c.bytes, c.Input[:]...)
|
|
c.bytes = append(c.bytes, c.Date[:]...)
|
|
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 > 0xffffffffffffff {
|
|
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 {
|
|
bytes := make([]byte, len(c.bytes))
|
|
copy(bytes, c.bytes)
|
|
for i := range 8 {
|
|
bytes[75-i] = byte(v >> (i * 8))
|
|
}
|
|
sum := make([]byte, 20)
|
|
hash := crypto.SHA1.New()
|
|
hash.Write(bytes)
|
|
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 {
|
|
bytes := make([]byte, 0)
|
|
bytes = append(bytes, c.Input[:]...)
|
|
bytes = append(bytes, c.Date[:]...)
|
|
bytes = append(bytes, make([]byte, 8)...)
|
|
bytes[68] = c.Difficulty
|
|
for i := range 7 {
|
|
bytes[75-i] = byte(c.Solution >> (i * 8))
|
|
}
|
|
return hex.EncodeToString(bytes)
|
|
}
|