Ethers.js: SolidityKeccak256 without tightly packing

Created on 12 Aug 2020  路  5Comments  路  Source: ethers-io/ethers.js

I have an ABIEncoderV2 struct that holds 3 arrays as such

    struct Update {
        address[] target;
        uint256[] value;
        bytes[] data;
    }

We hash this struct but these types can't be packed by the ABI, but I still need to get the hash of said struct in JS.
bytes32 updateHash = keccak256(abi.encode(_update));

since solidityKeccak256(types, values) tightly packs the data I can't use that , is there an alternative that doesn't pack the data?

i.e. utils.solidityKeccak256(["address[], uint256[], bytes[]"], [update.target, update.value, update.data]) would not work due to reasons mentioned. I think it would even throw an error IIUC.

Most helpful comment

You need to use the normal encoder. You are correct, the solidity* functions are only for tightly packed.

To hash normally, use utils.defaultAbiCoder.encode([ "tuple(address[] target, uint[] value, bytes[] data)" ], [ objectHere ]) and you can then just use the normal utils.keccak256 on that. This should match the abi.encode in Solidity. :)

One quick note about your API, if you allow calls that use ecrecover to establish the from, you likely want to include a nonce as well to prevent replaying. You may already have other protection from this, but just in case; for delegated transactions that is often overlooked. :)

All 5 comments

You need to use the normal encoder. You are correct, the solidity* functions are only for tightly packed.

To hash normally, use utils.defaultAbiCoder.encode([ "tuple(address[] target, uint[] value, bytes[] data)" ], [ objectHere ]) and you can then just use the normal utils.keccak256 on that. This should match the abi.encode in Solidity. :)

One quick note about your API, if you allow calls that use ecrecover to establish the from, you likely want to include a nonce as well to prevent replaying. You may already have other protection from this, but just in case; for delegated transactions that is often overlooked. :)

Thanks for the quick reply !

One quick note about your API, if you allow calls that use ecrecover to establish the from

We currently do address based authentication for these calls , not signer based authentication. The sender will be a multisig in production. Our replay protection involves the updateHash mapped to a block in the future , if an external call for Update takes place this value gets removed and we require the value to not be null to actually make the external call.

@kyriediculous I'm trying to do the same thing, upgrading a contract from using encodePacked to using encode. The documentation isn't very clear on how to accomplish the same thing as abi.encode with ether.js, so i landed here. I've used your example with the "tuple(..." string, and it almost does it right, but seems to be adding an extra "0000000000000000000000000000000000000000000000000000000000000020" to the front of the encoded value.

Here is my Solidity code:

bytes memory abiEncoded = abi.encode(
    _peggyId,
    methodName,
    _valsetNonce,
    _validators,
    _powers
);

and my JS

let abiEncoded = ethers.utils.defaultAbiCoder.encode(
    [
      "tuple(bytes32 peggyId, bytes32 methodName, uint256 valsetNonce, address[] validators, uint256[] powers)"
    ],
    [[peggyId, methodName, valsetNonce, validators, powers]]
  )

In your case, that isn鈥檛 a tuple, I don鈥檛 think? Try using it as a non-tuple:

let abiEncoded = ethers.utils.defaultAbiCoder.encode(
    [
      "bytes32", "bytes32", "uint256", "address[]", "uint256[]"
    ],
    [peggyId, methodName, valsetNonce, validators, powers]
  )

Does that work?

Ah, that works.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dagogodboss picture dagogodboss  路  3Comments

naddison36 picture naddison36  路  3Comments

ricmoo picture ricmoo  路  3Comments

moshebeeri picture moshebeeri  路  3Comments

zemse picture zemse  路  3Comments