near / nearcore

Reference client for NEAR Protocol
https://near.org
GNU General Public License v3.0
2.31k stars 618 forks source link

Fix and impl type parsing of eip-712 for near evm #3554

Closed ailisp closed 3 years ago

ailisp commented 3 years ago

Linked to meta-tx tracking issue: https://github.com/near/nearcore/issues/3338 From Illia:

You should start by understanding the spec for EIP-712 - the easiest way is to grab that JSON that signTypedData is providing and plug it into eip-712 cargo. And then step through the debugger.Secifcially:

we need to pass in method name, not just adopt(uint128) but more like adopt(uint128 petId)  also if there are subtypes in arguments - they need to be there as well in the form adopt(SomeType x)SomeType(uint256 arg1, string x)
need to parse that and represent in the current text NearTx  into NearTx(…)Arguments(uint128 petId)
make sure all strings and subtypes are hashed
ailisp commented 3 years ago

i thoroughly understand eip-712 and able to correctly construct msg and sig with eip-712 now. Next step is parsing type params to do eip-712 basd on any input. will do after #3552

ailisp commented 3 years ago

parsing method name is done. however, it's still unfinished on how evm function arg is encoded. It must be in a way that not lost any information (not eip-712) such as rlp or borsh. I'll deserialize evm function args (rlp or borsh), then i can do a programmatic eip-712 enocding of the structured args. Then if it were rlp, can be directly pass to evm runner (plus the eip-712 sig). If it were borsh, re-encode it into rlp then to evm runner

ailisp commented 3 years ago

We discussed and conclusion: it's not required for our rust code to decode evm function arg encoded in rlp, and do a eip-712 encoding according to the structure of function arg. So, just review and merge will close this. @djsatok

Note the whole process of a eip-712 encoding a meta txn is:

  1. decode evm function args in rlp, this is going to be passed to evm runner as a function args of a contract call. evm crate we used (openethereum) knows how to decode and interpret this data. For nearcore code we treat it as an opaque u8 array.
  2. based on the structure of 1., doing eip-712 hash of the function args.
  3. (implemented in #3704) given eip-712 hashed function args, eip-712 hash the whole meta txn, produce msg and sig.
  4. (implemented in #3704 ) test msg and sig in 3. is correctly produced as metamask did, and test ecrecover is correct

For 1. and 2. we only used in test, so in #3704 i did is manually take "decoded function args" (since in specific test you know what is the specific signature and arguments), use utility function implemented in #3704 to do a manual eip-712 encoding. Therefore, for our use case of eip-712 in nearcore, it's sufficient.

ailisp commented 3 years ago

@evgenykuzyakov during doing refactor and comment #3704, i found i have to do 1-2 in rust, otherwise the usage of parse_meta_call is incorrect:


/// Format
...
/// 117..: RLP encoded rest of arguments. 
pub fn parse_meta_call(

(These numbers are wrong, should be 170, etc.)

Then in pub fn parse_meta_call:

    let args = &args[170 + method_name_len..];

    let msg = prepare_meta_call_args(
        domain_separator,
        account_id,
        nonce,
        fee_amount,
        fee_address,
        contract_address,
        &method_name,
        args,
    )?;

    let input = [method_name_rlp.to_vec(), args.to_vec()].concat();
    Ok(MetaCallArgs { sender, nonce, fee_amount, fee_address, contract_address, input })

so this requires args to be rlp encoded, to not lost information passed as function args to evm. but, in fn prepare_meta_call_args, it assumes args is already eip-712 hashed according to the function signature, and only in this case it eip-712 entire meta txn correctly and pass tests. So, I still need to do 1,2 mentioned above programmatically in prepare_meta_call_args, otherwise parse_meta_calls is useless since it's passing eip-712 hashed function args to evm runner (lost actual args, e.g.:

vec![
            u256_to_arr(&U256::from(9)).to_vec(),
            keccak(
                &vec![
                    encode_string("PetObj(string petName,address owner)"),
                    encode_string("CapsLock"),
                    encode_address(Address::from_slice(
                        &hex::decode("0123456789012345678901234567890123456789").unwrap(),
                    )),
                ]
                .concat(),
            )
            .as_bytes()
            .to_vec(),
        ]