Metamask签名验证:Remix中ecrecover正常,Go-web3中失败
问题根源分析
你的问题核心在于字符串到以太坊原生类型的转换逻辑错误,尤其是bytes32类型的解码和v值的处理,导致合约接收到的参数和签名时的原始数据不匹配,最终恢复出错误地址。
1. bytes32参数转换错误
你直接将十六进制字符串(如0x...)用[]byte()转换为字节数组,这是错误的——[]byte("0xabc")会把每个字符(包括0、x、a等)转成对应的ASCII字节,而非把十六进制字符串解码为32字节的哈希值。正确做法是用hex.DecodeString先去掉0x前缀,再解码为字节数组。
2. v值的溢出与格式转换问题
MetaMask使用EIP-155签名时(对应Avax测试网ChainID 43113),返回的v值是chainId*2 + 35或chainId*2 + 36(即86261或86262),你直接转为uint8会导致数值溢出(uint8最大值为255)。另外Solidity的ecrecover要求v值为27或28,需先将EIP-155格式的v转换为标准格式。
3. 冗余代码清理
调用pure类型的合约函数无需设置账号和ChainID,这些代码完全多余,应删除。
修正后的Go代码
import ( "encoding/hex" "strconv" "strings" "log" "github.com/ethereum/go-ethereum/common" ) func VerifySignature(_data global.SignatureData, walletAddress string) bool { web3, err := web3.NewWeb3(global.AvaxRpcURL) if err != nil { log.Println(err) return false } tokenAddr := "0xF686F5D7165e8Ce1C606978F424a2DBd4a37e122" contract, err := web3.Eth.NewContract(constants.VerificationABI, tokenAddr) if err != nil { log.Println(err) return false } // 处理HashedMessage:解码十六进制字符串为bytes32 msgHex := strings.TrimPrefix(_data.HashedMessage, "0x") msgBytes, err := hex.DecodeString(msgHex) if err != nil || len(msgBytes) != 32 { log.Println("invalid hashed message") return false } msg := [32]byte{} copy(msg[:], msgBytes) // 处理v值:转换为EIP-155标准格式的27/28 vInt, err := strconv.Atoi(_data.V) if err != nil { log.Println(err) return false } avaxTestnetChainId := 43113 standardV := vInt - 2*avaxTestnetChainId - 35 if standardV != 27 && standardV != 28 { log.Println("invalid v value") return false } v := uint8(standardV) // 处理r值:解码十六进制字符串为bytes32 rHex := strings.TrimPrefix(_data.R, "0x") rBytes, err := hex.DecodeString(rHex) if err != nil || len(rBytes) != 32 { log.Println("invalid r value") return false } r := [32]byte{} copy(r[:], rBytes) // 处理s值:解码十六进制字符串为bytes32 sHex := strings.TrimPrefix(_data.S, "0x") sBytes, err := hex.DecodeString(sHex) if err != nil || len(sBytes) != 32 { log.Println("invalid s value") return false } s := [32]byte{} copy(s[:], sBytes) signer, err := contract.Call("VerifyMessage", msg, v, r, s) if err != nil { log.Println(err) return false } // 统一地址大小写避免匹配失败 return strings.ToLower(signer.(common.Address).String()) == strings.ToLower(walletAddress) }
额外注意事项
你的Solidity合约存在逻辑漏洞:标准以太坊消息签名会自动添加"\x19Ethereum Signed Message:\n32"前缀,当前合约仅对_hashedMessage做哈希,未拼接前缀,若MetaMask/web3js是按标准流程签名,需修正Solidity代码:
contract Verify { function VerifyMessage(bytes32 _hashedMessage, uint8 _v, bytes32 _r, bytes32 _s) public pure returns (address) { bytes memory prefix = "\x19Ethereum Signed Message:\n32"; // 拼接前缀后再哈希 bytes32 prefixedHashMessage = keccak256(abi.encodePacked(prefix, _hashedMessage)); address signer = ecrecover(prefixedHashMessage, _v, _r, _s); return signer; } }
内容的提问来源于stack exchange,提问作者Thomas Parker
相关产品推荐
相关产品推荐

