proofofwork.go 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. package proofofwork
  2. import (
  3. "crypto/subtle"
  4. "fmt"
  5. "log/slog"
  6. "net/http"
  7. "strconv"
  8. "strings"
  9. "github.com/TecharoHQ/anubis/internal"
  10. chall "github.com/TecharoHQ/anubis/lib/challenge"
  11. "github.com/TecharoHQ/anubis/lib/localization"
  12. "github.com/a-h/templ"
  13. )
  14. //go:generate go tool github.com/a-h/templ/cmd/templ generate
  15. func init() {
  16. chall.Register("fast", &Impl{Algorithm: "fast"})
  17. chall.Register("slow", &Impl{Algorithm: "slow"})
  18. }
  19. type Impl struct {
  20. Algorithm string
  21. }
  22. func (i *Impl) Setup(mux *http.ServeMux) {}
  23. func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *chall.IssueInput) (templ.Component, error) {
  24. loc := localization.GetLocalizer(r)
  25. return page(loc), nil
  26. }
  27. func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *chall.ValidateInput) error {
  28. rule := in.Rule
  29. challenge := in.Challenge.RandomData
  30. nonceStr := r.FormValue("nonce")
  31. if nonceStr == "" {
  32. return chall.NewError("validate", "invalid response", fmt.Errorf("%w nonce", chall.ErrMissingField))
  33. }
  34. nonce, err := strconv.Atoi(nonceStr)
  35. if err != nil {
  36. return chall.NewError("validate", "invalid response", fmt.Errorf("%w: nonce: %w", chall.ErrInvalidFormat, err))
  37. }
  38. elapsedTimeStr := r.FormValue("elapsedTime")
  39. if elapsedTimeStr == "" {
  40. return chall.NewError("validate", "invalid response", fmt.Errorf("%w elapsedTime", chall.ErrMissingField))
  41. }
  42. elapsedTime, err := strconv.ParseFloat(elapsedTimeStr, 64)
  43. if err != nil {
  44. return chall.NewError("validate", "invalid response", fmt.Errorf("%w: elapsedTime: %w", chall.ErrInvalidFormat, err))
  45. }
  46. response := r.FormValue("response")
  47. if response == "" {
  48. return chall.NewError("validate", "invalid response", fmt.Errorf("%w response", chall.ErrMissingField))
  49. }
  50. calcString := fmt.Sprintf("%s%d", challenge, nonce)
  51. calculated := internal.SHA256sum(calcString)
  52. if subtle.ConstantTimeCompare([]byte(response), []byte(calculated)) != 1 {
  53. return chall.NewError("validate", "invalid response", fmt.Errorf("%w: wanted response %s but got %s", chall.ErrFailed, calculated, response))
  54. }
  55. // compare the leading zeroes
  56. if !strings.HasPrefix(response, strings.Repeat("0", rule.Challenge.Difficulty)) {
  57. return chall.NewError("validate", "invalid response", fmt.Errorf("%w: wanted %d leading zeros but got %s", chall.ErrFailed, rule.Challenge.Difficulty, response))
  58. }
  59. lg.Debug("challenge took", "elapsedTime", elapsedTime)
  60. chall.TimeTaken.WithLabelValues(i.Algorithm).Observe(elapsedTime)
  61. return nil
  62. }