package main import ( "crypto/rand" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "log" "math" "net/http" "strconv" "golang.org/x/crypto/scrypt" ) const numberOfChallenges = 1000 const deprecateAfterGenerations = 10 const portNumber = 2370 // https://en.wikipedia.org/wiki/Scrypt type ScryptParameters struct { CPUAndMemoryCost int `json:"N"` BlockSize int `json:"r"` Paralellization int `json:"p"` KeyLength int `json:"klen"` } type Challenge struct { ScryptParameters Preimage string `json:"i"` Difficulty string `json:"d"` DifficultyLevel int `json:"dl"` } var currentChallengesGeneration = 0 var challenges = map[string]int{} func main() { scryptParameters := ScryptParameters{ CPUAndMemoryCost: 4096, BlockSize: 8, Paralellization: 1, KeyLength: 16, } http.HandleFunc("/GetChallenges", func(responseWriter http.ResponseWriter, request *http.Request) { if request.Method != "POST" { responseWriter.Header().Set("Allow", "POST") http.Error(responseWriter, "405 Method Not Allowed, try POST", http.StatusMethodNotAllowed) } currentChallengesGeneration++ requestQuery := request.URL.Query() difficultyLevelString := requestQuery.Get("difficultyLevel") difficultyLevel, err := strconv.Atoi(difficultyLevelString) if err != nil { http.Error( responseWriter, fmt.Sprintf( "400 url param ?difficultyLevel=%s value could not be converted to an integer", difficultyLevelString, ), http.StatusInternalServerError, ) return } toReturn := make([]string, numberOfChallenges) for i := 0; i < numberOfChallenges; i++ { preimageBytes := make([]byte, 8) _, err := rand.Read(preimageBytes) if err != nil { http.Error(responseWriter, "500 internal server error", http.StatusInternalServerError) log.Printf("read random bytes failed: %v", err) return } preimage := base64.StdEncoding.EncodeToString(preimageBytes) difficultyBytes := make([]byte, int(math.Ceil(float64(difficultyLevel)/float64(8)))) for j := 0; j < len(difficultyBytes); j++ { difficultyByte := byte(0) for k := 0; k < 8; k++ { currentBitIndex := (len(difficultyBytes) * 8) - (j*8 + k) if currentBitIndex > difficultyLevel { difficultyByte = difficultyByte | 1< challenge.Difficulty { http.Error( responseWriter, fmt.Sprintf( "400 bad request: nonce given by url param ?nonce=%s did not result in a hash that meets the required difficulty", nonceHex, ), http.StatusBadRequest, ) return } responseWriter.WriteHeader(200) responseWriter.Write([]byte("OK")) }) http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static/")))) log.Printf("💥 PoW! Captcha server listening on port %d", portNumber) err := http.ListenAndServe(fmt.Sprintf(":%d", portNumber), nil) // if got this far it means server crashed! panic(err) }