Go语言实现solidity部分接口

admin
admin 2022年01月02日
  • 在其它设备中阅读本文章

打包数据

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