mirror of
https://github.com/TecharoHQ/anubis.git
synced 2025-09-13 14:47:39 -04:00
lib: add Verifier interface
Signed-off-by: Xe Iaso <me@xeiaso.net>
This commit is contained in:
parent
5610b026cc
commit
95f70ddf21
70
lib/verifier.go
Normal file
70
lib/verifier.go
Normal file
@ -0,0 +1,70 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
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, data, difficulty)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
114
lib/verifier_test.go
Normal file
114
lib/verifier_test.go
Normal file
@ -0,0 +1,114 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// echo -n "hi2" | sha256sum
|
||||
const hi2SHA256 = "0251f1ec2880f67631b8d0b3a62cf71a17dfa31858a323e7fc38068fcfaeded0"
|
||||
const nonce uint32 = 5
|
||||
const expectedVerifyString = "0543cbd94db5da055e82263cb775ac16f59fbbc1900645458baa197f9036ae9d"
|
||||
|
||||
func TestBasicSHA256Verify(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
challenge, err := hex.DecodeString(hi2SHA256)
|
||||
if err != nil {
|
||||
t.Fatalf("[unexpected] %s does not decode as hex", hi2SHA256)
|
||||
}
|
||||
|
||||
expectedVerify, err := hex.DecodeString(expectedVerifyString)
|
||||
if err != nil {
|
||||
t.Fatalf("[unexpected] %s does not decode as hex", expectedVerifyString)
|
||||
}
|
||||
|
||||
t.Logf("got nonce: %d", nonce)
|
||||
t.Logf("got hash: %x", expectedVerify)
|
||||
|
||||
invalidVerify := make([]byte, len(expectedVerify))
|
||||
copy(invalidVerify, expectedVerify)
|
||||
invalidVerify[len(invalidVerify)-1] ^= 0xFF // Flip the last byte
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
challenge []byte
|
||||
verify []byte
|
||||
nonce uint32
|
||||
difficulty uint32
|
||||
want bool
|
||||
expectError error
|
||||
}{
|
||||
{
|
||||
name: "valid verification",
|
||||
challenge: challenge,
|
||||
verify: expectedVerify,
|
||||
nonce: nonce,
|
||||
difficulty: 1,
|
||||
want: true,
|
||||
expectError: nil,
|
||||
},
|
||||
{
|
||||
name: "invalid verify data",
|
||||
challenge: challenge,
|
||||
verify: invalidVerify,
|
||||
nonce: nonce,
|
||||
difficulty: 1,
|
||||
want: false,
|
||||
expectError: ErrChallengeFailed,
|
||||
},
|
||||
{
|
||||
name: "insufficient computed data difficulty",
|
||||
challenge: challenge,
|
||||
verify: expectedVerify,
|
||||
nonce: nonce,
|
||||
difficulty: 5,
|
||||
want: false,
|
||||
expectError: ErrWrongChallengeDifficulty,
|
||||
},
|
||||
{
|
||||
name: "zero difficulty",
|
||||
challenge: challenge,
|
||||
verify: expectedVerify,
|
||||
nonce: nonce,
|
||||
difficulty: 0,
|
||||
want: true,
|
||||
expectError: nil,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := BasicSHA256Verify(ctx, tc.challenge, tc.verify, tc.nonce, tc.difficulty)
|
||||
if !errors.Is(err, tc.expectError) {
|
||||
t.Errorf("BasicSHA256Verify() error = %v, expectError %v", err, tc.expectError)
|
||||
return
|
||||
}
|
||||
if got != tc.want {
|
||||
t.Errorf("BasicSHA256Verify() got = %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHasLeadingZeroNibbles(t *testing.T) {
|
||||
for _, cs := range []struct {
|
||||
data []byte
|
||||
difficulty uint32
|
||||
valid bool
|
||||
}{
|
||||
{[]byte{0x10, 0x00}, 1, false},
|
||||
{[]byte{0x00, 0x00}, 4, true},
|
||||
{[]byte{0x01, 0x00}, 4, false},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("%x-%d-%v", cs.data, cs.difficulty, cs.valid), func(t *testing.T) {
|
||||
result := hasLeadingZeroNibbles(cs.data, cs.difficulty)
|
||||
if result != cs.valid {
|
||||
t.Errorf("wanted %v, but got: %v", cs.valid, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user