solidity“abi.encode/abi.encodePacked”使用golang编码

uniswap v2 erc20合约中有一个预授权功能,也就是链下签名链上验证,授权方法如下:

    function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
        require(deadline >= block.timestamp, 'UniswapV2: EXPIRED');
        bytes32 digest = keccak256(
            abi.encodePacked(
                '\x19\x01',
                DOMAIN_SEPARATOR,
                keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline))
            )
        );
        address recoveredAddress = ecrecover(digest, v, r, s);
        require(recoveredAddress != address(0) && recoveredAddress == owner, 'UniswapV2: INVALID_SIGNATURE');
        _approve(owner, spender, value);
    }

里面在对签名数据进行编码时用到了abi.encode/abi.encodePacked两种方法,查阅solitidy文档得知两种方法区别在于:abi.encode 编码的数据需要32字节对齐,abi.encodePacked对小于32字节的数据不补0,以及其他一些区别:https://solidity.readthedocs.io/en/v0.7.3/abi-spec.html#abi-packed-mode

使用golang实现如下

// 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),// 不足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))),//不足32字节 不补齐
        hash.Bytes(),
    )

最后,使用golang写了一个UNISWAP合约中相关计算的例子。


const (
	// keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)')
	EIP712DomainHash = "0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f"
	// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)");
	PERMIT_TYPEHASH = "0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9"
)

func calcDigest(verifyingContract, owner, spender ethcmn.Address, value *big.Int, nonce, deadline int64) ethcmn.Hash {
	return crypto.Keccak256Hash(
		[]byte("\x19\x01"),
		calcDomainSeparatorHash(verifyingContract).Bytes(),
		calcDigestPermitFuncHash(owner, spender, value, nonce, deadline).Bytes(),
	)
}

func TestCalcDigest(t *testing.T) {
	digest := calcDigest(ethcmn.HexToAddress("0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"),
		ethcmn.HexToAddress("0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"),
		ethcmn.HexToAddress("0x5B38Da6a701c568545dCfcB03FcB875f56beddC4"),
		big.NewInt(123),
		1,
		1600000000)
	fmt.Println(digest.Hex())
}

func calcDomainSeparatorHash(verifyingContract ethcmn.Address) ethcmn.Hash {
	nameHash := crypto.Keccak256Hash([]byte("Uniswap V2")) //0xbfcc8ef98ffbf7b6c3fec7bf5185b566b9863e35a9d83acd49ad6824b5969738
	versionHash := crypto.Keccak256Hash([]byte("1"))       //0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6
	chainId := big.NewInt(1).Bytes()
	return crypto.Keccak256Hash(
		ethcmn.HexToHash(EIP712DomainHash).Bytes(),
		nameHash.Bytes(),
		versionHash.Bytes(),
		ethcmn.LeftPadBytes(chainId, 32),
		ethcmn.LeftPadBytes(verifyingContract.Bytes(), 32),
	)
}

func calcDigestPermitFuncHash(owner, spender ethcmn.Address, value *big.Int, nonce int64, deadline int64) ethcmn.Hash {
	return crypto.Keccak256Hash(
		ethcmn.HexToHash(PERMIT_TYPEHASH).Bytes(),
		ethcmn.LeftPadBytes(owner.Bytes(), 32),
		ethcmn.LeftPadBytes(spender.Bytes(), 32),
		ethcmn.LeftPadBytes(value.Bytes(), 32),
		ethcmn.LeftPadBytes(big.NewInt(nonce).Bytes(), 32),
		ethcmn.LeftPadBytes(big.NewInt(deadline).Bytes(), 32),
	)
}

posted on 2020-10-21 15:40  angry-baby  阅读(6104)  评论(0编辑  收藏  举报

导航