package service import ( "encoding/hex" "errors" "fmt" "strconv" "strings" "github.com/decred/dcrd/dcrec/secp256k1/v4/ecdsa" "golang.org/x/crypto/sha3" ) // 以太坊 personal_sign(EIP-191)验签工具:仅覆盖 EOA 签名恢复, // 不做 EIP-1271 合约钱包(需链上 RPC,超出自托管零依赖边界)。 // errEthSig 是签名格式/恢复失败的内部标记;对外统一映射 ErrWalletSig。 var errEthSig = errors.New("invalid ethereum signature") // keccak256 计算 Keccak-256 摘要(以太坊场景的非 NIST 填充变体)。 func keccak256(data ...[]byte) []byte { h := sha3.NewLegacyKeccak256() for _, d := range data { h.Write(d) } return h.Sum(nil) } // personalSignDigest 计算 EIP-191 personal_sign 摘要: // keccak256("\x19Ethereum Signed Message:\n" + 消息字节长度十进制 + 消息)。 func personalSignDigest(msg []byte) []byte { prefix := []byte("\x19Ethereum Signed Message:\n" + strconv.Itoa(len(msg))) return keccak256(prefix, msg) } // recoverEthAddress 从 65 字节签名(r||s||v)恢复签名者地址(EIP-55 格式)。 // v 兼容钱包生态的 27/28 与原始 0/1 两种取值;dcrec 的 compact 格式要求 // 恢复位打头(27+v),与以太坊的尾置布局相反,需重排。 func recoverEthAddress(digest, sig []byte) (string, error) { if len(sig) != 65 { return "", fmt.Errorf("%w: length %d", errEthSig, len(sig)) } v := sig[64] if v >= 27 { v -= 27 } if v > 1 { return "", fmt.Errorf("%w: recovery id %d", errEthSig, sig[64]) } compact := make([]byte, 65) compact[0] = 27 + v copy(compact[1:], sig[:64]) pub, _, err := ecdsa.RecoverCompact(compact, digest) if err != nil { return "", fmt.Errorf("%w: %v", errEthSig, err) } // 地址 = keccak256(未压缩公钥去掉 0x04 前缀)的后 20 字节 return eip55(keccak256(pub.SerializeUncompressed()[1:])[12:]), nil } // eip55 把 20 字节地址编码为 EIP-55 混合大小写校验和格式: // 对小写 hex 串取 keccak256,对应半字节 ≥ 8 的字母转大写。 func eip55(addr []byte) string { lower := hex.EncodeToString(addr) sum := keccak256([]byte(lower)) out := []byte(lower) for i, ch := range out { nibble := sum[i/2] >> 4 if i%2 == 1 { nibble = sum[i/2] & 0x0f } if ch >= 'a' && nibble >= 8 { out[i] = ch - 'a' + 'A' } } return "0x" + string(out) } // normalizeEthAddress 校验 0x+40hex 地址并返回 EIP-55 规范形;大小写不敏感。 func normalizeEthAddress(addr string) (string, error) { if len(addr) != 42 || !strings.HasPrefix(addr, "0x") { return "", ErrWalletAddress } raw, err := hex.DecodeString(strings.ToLower(addr[2:])) if err != nil { return "", ErrWalletAddress } return eip55(raw), nil }