EIP-6492: Signature validation for pre-deploy contracts

Or, more accurately, front running the transaction isn’t problematic in our use case because the ownership information is encoded into the signature validation.

And the signature validation to create the account is not known beforehand.

Submitting an eip today for this use case so hopefully it’s more clear.

That’s not how Ethereum works - once you put the transaction into the mempool to have it mined, anyone else can use that “secret” data (read: it’s not secret) to do the same. So you cannot have “secrets” really on-chain.

The correct way to implement smart contract wallets (AA accounts) is in such a way that the deploy data can be used by anyone to deploy the contract, but the contract can only be controlled by a it’s original designated owner. There’s very few ways to do it differently anyway (presuming you’re using CREATE2), unless you’re actually relying on msg.sender, which is the wrong approach - rather, you should be encoding the owner of the account in the deploy code itself - you can do this very easily via constructor arguments rather than relying on msg.sender

Protected against front-running means you also don’t care if someone else creates your account for you, but running this initialization code on-chain.

So there is no problem if the creation code is passed as part of the signature validation - the “secret” part of the creation code is not really a secret but “owner-specific information”, which is good.

So I don’t understand why you say:

I don’t think we’re on the same page.

I agree and am aware of all your points.

Once a signature is exposed, anyone can see it. That’s fine.

Here’s a more complete description of how our factory account creation works:

It accepts the salt for the wallet being created (which is used to compute the counterfactual addresss), the initial controlling owner of this wallet, and a signature/message that is used to validate (via ecdsa or 1271) that the owner can actually be assigned to this salt/counterfactual address. Note that the message is a keccak256 of the salt/owner/optional msg.sender.

The signature/message is validated against a signing key set within the factory.

There is no harm for someone to front run this transaction once this information is exposed. Furthermore, there is the option to enforce that the msg.sender is a specific address as well by adding it to the signature/message.

In our case, the owner of the counterfactual wallet is not known ahead of time, and signatures are not generated until the owner address is assigned to a salt.

Please explain to me how the counterfactual address can be secured against front-running, if the owner is not part of it. The idea of counterfactual address is that I (as an owner) knows that I control the contract address and can move assets to it, knowing that I’m the only one who will be able to own that contract. If the address of the owner is not known in advance, then there is some entity that can arbitrarily create this account and assign it to a different owner, and the owner trusts this entity not to do so.

I see where you’re coming from, so maybe it’s out of scope for EIP-6492.

Here is our EIP, that hopefully makes our use case more clear:

It permits the assignment of counterfactual addresses for a given salt, however, the ‘ownership’ of that salt/account is not set initially. The future ownership assignment is governed by the factory.

So, maybe out of scope here, but we were hoping that EIP-6492 could cover the case where signature validity was delegated to the contract creation factory, which would have it’s own signature validation logic for non-web3 owner assigned and non-deployed accounts

are you sure this is the right way to do things? Anything that relies on “context” for authentication with create2 may be fundamentally insecure, because the deploy data will be leaked in the mempool.

You can get around this, and I think you did, by having the factory govern this, but it seems like a convoluted approach that will break many other things in the future.

The goal of wrapping AA wallets as NFTs can be achieved via an additional manager+721 contract, as seen here: https://twitter.com/Ivshti/status/1653982135085068290

Yes, I’m familiar with 6551 (I’m an author on it), but this EIP is specifically for non-web3 identifiers linking to web3 accounts for future claim via a controlled registry. The open question right now is how signing could be supported in such a design.

AA via an NFT doesn’t really make sense here (I’ve tried an implementation around it, and it’s far too clunky).

The pattern I’m thinking of at the moment is a scheme where authentication could be delegated to the contract factory. But thinking more about it, this may be impossible to validate, as how could one ensure that a given counterfactual address is indeed deployable by an indicated factory without simulation (which is what you’ve addressed via the multicall in your spec).

Off topic, but would appreciate further thoughts around security on this thread:

I’m not sure I understand the security concern here. The deploy data is leaked in the mempool, but the account creation verification is based on a registry’s configured signing key, so it wouldn’t really matter who ends up performing the transaction, as it would still assign the wallet to the appropriate owner.

Perhaps, with respect to 6981, it might be smarter to split up the concept of account creation and account control assignment, which would make it 6492 compatible.

Love the spec.

We’re supporting this in our ROA accounts. I think this will be very interesting for onboarding people.

I was implementing this EIP in our wallet and I found that a small addition to its logic can extend its functionality quite a bit. Right now, it skips the call to the factory if it detects that the wallet is already deployed. This makes sense most of the time because someone could have deployed the wallet in the time between the signing of the message and its validation.

However, this makes the EIP useless when the wallet doesn’t need to be deployed, but it needs to be updated in one way or another. A wallet may already have code, but it may not be in a state that’s ready to validate the signature.

This could be all sort of things:

  • Contract code
  • Onchain set of signers
  • Enabling of a flag

We can extend the EIP to address this by performing a “second try”. If the validation of the signature fails (and there is factoryCalldata), we can try executing this data even if the wallet already has code. This would enable any wallet to perform any sort of “preparations”, even after deployment.

The only downside of this approach is that this new code path is less efficient. The contract needs to perform a failed validation and only then can it proceed to the “full validation”. However, the alternative is that the EIP doesn’t provide any support for this scenario at all.

This is the proposed change:

Hello there, I don’t know if anyone mentioned it before. I found a bug in the sample code.
The isValid variable should be declared before try catch scope like:

      bool isValid;
      try IERC1271Wallet(_signer).isValidSignature(_hash, sigToValidate) returns (bytes4 magicValue) {
        isValid = magicValue == ERC1271_SUCCESS;

Otherwise, the isValid variable in catch scope will report DeclarationError when compile by solc.

        } catch (bytes memory err) {
        // retry, but this time assume the prefix is a prepare call
        if (!isValid && !tryPrepare && contractCodeLen > 0) {
          return isValidSigImpl(_signer, _hash, _signature, allowSideEffects, true);
        }

hello,

this is fixed now, the reason for the issue was slightly different

Hey all, a huge fan of this EIP! I’m curious what the latest timeline estimate is for deploying a canonical address for the most-used EVM chains? Has anyone been doing outreach to wallet authenticators like Dynamic or Privy to get them onboard and help distribute the standard? Happy to help on that last point if not :).

Also as a piece of feedback on how the Verifier side works. Right now, it seems ambiguous what network should be used when doing eth_call.

Pre-EIP-6492, in my own experimentation of performing SIWE signatures with smart contract accounts on Sequence, they cite that the “wallet needs to be deployed on Ethereum to sign messages”. This implies that there is some agreement among Verifiers to use Ethereum mainnet currently. In absence of a mechanism to specify which network should be used in EIP-6492, I would naturally assume the same choice would be made. I think 6492 gives us an opportunity to remove this default assumption.

If it has not been considered yet, I propose to add some mechanism of including a specific chainId in the signature schema so that Verifiers can check a specific network. Chain-specificity gives developers more control over the nuances of multi-chain account deployments.

One example improvement this brings is easier compatibility with ERC-6551, where an NFT on one network can control accounts on multiple networks. The least-effort way to verify account control for a 6551 Account is to do the 6492 process on the same network that the underlying NFT is deployed. Having a mechanism for the chainId to be provided in the signature returned to Verifiers, that they then MUST call eth_call on could solve the problem.

Looking for any comments/feedback and thank you for your time :).

Adding further clarification on my previous message, here are quotes from the current EIP:

Signer side

  • If the contract is deployed, produce a normal ERC-1271 signature
  • If the contract is not deployed yet, wrap the signature as follows: concat(abi.encode((create2Factory, factoryCalldata, originalERC1271Signature), (address, bytes, bytes)), magicBytes)
  • If the contract is deployed but not ready to verify using ERC-1271, wrap the signature as follows: concat(abi.encode((prepareTo, prepareData, originalERC1271Signature), (address, bytes, bytes)), magicBytes); prepareTo and prepareData must contain the necessary transaction that will make the contract ready to verify using ERC-1271 (e.g. a call to migrate or update)

“If the contract is deployed” → This is not a realistic framing of the boolean check a signer will perform because a contract may be deployed on some networks, but not others. If my account is deployed on Polygon, but not Ethereum, what should I do as a Signer?

The same applies for the specification for Verifiers:

Verifier side

Full signature verification MUST be performed in the following order:

  • check if the signature ends with magic bytes, in which case do an eth_call to a multicall contract that will call the factory first with the factoryCalldata and deploy the contract if it isn’t already deployed; Then, call contract.isValidSignature as usual with the unwrapped signature
  • check if there’s contract code at the address. If so perform ERC-1271 verification as usual by invoking isValidSignature
  • if the ERC-1271 verification fails, and the deploy call to the factory was skipped due to the wallet already having code, execute the factoryCalldata transaction and try isValidSignature again
  • if there is no contract code at the address, try ecrecover verification

“do an eth_call to a multicall contract…” / “check if there’s contract code at the address” → on which network? Following the same theoretical, I get different results if I choose Polygon vs. Ethereum vs. Other.


I think the EIP can remove these ambiguities that risk implementation variance that will negatively impact UX consistency by implementing something akin to the following (attempted to highlight the diff with bolding):

Signer side

  • If the contract is deployed, wrap the signature as follows: abi.encode((chainId, signature), (uint256, bytes))
  • If the contract is not deployed yet, wrap the signature as follows: concat(abi.encode((chainId, create2Factory, factoryCalldata, originalERC1271Signature), (uint256, address, bytes, bytes)), magicBytes)
  • If the contract is deployed but not ready to verify using ERC-1271, wrap the signature as follows: concat(abi.encode((chainId, prepareTo, prepareData, originalERC1271Signature), (uint256, address, bytes, bytes)), magicBytes); chainId, prepareTo and prepareData must contain the necessary transaction that will make the contract ready to verify using ERC-1271 (e.g. a call to migrate or update)

and for the Verifier side:

Verifier side

Full signature verification MUST be performed in the following order:

  • check if the signature ends with magic bytes, in which case do an eth_call to a multicall contract on the network matching chainId that will call the factory first with the factoryCalldata and deploy the contract if it isn’t already deployed; Then, call contract.isValidSignature as usual with the unwrapped signature
  • check if there’s contract code at the address on the network matching chainId. If so perform ERC-1271 verification as usual by invoking isValidSignature
  • if the ERC-1271 verification fails, and the deploy call to the factory was skipped due to the wallet already having code, execute the factoryCalldata transaction and try isValidSignature again
  • if there is no contract code at the address, try ecrecover verification

Apologies for the large message block, but I thought the quotes would help communicate the issues I think need resolution!

Hey, thanks for the feedback and I think that could be quite useful.

However, I see a few issues with this:

  • signatures may be intentionally cross-chain - this can be resolved via a santinel value for chainId that signifies “universal”
  • signatures may be malleable, and if the chainId is in the wrapper format, the developers MUST also include it in the hash in order to avoid malleability
  • the EIP is now final so I’m not sure if such changes are ok or this should be a new EIP

Can’t wallet providers deduce where to verify the signatures based on the currently selected network of the dapp?

Hey Ivshti, thanks for the response!

Good call on a sentinel value and clarifying the need for chain malleability awareness on the account.

As for the EIP being final, this leads me to think about drafting a new EIP with this feature. Do you think that this scope increase is large enough to warrant a new proposal?

Can’t wallet providers deduce where to verify the signatures based on the currently selected network of the dapp?

Technically, I think they can, but this creates a high UX burden on the user. I’m reaching out to my contacts who do 1271 verification and right now it seems like everyone informally requires the account to be on Ethereum mainnet which is a big bummer for accounts that desire to be L2-first for cost effectiveness. Practically speaking, it’s a large UX burden for users to internalize that they need to connect the account, make sure their account selects the appropriate network, then trigger a SIWE flow. Some apps also trigger a SIWE immediately after wallet connection which makes it even harder for the user to know what to do. Some account implementations also rely on authentication deriving from state that is local to a single network (e.g. ERC-721 owner for an EIP-6551 account) which also demands some way to communicate to a Verifier that a specific chain should be used for authentication.

New EIP if you’re going to be making material/normative changes.