90 lines
2.6 KiB
Go
90 lines
2.6 KiB
Go
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
|
|
}
|