-
Notifications
You must be signed in to change notification settings - Fork 0
Cross chain execution and payments via UserOps #1
Conversation
dbbc4c5
to
3281226
Compare
if ( | ||
validateSignature(userOpHash, ownerSig, owner) != SIG_VALIDATION_SUCCEEDED | ||
) { | ||
return SIG_VALIDATION_FAILED; |
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.
The ERC 4337 spec specifies that the wallet SHOULD return SIG_VALIDATION_FAILED (and not revert) on signature mismatch
, so I've changed this from a revert
to return SIG_VALIDATION_FAILED
81dc9f0
to
17bada2
Compare
17bada2
to
4644179
Compare
Thanks for all of the details in the PR description. It might be useful to turn this PR description into a .md file within the repo. That way the documentation is not lost when this PR is merged and it is easy to leave comments on specific parts of the writeup as we are reviewing |
|
|
||
# Multihop | ||
|
||
Since a UserOp can contain multiple `PaymentChainInfo`s and `ExecuteChainInfos`, this approach can be used for multi-hop execution or payments. We just require the `owner` and `intermediary` of every BridgeWallet involved to sign the UserOp. |
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.
When we go beyond a single hop, we have "intermediary-intermediary" channels. Is this a bit awkward with the "owner" / "intermediary" roles of the bridge wallet? Will one of the parties just arbitrarily take the role of "owner"?
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.
I think right now it might be a bit awkward, as once intermediary would play the role of the owner
and one intermediary would play the role of intermediary
in the BridgeWallet.
To solve this we could implement a specific Intermediary<->Intermediary mode (or separate contract?) for the BridgeWallet that handles the "intermediary-intermediary" use case.
|
||
It's important that we're not vulnerable to replay attacks, where a `UserOp` is submitted again using a different entrypoint or chain. | ||
|
||
If we're working on one chain, it's fairly easy to prevent replay attacks. The `userOpHash` [provided by the Entrypoint](https://github.com/magmo/Bridge-Wallet/blob/ad6d24fa2435f449751d1b61e24d12faff1f83a9/contracts/core/EntryPoint.sol#L298) to `validateUserOp` is hashed against the current chain and entrypoint. This means that if the UserOp is run against a different chain or entrypoint, there will be a different `userOpHash` causing all the signature checks to fail. |
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 feels a little suboptimal. I suppose the entrypoint has not been designed with cross chain applications in mind, meaning that we have to make multiple hashes and a signature on each one (and as you say validate all of those combinations).
A "cross chain entrypoint" would ease this, but would be a departure from 4337 norms (I guess?).
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.
With this change I don't think it's too bad. Now instead of generating a unique hash for each chain, all participants just sign the hash based on the first chain. This means we can generate one hash and check all the signatures against that.
I think it's safe to have everyone sign the same UserOpHash
since we perform a separate check on the chainId and entrypoint based on the calldata in the UserOp
. If you try submitting the UserOp, it will only work if the chain and entrypoint match a ExecuteChainInfo
or PaymentChainInfo
.
So the cost of validateCrossChain
would be generating one hash, and then checking 2 signatures for every BridgeWallet
involved. IE: If we have a multi-hop UserOp with Alice,Irene,Iris,Bob we'd expect the UserOp
to have the following signatures: [AliceSig,IreneSig,IreneSig,IrisSig,IrisSig,BobSig]
. We'd generate one hash and check the6 signatures against that hash.
It would also be an easy optimization to omit or skip duplicate signatures so we could get the cost down to generating one hash and checking 1 signature per participant.
ownerSig, | ||
intermediarySig, |
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.
Should we extend the test to work with two different chains (and chain ids) somehow?
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.
Yup, I've created #4 for that. We'll need to rig up a test that works with multiple chains.
// Only the entrypoint should trigger this by excecuting a UserOp | ||
require(msg.sender == entrypoint, "account: not EntryPoint"); |
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.
Curious if this is necessary -- should we have had this check before? Should all 4337 wallets have this check?
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.
It also seems to be replicated below?
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 needed since we want to enforce that crossChain
is only triggered through submitting a UserOp. Otherwise anyone could call with crossChain
, using a transaction from an EoA, and whatever PaymentChainInfo
or ExecutionChainInfo
they wanted. Since challenge
is allowed to be called at any time, someone could call crossChain
with a bogus unsigned state and trigger a challenge using that.
Should all 4337 wallets have this check?
It depends on the function and the 4337 wallet! In a lot of cases there will be functions restricted to only some owner
or entrypoint
(IE: A function to send user funds)
require(
msg.sender == entrypoint || msg.sender == owner,
"account: not Owner or EntryPoint"
);
This allows the owner of the wallet to call the function using either an EoA account or through submitting a UserOp
.
In our case we need to be a bit stricter, we only want to trigger crossChain
if a UserOp
has been submitted signed by all the participants. We're relying on the signature validation of the UserOp to protect the call to crossChain
.
It also seems to be replicated below?
I think that's in a separate execute
function, which can be triggered outside of crossChain
. So we need to implement the check in both functions.
Adding a multi-chain sequence diagram would be really good. |
|
||
# Payments | ||
|
||
We embed payment information in the UserOp using a `PaymentChainInfo` |
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.
Could the structs be renamed to PaymentInfo
and ExecuteInfo
for simplicity? Or does the use of Chain
in those names help clarify what they are?
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.
I think the use of Chain
helps clarify that info in these structs is specific to a given chain, and should only be honoured on that chain. I don't feel too strongly about it though so I'm happy to change it if others feel differently.
Co-authored-by: Colin Kennedy <NiloCK@users.noreply.github.com> Co-authored-by: George C. Knee <georgeknee@googlemail.com> Co-authored-by: Sam Stokes <35908605+bitwiseguy@users.noreply.github.com>
3b6d304
to
c4829f5
Compare
See markdown doc
TODO