Go语言实现solidity部分接口
打包数据
abi.encode
打包数据时每个变量都补齐到 32 字节大小,比如说 address 类型的变量,需要在前面补 12 个 0 字节来凑齐 32 字节,而abi.encodePacked
接口不需要,直接根据变量所占字节大小按顺序拼接成字节数组即可。
solidity 语言:
function abiEncode() public pure returns (bytes memory) {
address a = 0x1000000000000000000000000000007000006000;
bytes32 b = "hello" ;
return abi.encode(a, b);
}
function abiEncodePacked() public pure returns (bytes memory) {
address a = 0x1000000000000000000000000000007000006000;
bytes32 b = "hello" ;
return abi.encodePacked(a, b);
}
go 语言:
import (
"fmt"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
func abiEncode() {
addressTy, _ := abi.NewType("address", "", nil)
bytes32Ty, _ := abi.NewType("bytes32", "", nil)
arguments := abi.Arguments{
{
Type: addressTy,
},
{
Type: bytes32Ty,
},
}
bytes, _ := arguments.Pack(
common.HexToAddress("0x1000000000000000000000000000007000006000"),
[32]byte{'h', 'e', 'l', 'l', 'o'},
)
fmt.Println(hexutil.Encode(bytes))
}
func abiEncodePacked() {
bytes = append(
common.HexToAddress("0x1000000000000000000000000000007000006000").Bytes(),
common.RightPadBytes([]byte{'h', 'e', 'l', 'l', 'o'}, 32)...,
)
fmt.Println(hexutil.Encode(bytes))
}
输出:
0x000000000000000000000000100000000000000000000000000000700000600068656c6c6f000000000000000000000000000000000000000000000000000000
0x100000000000000000000000000000700000600068656c6c6f000000000000000000000000000000000000000000000000000000
哈希数据:
solidity 使用 keccak256 接口将字节数组来生成 32 字节的哈希,部分前端 js 库在签名或哈希时自动在数据前加特殊的前缀,为了使 solidity 计算带前缀的哈希方便,将数据编码并哈希成 32 字节后再加前缀来计算哈希(这时前缀固定为\x19Ethereum Signed Message:\n32
)
solidity 语言:
function hash() public pure returns (bytes32) {
address a = 0x1000000000000000000000000000007000006000;
bytes32 b = "hello" ;
return keccak256(abi.encodePacked(a,b));
}
function prefixedHash() public pure returns (bytes32) {
address a = 0x1000000000000000000000000000007000006000;
bytes32 b = 'hello';
return keccak256(abi.encodePacked('\x19Ethereum Signed Message:\n32', keccak256(abi.encodePacked(a, b))));
}
go 语言:
import (
"fmt"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
)
func hashAndPrefixed() {
hash := crypto.Keccak256Hash(
common.HexToAddress("0x1000000000000000000000000000007000006000").Bytes(),
common.RightPadBytes([]byte{'h', 'e', 'l', 'l', 'o'}, 32),
)
fmt.Println(hash)
prefixedHash := crypto.Keccak256Hash(
[]byte("\x19Ethereum Signed Message:\n32"),
hash.Bytes(),
)
fmt.Println(prefixedHash)
}
输出:
0xd91dd3198b92de9e6c8678e55c5b62c026dce4d101c1b4bf2de31878c3bc08ba
0x589fec5d88151e9489222a3234a28adf42c0f640bba53cce3329c06d014f2237
签名生成与验证
使用crypto.Sign(digestHash, privateKey)
接口来生成签名(65 字节),使用crypto.SigToPub(hash, sig)
接口恢复签名的公钥,使用crypto.PubkeyToAddress(*rpk)
接口获取公钥对应的地址,对比恢复出来的地址和签名的账户地址一致来验证签名。使用crypto.HexToECDSA(hexkey)
接口将私钥字符串实列化为可使用私钥对象。
注:使用crypto.SigToPub
接口时,如果签名的最后一个字节大于等于 27,需要减去 27;需要引用github.com/ethereum/go-ethereum/crypto
包