Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions model/auth_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -572,10 +572,12 @@ func VerifyEthereumSignature(publicKey string, message string, signature string)
msg := []byte(prefix + message)
msgHash := crypto.Keccak256Hash(msg)

// Decode signature
sig := common.FromHex(signature)
// Decode signature – accept hex (with or without 0x) and base64/base64url
// (some mobile clients such as DGEN1 encode the signature with
// base64.RawURLEncoding rather than hex)
sig := decodeSignatureBytes(signature)
if len(sig) != 65 {
return false, fmt.Errorf("signature must be 65 bytes")
return false, fmt.Errorf("signature must be 65 bytes, got %d (hex/base64 decode of %q)", len(sig), signature)
}
// Ethereum uses v = 27 or 28, Go expects 0 or 1
if sig[64] >= 27 {
Expand All @@ -587,7 +589,29 @@ func VerifyEthereumSignature(publicKey string, message string, signature string)
return false, err
}
recoveredAddr := crypto.PubkeyToAddress(*pubKey)
return recoveredAddr.Hex() == publicKey, nil
// Use case-insensitive comparison: crypto.PubkeyToAddress returns an EIP-55
// checksummed (mixed-case) address, but some clients send all-lowercase.
return strings.EqualFold(recoveredAddr.Hex(), publicKey), nil
}

// decodeSignatureBytes tries to decode a signature string first as hex
// (with or without 0x prefix), then as standard base64, then as raw
// base64url (no padding). The first encoding that produces a non-empty
// result whose length matches a plausible signature length wins.
func decodeSignatureBytes(s string) []byte {
// 1. Try hex (handles "0x" prefix automatically)
if b := common.FromHex(s); len(b) > 0 {
return b
}
// 2. Try standard base64 (with padding)
if b, err := base64.StdEncoding.DecodeString(s); err == nil && len(b) > 0 {
return b
}
// 3. Try raw base64url (no padding) – used by some mobile Go clients
if b, err := base64.RawURLEncoding.DecodeString(s); err == nil && len(b) > 0 {
return b
}
return nil
}

/**
Expand Down
31 changes: 29 additions & 2 deletions model/auth_model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package model
import (
"bytes"
"context"
"encoding/base64"
"encoding/hex"
"fmt"
"log"
"strings"
"testing"

"github.com/ethereum/go-ethereum/accounts"
Expand Down Expand Up @@ -207,19 +209,44 @@ func TestVerifyEthereumSignature(t *testing.T) {
}
sigHex := hex.EncodeToString(signature)

// --- hex-encoded signature, checksummed address (baseline) ---
isValid, err := VerifyEthereumSignature(address.String(), messageStr, sigHex)
assert.Equal(t, err, nil)
assert.Equal(t, isValid, true)

// Test with an invalid signature (modified signature)
// --- hex-encoded signature, all-lowercase address ---
// Catches: case-sensitive address comparison bug.
// address.String() returns EIP-55 checksummed (mixed-case); real clients
// often send all-lowercase. The old code used == which would fail here.
lowercaseAddr := strings.ToLower(address.String())
isValid, err = VerifyEthereumSignature(lowercaseAddr, messageStr, sigHex)
assert.Equal(t, err, nil)
assert.Equal(t, isValid, true)

// --- base64url (no padding) encoded signature, checksummed address ---
// Catches: mobile clients (e.g. DGEN1) that encode the signature with
// base64.RawURLEncoding instead of hex. The old code used common.FromHex
// which silently returns ~0 bytes for base64url input.
sigBase64url := base64.RawURLEncoding.EncodeToString(signature)
isValid, err = VerifyEthereumSignature(address.String(), messageStr, sigBase64url)
assert.Equal(t, err, nil)
assert.Equal(t, isValid, true)

// --- standard base64 (with padding) encoded signature ---
sigBase64 := base64.StdEncoding.EncodeToString(signature)
isValid, err = VerifyEthereumSignature(address.String(), messageStr, sigBase64)
assert.Equal(t, err, nil)
assert.Equal(t, isValid, true)

// --- invalid signature (modified bits) ---
invalidSigBytes, _ := hex.DecodeString(sigHex)
invalidSigBytes[10] ^= 0xFF // Flip some bits in the R or S part
invalidSigHex := hex.EncodeToString(invalidSigBytes)

isValid, err = VerifyEthereumSignature(address.String(), messageStr, invalidSigHex)
assert.Equal(t, isValid, false)

// Malformed signature (wrong length)
// --- malformed signature (wrong length) ---
malformedSig := "wrongsig"
isValid, err = VerifyEthereumSignature(address.String(), messageStr, malformedSig)
assert.NotEqual(t, err, nil) // Error expected
Expand Down