-
Notifications
You must be signed in to change notification settings - Fork 75
feat: eip-712 support #222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
ACX-590 Add support for EIP712
Instead of a cryptic bytestring, the user would be presented with a human-readable data before signing the message. https://eips.ethereum.org/EIPS/eip-712 We should only implement this if there a tested library, for example one from OZ we can use. If we had to roll our own implementation then the risk wouldn't be worth it IMO |
adfcd42 to
0a407b2
Compare
contracts/EIP712CrossChain.sol
Outdated
| bytes32 private immutable _HASHED_VERSION; | ||
| bytes32 private immutable _TYPE_HASH; | ||
| // NOTE: We use a hardcoded address to allow cross-chain verification | ||
| address public immutable _VERIFYING_CONTRACT = 0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would we need to change this in production? Isn't this contract the destination SpokePool's address?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is again the same problem as with the chain id. Because this value is part of the domain separator, we either need to hardcode it across all chains / spoke pool contracts OR make it also a parameter. I decided to hardcode it because:
- The spec defines this only with the keyword may, so my interpretation is that this is optional. See https://eips.ethereum.org/EIPS/eip-712#definition-of-domainseparator
- If this would be a parameter, then the function signatures of the methods
speedUpDepositandfillRelayWithUpdatedFeewould need to change. Because we would need to somehow pass the address of the respective spoke pool (same value for corresponding chains)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmmm this is an interesting point @dohaki . So one way around this is to just hardcode the verifying contract so that its essentially ignored. Does that break wallet clients ability to parse this message? I just don't want them to reject it because the verifyingContract isn't actually the contract that is verifying it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed offline, it seems that some of the fields like verifyingContract are optional in the EIP so we don't need to use that one. I still think we should keep chainId = originChainId. If we remove verifyingContract i don't think we expose ourselves to any replay attacks where someone submits the signature to a different spoke pool on the originChain. The SpokePools now allow fills to be paused including the fillRelayWithUpdatedFee so that will revert on deprecated SpokePools. Additionally, even if the fillRelayWithUpdatedFee succeeds, the relayer will note receive a refund if the SpokePool isn't connected to the HubPool anymore.
nicholaspai
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some high level comments !
8be9cc1 to
e8a9c8d
Compare
| originChainId, | ||
| }, | ||
| }; | ||
| const signature = await depositor._signTypedData(typedData.domain, typedData.types, typedData.message); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- This is how we'd sign the speed up in the FE after this change goes through? Is there a non underscore function we can use instead?
- Do we need any additional unit tests? Like verifying that the typed data has the expected fields and can be parsed by a wallet client?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- This is how we'd sign the speed up in the FE after this change goes through? Is there a non underscore function we can use instead?
Yes, we would use this in the FE to speed up. This is just a wrapper method provided by ethers and will eventually be replaced by signTypedData without an underscore. Alternatively, we could just directly use the JSON RPC like:
const signature = await depositor.provider.send("eth_signTypedData_v4", [fromAddress, encodedMsgParams]where we need to do some additional encoding. This approach is a bit verbose though.
- Do we need any additional unit tests? Like verifying that the typed data has the expected fields and can be parsed by a wallet client?
Not sure if I understand correctly. Verifying whether the typed data has the expected fields is already covered in our tests, no? Basically, the test case is where if the signed message is not equal to the expected message then the verification fails. I am also not sure how to write a unit test on whether or not a wallet client can parse the typed data. Doesn't this depend on the implementation of the wallet? And can't we assume that if a wallet supports the method eth_signTypedData_v4 then it can parse any message supported by the spec?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eventually be replaced by signTypedData without an underscore
Any idea when this is going to happen? Not that it blocks us i'm just curious what's holding this up
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Verifying whether the typed data has the expected fields is already covered in our tests, no? Basically, the test case is where if the signed message is not equal to the expected message then the verification fails
Do we need to perform additional checks that the signed message is in the correct EIP712 format?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eventually be replaced by signTypedData without an underscore
Any idea when this is going to happen? Not that it blocks us i'm just curious what's holding this up
No idea tbh... Couldn't find anything on this in the repo.
Verifying whether the typed data has the expected fields is already covered in our tests, no? Basically, the test case is where if the signed message is not equal to the expected message then the verification fails
Do we need to perform additional checks that the signed message is in the correct EIP712 format?
Is this possible? The signature is in the end just the same bytes string as a message signed with signer.signMessage.
mrice32
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Signed-off-by: Dong-Ha Kim <dongha.kim210@gmail.com>
Signed-off-by: Dong-Ha Kim <dongha.kim210@gmail.com>
Signed-off-by: Dong-Ha Kim <dongha.kim210@gmail.com>
Signed-off-by: Dong-Ha Kim <dongha.kim210@gmail.com>
e87158f to
21aab9c
Compare
james-a-morris
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left some comments.
| keccak256( | ||
| abi.encode( | ||
| keccak256( | ||
| "UpdateRelayerFeeMessage(uint64 newRelayerFeePct,uint32 depositId,uint256 originChainId)" | ||
| ), | ||
| newRelayerFeePct, | ||
| depositId, | ||
| originChainId | ||
| ) | ||
| ), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to store some of these values in local variables with descriptive names? From a readability standpoint, there are several nested function calls that may not be entirely clear for a newcomer to the repo.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea, I agree that this is a bit nested. But tbh I don't know what to put in local variables because this hash structure is defined like that in the EIP. I could do
bytes32 hashStruct = keccak256(...);
bytes32 expectedTypedDataV4Hash = _hashTypedDataV4(hashStruct);but not sure if this makes it really more readable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a gas difference if you change the syntax? Can run the test like so REPORT_GAS=true hardhat test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nicholaspai Thanks for the suggestion. Yes, with the additional local variable the methods speedUpDeposit and fillRelayWithUpdatedFee cost 10 gas more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is an interesting case where smart contract engineering is different from off-chain engineering. Temp variables increase readability but incur a real cost! In this case I'd opt for the reduction in readability to save gas
amateima
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice work 👍
james-a-morris
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great work 🔥 Thanks for addressing my comments
|
@nicholaspai I e2e tested a deployed version of the contract with the frontend and everything seems fine 👍 When you are happy, then I can merge |
Just one comment then LGTM |
nicholaspai
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
@nicholaspai Thanks! I haven't actually put the nested stuff in a local variable. So this PR has the version with lower gas costs |
Closes ACX-590
This allows the usage of EIP-712 structured and typed data signatures for updating relayer fees. The implementation of
EIP712CrossChainis based on OZ's implementation of the EIP-712 specification but is less strict to allow the verification of signatures cross-chain.