mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-12-15 23:18:49 +00:00
feat: enhance signature verification for multiple blockchain types
- Added support for verifying signatures from both Ethereum (ETH) and Solana (SOL) blockchains in the verifyHandler. - Introduced a new ChainType field in the request to specify the blockchain type, defaulting to ETH for backward compatibility. - Implemented Ed25519 signature verification for Solana, including base64 decoding of signatures and base58 decoding of public keys. - Enhanced error handling for unsupported chain types and invalid signature formats, improving robustness of the verification process.
This commit is contained in:
parent
0f0237b5ea
commit
c208ff3288
@ -1,11 +1,13 @@
|
|||||||
package gateway
|
package gateway
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/ed25519"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -143,6 +145,7 @@ func (g *Gateway) verifyHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
Nonce string `json:"nonce"`
|
Nonce string `json:"nonce"`
|
||||||
Signature string `json:"signature"`
|
Signature string `json:"signature"`
|
||||||
Namespace string `json:"namespace"`
|
Namespace string `json:"namespace"`
|
||||||
|
ChainType string `json:"chain_type"` // "ETH" or "SOL", defaults to "ETH"
|
||||||
}
|
}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
writeError(w, http.StatusBadRequest, "invalid json body")
|
writeError(w, http.StatusBadRequest, "invalid json body")
|
||||||
@ -176,36 +179,94 @@ func (g *Gateway) verifyHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
nonceID := nres.Rows[0][0]
|
nonceID := nres.Rows[0][0]
|
||||||
|
|
||||||
// EVM personal_sign verification of the nonce
|
// Determine chain type (default to ETH for backward compatibility)
|
||||||
// Hash: keccak256("\x19Ethereum Signed Message:\n" + len(nonce) + nonce)
|
chainType := strings.ToUpper(strings.TrimSpace(req.ChainType))
|
||||||
msg := []byte(req.Nonce)
|
if chainType == "" {
|
||||||
prefix := []byte("\x19Ethereum Signed Message:\n" + strconv.Itoa(len(msg)))
|
chainType = "ETH"
|
||||||
hash := ethcrypto.Keccak256(prefix, msg)
|
}
|
||||||
|
|
||||||
// Decode signature (expects 65-byte r||s||v, hex with optional 0x)
|
// Verify signature based on chain type
|
||||||
sigHex := strings.TrimSpace(req.Signature)
|
var verified bool
|
||||||
if strings.HasPrefix(sigHex, "0x") || strings.HasPrefix(sigHex, "0X") {
|
var verifyErr error
|
||||||
sigHex = sigHex[2:]
|
|
||||||
}
|
switch chainType {
|
||||||
sig, err := hex.DecodeString(sigHex)
|
case "ETH":
|
||||||
if err != nil || len(sig) != 65 {
|
// EVM personal_sign verification of the nonce
|
||||||
writeError(w, http.StatusBadRequest, "invalid signature format")
|
// Hash: keccak256("\x19Ethereum Signed Message:\n" + len(nonce) + nonce)
|
||||||
|
msg := []byte(req.Nonce)
|
||||||
|
prefix := []byte("\x19Ethereum Signed Message:\n" + strconv.Itoa(len(msg)))
|
||||||
|
hash := ethcrypto.Keccak256(prefix, msg)
|
||||||
|
|
||||||
|
// Decode signature (expects 65-byte r||s||v, hex with optional 0x)
|
||||||
|
sigHex := strings.TrimSpace(req.Signature)
|
||||||
|
if strings.HasPrefix(sigHex, "0x") || strings.HasPrefix(sigHex, "0X") {
|
||||||
|
sigHex = sigHex[2:]
|
||||||
|
}
|
||||||
|
sig, err := hex.DecodeString(sigHex)
|
||||||
|
if err != nil || len(sig) != 65 {
|
||||||
|
writeError(w, http.StatusBadRequest, "invalid signature format")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Normalize V to 0/1 as expected by geth
|
||||||
|
if sig[64] >= 27 {
|
||||||
|
sig[64] -= 27
|
||||||
|
}
|
||||||
|
pub, err := ethcrypto.SigToPub(hash, sig)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusUnauthorized, "signature recovery failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
addr := ethcrypto.PubkeyToAddress(*pub).Hex()
|
||||||
|
want := strings.ToLower(strings.TrimPrefix(strings.TrimPrefix(req.Wallet, "0x"), "0X"))
|
||||||
|
got := strings.ToLower(strings.TrimPrefix(strings.TrimPrefix(addr, "0x"), "0X"))
|
||||||
|
if got != want {
|
||||||
|
writeError(w, http.StatusUnauthorized, "signature does not match wallet")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
verified = true
|
||||||
|
|
||||||
|
case "SOL":
|
||||||
|
// Solana uses Ed25519 signatures
|
||||||
|
// Signature is base64-encoded, public key is the wallet address (base58)
|
||||||
|
|
||||||
|
// Decode base64 signature (Solana signatures are 64 bytes)
|
||||||
|
sig, err := base64.StdEncoding.DecodeString(req.Signature)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid base64 signature: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(sig) != 64 {
|
||||||
|
writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid signature length: expected 64 bytes, got %d", len(sig)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode base58 public key (Solana wallet address)
|
||||||
|
// Using a simple base58 decoder
|
||||||
|
pubKeyBytes, err := base58Decode(req.Wallet)
|
||||||
|
if err != nil {
|
||||||
|
writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid wallet address: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(pubKeyBytes) != 32 {
|
||||||
|
writeError(w, http.StatusBadRequest, fmt.Sprintf("invalid public key length: expected 32 bytes, got %d", len(pubKeyBytes)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify Ed25519 signature
|
||||||
|
message := []byte(req.Nonce)
|
||||||
|
if !ed25519.Verify(ed25519.PublicKey(pubKeyBytes), message, sig) {
|
||||||
|
writeError(w, http.StatusUnauthorized, "signature verification failed")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
verified = true
|
||||||
|
|
||||||
|
default:
|
||||||
|
writeError(w, http.StatusBadRequest, fmt.Sprintf("unsupported chain type: %s (must be ETH or SOL)", chainType))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Normalize V to 0/1 as expected by geth
|
|
||||||
if sig[64] >= 27 {
|
if !verified {
|
||||||
sig[64] -= 27
|
writeError(w, http.StatusUnauthorized, fmt.Sprintf("signature verification failed: %v", verifyErr))
|
||||||
}
|
|
||||||
pub, err := ethcrypto.SigToPub(hash, sig)
|
|
||||||
if err != nil {
|
|
||||||
writeError(w, http.StatusUnauthorized, "signature recovery failed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
addr := ethcrypto.PubkeyToAddress(*pub).Hex()
|
|
||||||
want := strings.ToLower(strings.TrimPrefix(strings.TrimPrefix(req.Wallet, "0x"), "0X"))
|
|
||||||
got := strings.ToLower(strings.TrimPrefix(strings.TrimPrefix(addr, "0x"), "0X"))
|
|
||||||
if got != want {
|
|
||||||
writeError(w, http.StatusUnauthorized, "signature does not match wallet")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1025,3 +1086,41 @@ func (g *Gateway) logoutHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
writeError(w, http.StatusBadRequest, "nothing to revoke: provide refresh_token or all=true")
|
writeError(w, http.StatusBadRequest, "nothing to revoke: provide refresh_token or all=true")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// base58Decode decodes a base58-encoded string (Bitcoin alphabet)
|
||||||
|
// Used for decoding Solana public keys (base58-encoded 32-byte ed25519 public keys)
|
||||||
|
func base58Decode(encoded string) ([]byte, error) {
|
||||||
|
const alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||||
|
|
||||||
|
// Build reverse lookup map
|
||||||
|
lookup := make(map[rune]int)
|
||||||
|
for i, c := range alphabet {
|
||||||
|
lookup[c] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to big integer
|
||||||
|
num := big.NewInt(0)
|
||||||
|
base := big.NewInt(58)
|
||||||
|
|
||||||
|
for _, c := range encoded {
|
||||||
|
val, ok := lookup[c]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid base58 character: %c", c)
|
||||||
|
}
|
||||||
|
num.Mul(num, base)
|
||||||
|
num.Add(num, big.NewInt(int64(val)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to bytes
|
||||||
|
decoded := num.Bytes()
|
||||||
|
|
||||||
|
// Add leading zeros for each leading '1' in the input
|
||||||
|
for _, c := range encoded {
|
||||||
|
if c != '1' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
decoded = append([]byte{0}, decoded...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return decoded, nil
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user