ETH use golang sign Transaction offline
构造交易
import (
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/common/hexutil"
)
const (
GAS_LIMIT = 21000
GAS_PRICE = 500000000000
)
tx := types.NewTransaction(nonce, common.HexToAddress(toAddress), value, GAS_LIMIT, big.NewInt(GAS_PRICE), nil)
其中 nonce 是该地址之前发出交易的总笔数,比如你发出17笔交易,那第一笔nonce是0,第十七笔nonce是16,所以当前要想发第十八笔 nonce 就是17,最后一个 nil 的参数 data 是 ETH 中的 input(message) 当前是普通的 eth 交易并且不需要携带消息内容所以是空,注意 ERC20 的交易这里 data 有不同的含义,下面👇会介绍
签名交易
其中 StringToPrivateKey
是获取私钥的方法,在下边会有介绍
func SignTransaction(tx *types.Transaction, privateKeyStr string) (string, error) {
privateKey, err := StringToPrivateKey(privateKeyStr)
if err != nil {
return "", err
}
signTx, err := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(4)), privateKey)
//signTx, err := types.SignTx(tx, types.HomesteadSigner{}, privateKey)
if err != nil {
return "", nil
}
b, err := rlp.EncodeToBytes(signTx)
if err != nil {
return "", err
}
return hex.EncodeToString(b), nil
}
其中签名有两种算法
- types.NewEIP155Signer(big.NewInt(chainId))
- types.HomesteadSigner{}
选一种使用就好,第二种不需要提供 chainId
但是据说不稳定,types.NewEIP155Signer(big.NewInt(4)
4 是 rinkeby 测试网络,1是主网
私钥的获取 *ecdsa.PrivateKey
- 从明文的私钥字符串转换成该类型
func StringToPrivateKey(privateKeyStr string) (*ecdsa.PrivateKey, error) {
privateKeyByte, err := hexutil.Decode(privateKeyStr)
if err != nil {
return nil, err
}
privateKey, err := crypto.ToECDSA(privateKeyByte)
if err != nil {
return nil, err
}
return privateKey, nil
}
- keystore + password 解析出私钥
func KeystoreToPrivateKey(keystoreContent []byte, password string) (*ecdsa.PrivateKey, error) {
unlockedKey, err := keystore.DecryptKey(keystoreContent, password)
if err != nil {
return nil, err
}
return unlockedKey.PrivateKey, nil
}
ERC20 交易签名
ERC20 的交易和普通 ETH 的 Transaction 结构交易基本是相同的,只有 data 字段 ERC20 是有规定的,因为是要调用合约方法,根据规定需要获取 transfer 方法的16进制前部分,这个ERC20标准规定的,所有合约TRANSFER_METHOD_ID
都是固定的,包括目标地址的补 0 ,按顺序拼接到一起。
const (
TRANSFER_METHOD_ID = "0xa9059cbb"
)
func MakeERC20TransferData(toAddress string, amount *big.Int) ([]byte, error) {
var data []byte
methodId, err := hexutil.Decode(TRANSFER_METHOD_ID)
if err != nil {
return methodId, err
}
data = append(data, methodId...)
paddedAddress := common.LeftPadBytes(common.HexToAddress(toAddress).Bytes(), 32)
data = append(data, paddedAddress...)
paddedAmount := common.LeftPadBytes(amount.Bytes(), 32)
data = append(data, paddedAmount...)
return data, nil
}
只需要组织一个新的交易结构体
data, err := MakeERC20TransferData(toAddress, big.NewInt(1000000000))
if err != nil {
t.Error(err)
}
tx := eth_client.NewTransaction(uint64(nonce), toContractAddress, big.NewInt(0), data)
其中转账目标是合约地址,转账金额一般是0,再将生成的 tx 结构带入 SignTransaction
方法就可以返回签名的交易哈希
验证交易
首先需要我们在测试网络上发一个代币来验证这里的转账签名是否成功,发币请看 ERC20 代币发行,拿到发币的账户地址和私钥以及对应的合约地址之后带入函数执行即可