64 lines
2.1 KiB
Go
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
|
|
}
|