mirror of
https://github.com/TecharoHQ/anubis.git
synced 2025-09-12 06:05:29 -04:00
93 lines
2.6 KiB
Go
93 lines
2.6 KiB
Go
package lib
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"crypto/subtle"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"golang.org/x/sync/semaphore"
|
|
)
|
|
|
|
var (
|
|
ErrChallengeFailed = errors.New("libanubis: challenge failed, hash does not match what the server calculated")
|
|
ErrWrongChallengeDifficulty = errors.New("libanubis: wrong challenge difficulty")
|
|
)
|
|
|
|
type Verifier interface {
|
|
Verify(ctx context.Context, challenge, verify []byte, nonce, difficulty uint32) (bool, error)
|
|
}
|
|
|
|
type VerifierFunc func(ctx context.Context, challenge, verify []byte, nonce, difficulty uint32) (bool, error)
|
|
|
|
func (vf VerifierFunc) Verify(ctx context.Context, challenge, verify []byte, nonce, difficulty uint32) (bool, error) {
|
|
return vf(ctx, challenge, verify, nonce, difficulty)
|
|
}
|
|
|
|
func BasicSHA256Verify(ctx context.Context, challenge, verify []byte, nonce, difficulty uint32) (bool, error) {
|
|
h := sha256.New()
|
|
fmt.Fprintf(h, "%x%d", challenge, nonce)
|
|
data := h.Sum(nil)
|
|
|
|
if subtle.ConstantTimeCompare(data, verify) != 1 {
|
|
return false, fmt.Errorf("%w: wanted %x, got: %x", ErrChallengeFailed, verify, data)
|
|
}
|
|
|
|
if !hasLeadingZeroNibbles(data, difficulty) {
|
|
return false, fmt.Errorf("%w: wanted %d leading zeroes in calculated data %x, but did not get it", ErrWrongChallengeDifficulty, difficulty, data)
|
|
}
|
|
|
|
if !hasLeadingZeroNibbles(verify, difficulty) {
|
|
return false, fmt.Errorf("%w: wanted %d leading zeroes in verification data %x, but did not get it", ErrWrongChallengeDifficulty, verify, difficulty)
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// hasLeadingZeroNibbles checks if the first `n` nibbles (in order) are zero.
|
|
// Nibbles are read from high to low for each byte (e.g., 0x12 -> nibbles [0x1, 0x2]).
|
|
func hasLeadingZeroNibbles(data []byte, n uint32) bool {
|
|
count := uint32(0)
|
|
for _, b := range data {
|
|
// Check high nibble (first 4 bits)
|
|
if (b >> 4) != 0 {
|
|
break // Non-zero found in leading nibbles
|
|
}
|
|
count++
|
|
if count >= n {
|
|
return true
|
|
}
|
|
|
|
// Check low nibble (last 4 bits)
|
|
if (b & 0x0F) != 0 {
|
|
break // Non-zero found in leading nibbles
|
|
}
|
|
count++
|
|
if count >= n {
|
|
return true
|
|
}
|
|
}
|
|
return count >= n
|
|
}
|
|
|
|
type ConcurrentVerifier struct {
|
|
Verifier
|
|
sem *semaphore.Weighted
|
|
}
|
|
|
|
func NewConcurrentVerifier(v Verifier, maxConcurrent int64) *ConcurrentVerifier {
|
|
return &ConcurrentVerifier{
|
|
Verifier: v,
|
|
sem: semaphore.NewWeighted(maxConcurrent),
|
|
}
|
|
}
|
|
|
|
func (cv *ConcurrentVerifier) Verify(ctx context.Context, challenge, verify []byte, nonce, difficulty uint32) (bool, error) {
|
|
if err := cv.sem.Acquire(ctx, 1); err != nil {
|
|
return false, fmt.Errorf("can't verify solution: %w", err)
|
|
}
|
|
|
|
return cv.Verifier.Verify(ctx, challenge, verify, nonce, difficulty)
|
|
}
|