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() }