How can I get the same return value as solidity `abi.encodePacked` in Golang
Asked Answered
J

2

13

How can i run abi.encodePacked in golang?

in solidity i use keccak256(abi.encodePacked(a, b)) to calc the signature of the params.

here is my contract.

pragma solidity ^0.4.24;

import "openzeppelin-solidity/contracts/ECRecovery.sol";


contract MyContract {
    using ECRecovery for bytes32;
    address permittedSinger;

    function doSomething(
    bytes32 id, uint256 amount, bytes sig
    ) {
        bytes32 hash = getHash(msg.sender, id, amount);
        address msgSigner = hash.recover(sig);
        require(msgSigner == permittedSinger);
    }

    function getMsgSigner(bytes32 proveHash, bytes sig) public pure returns (address) {
        return proveHash.recover(sig);
    }

    function getHash(
    address receiver, bytes32 id, uint256 amount
    ) pure returns (bytes32) {
        return keccak256(abi.encodePacked(receiver, id, amount));
    }
}
Jephthah answered 9/6, 2018 at 9:29 Comment(4)
No, I need to build some params in Golang. If someone call the contract, I want to check these params are valid. So i have to calculate the signature of the params in Golang, then check it in Contract.Jephthah
look here go-ethereum source codeMccowan
The easiest thing to do would just be to call the smart contract's getHash from your Go code.Fifi
@ChihebNexus thanksJephthah
J
11

finally I managed to do it. :)

package main

import (
    "math/big"
    "github.com/ethereum/go-ethereum/common/hexutil"
    "github.com/ethereum/go-ethereum/accounts/abi"
    "log"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/crypto/sha3"
)

func main() {
    uint256Ty, _ := abi.NewType("uint256")
    bytes32Ty, _ := abi.NewType("bytes32")
    addressTy, _ := abi.NewType("address")

    arguments := abi.Arguments{
        {
            Type: addressTy,
        },
        {
            Type: bytes32Ty,
        },
        {
            Type: uint256Ty,
        },
    }

    bytes, _ := arguments.Pack(
        common.HexToAddress("0x0000000000000000000000000000000000000000"),
        [32]byte{'I','D','1'},
        big.NewInt(42),
    )

    var buf []byte
    hash := sha3.NewKeccak256()
    hash.Write(bytes)
    buf = hash.Sum(buf)

    log.Println(hexutil.Encode(buf))
    // output:
    // 0x1f214438d7c061ad56f98540db9a082d372df1ba9a3c96367f0103aa16c2fe9a
}
Jephthah answered 9/6, 2018 at 15:45 Comment(3)
Unfortunately, it doesn't work the same for dynamic length types (like bytes). Go's arguments.Pack will return what you get from abi.encode not abi.encodePacked.Trapan
go-ethereum 1.9.7 has a few more args in abi.NewType. The above still work with abi.NewType("uint256", "uint256", nil)Pastorship
what if an array like two addresses?Shalloon
H
5

As Jakub N is said in comments to accepted answer, Go's arguments.Pack returns as abi.encode and not abi.encodePacked. In your case it works because all packed values are 32 bytes, but if you also add some strings then the result will be different.

Here is how to do it to be compatible with tightly packed encoding corresponding to abi.encodePacked:


// hash of packed byte array with arguments

hash := crypto.Keccak256Hash(
        common.HexToAddress("0x0000000000000000000000000000000000000000").Bytes(),
        [32]byte{'I','D','1'},
        common.LeftPadBytes(big.NewInt(42).Bytes(), 32),
        []byte("Some other string value"),
    )

// normally we sign prefixed hash
// as in solidity with `ECDSA.toEthSignedMessageHash`

prefixedHash := crypto.Keccak256Hash(
        []byte(fmt.Sprintf("\x19Ethereum Signed Message:\n%v", len(hash))),
        hash.Bytes(),
    )

// sign hash to validate later in Solidity

sig, err := crypto.Sign(prefixedHash.Bytes(), privateKey)

It is also more efficient as we don't pack and allocate additional memory for that. Hash function iterates over existing values.

Headon answered 29/1, 2020 at 10:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.