Files
root-me-api/internal/anubis/pow.go
T
mycroft cf961ab94f
build / test (push) Successful in 9s
build / build (push) Successful in 10s
build / build-image (push) Successful in 1m4s
feat: initial commit
2026-06-04 14:19:37 +02:00

64 lines
2.1 KiB
Go

// Package anubis implements a native Go client for getting past the Anubis
// (TecharoHQ) proof-of-work anti-scraper proxy without a headless browser.
//
// The challenge is embedded in the interstitial HTML as a JSON document inside
// a <script id="anubis_challenge"> element. The client must find an integer
// nonce such that SHA-256(randomData + nonce) has `difficulty` leading hex-zero
// digits, then submit it to the pass-challenge endpoint to obtain a signed-JWT
// auth cookie.
package anubis
import (
"crypto/sha256"
"encoding/hex"
"strconv"
)
// sha256Hex returns the hex-encoded SHA-256 of s. This is the answer to a
// "preact" Anubis challenge: result = SHA256(randomData).
func sha256Hex(s string) string {
sum := sha256.Sum256([]byte(s))
return hex.EncodeToString(sum[:])
}
// Solve performs the Anubis proof-of-work: it searches for the smallest nonce
// (starting at 0) such that SHA-256(randomData + nonce) has `difficulty`
// leading hex-zero digits. It returns the hex-encoded hash and the nonce.
//
// The zero check mirrors the Anubis worker: the first floor(difficulty/2)
// bytes must be zero and, when difficulty is odd, the high nibble of the next
// byte must also be zero.
func Solve(randomData string, difficulty int) (hash string, nonce uint64) {
prefix := []byte(randomData)
zeroBytes := difficulty / 2
oddNibble := difficulty%2 != 0
// Reusable buffer: challenge prefix + decimal nonce, refilled each round.
buf := make([]byte, 0, len(prefix)+20)
for n := uint64(0); ; n++ {
buf = append(buf[:0], prefix...)
buf = strconv.AppendUint(buf, n, 10)
sum := sha256.Sum256(buf)
if leadingZeros(sum[:], zeroBytes, oddNibble) {
return hex.EncodeToString(sum[:]), n
}
}
}
// leadingZeros reports whether sum has the required number of leading hex-zero
// digits: zeroBytes fully-zero bytes, plus (when oddNibble) a zero high nibble
// in the following byte.
func leadingZeros(sum []byte, zeroBytes int, oddNibble bool) bool {
for i := range zeroBytes {
if sum[i] != 0 {
return false
}
}
if oddNibble {
return sum[zeroBytes]>>4 == 0
}
return true
}