mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-20 23:26:40 +00:00
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user