From 37695ca2e1a52c4d7bc98cb12f58f0d413370048 Mon Sep 17 00:00:00 2001 From: Stuart <11183054+stuartwk@users.noreply.github.com> Date: Wed, 13 May 2026 14:29:18 +0900 Subject: [PATCH] bugfix ethereum signature verificaiton --- model/auth_model.go | 32 ++++++++++++++++++++++++++++---- model/auth_model_test.go | 31 +++++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/model/auth_model.go b/model/auth_model.go index 27ac0b8d..fffd8c97 100644 --- a/model/auth_model.go +++ b/model/auth_model.go @@ -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 { @@ -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 } /** diff --git a/model/auth_model_test.go b/model/auth_model_test.go index 8e0e7187..6fd0d21d 100644 --- a/model/auth_model_test.go +++ b/model/auth_model_test.go @@ -3,9 +3,11 @@ package model import ( "bytes" "context" + "encoding/base64" "encoding/hex" "fmt" "log" + "strings" "testing" "github.com/ethereum/go-ethereum/accounts" @@ -207,11 +209,36 @@ 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) @@ -219,7 +246,7 @@ func TestVerifyEthereumSignature(t *testing.T) { 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