Hello magicians! Would love to hear what you think about this!
Check out the account abstraction recovery demo to understand where the initial EIP idea came from.
Abstract
Introduce a universal account abstraction recovery mechanism recoverOwnership(newOwner, provider, proof) along with recovery provider management functions for smart accounts to securely update their owner.
Motivation
Account abstraction and the “contractization” of EOAs are important Ethereum milestones for improving on-chain UX and off-chain security. A wide range of smart accounts emerge daily, aiming to simplify the steep onboarding curve for new users. The ultimate smart account experience is to never ask them to deal with private keys, yet still allow for full account control and ownership recovery. With the developments in the ZKAI and ZK2FA fields, settling on a common mechanism may even open the doors for “account recovery provider marketplaces” to emerge.
The AARI aims to define a flexible interface for any smart account to implement, allowing users to actively manage their account recovery providers and restore the ownership of an account in case of a private key loss.
Specification
Check out the full specification on GitHub:
Check out the complete minimal reference implementation here.
IMO EIP-7702 must trigger the option to start getting rid of centralized MPCs that have the option to perform address-identity matching. This is where passkeys and proposals like this one that delegate recovery in a decentralized way come in. Looking forward to seeing how this proposal grows!
I am still swinging between the current design and an option to define a common interface for the recovery providers to implement. Probably something like:
interface IRecoveryProvider {
/**
* This function MUST be called from the `recoverOwnership` function on a smart account.
*/
function checkRecovery(bytes memory proof) external view returns (bool);
}
This would mitigate several security concerns (the ERC20 example) while still maintaining the same level of compatibility.
Your original design is already lean, and I especially like the second option you outlined.
We can shave off the last bit of complexity by letting the recovery-provider contract handle all proof parsing and validation:
function recoverOwnership(address newOwner)
external
onlyRecoveryProvider // modifier: require(recoveryProviderExists(msg.sender), "unauthorised")
returns (bytes4 magic)
{
// provider has already verified the proof off-chain or via `checkRecovery`
_owner = newOwner;
emit OwnershipRecovered(msg.sender, newOwner);
return MAGIC; // 0x3cfb167d
}
Questions:
What is the main reason to use the MAGIC?
Why not base the flow on a pre-signed message verified with ecrecover() (or contract signature validation) instead of keeping a provider list on-chain? Advantages:
No on-chain whitelist until it’s needed.
Safer backup: the owner can store a single, purpose-bound “recover to X” signature offline.
Hey, thanks for taking a look! I will probably update the spec to include the IRecoveryProvider interface for security reasons.
The idea here is just an additional sanity check like the one used in EIP-1271. However, it may be beneficial to simplify the design to a mere true/false.
There are several advantages to doing it fully on-chain:
If we take a wallet that wants to support (integrate) several recovery providers, it could have a user onboarding page showing “Optionally add one of these providers to increase the security of your account”. Then we can implement an indexing service to properly present the user-added providers (no need for a wallet to store anything on-device).
Storing this “provider choice” signature off-chain may have its benefits, however it would probably get lost if a user decides to uninstall the app. With the current design, it is still possible to add a recovery prover via a signature and a sponsored multicall.
The minimal reference implementation leveraging ERC-4337 SimpleAccount and a RecoveryProvider that verifies the knowledge of some hash preimage via a Groth16 proof is ready and available here.
Do you have some kind of diagram to understand more the EIP? I mean, I want to understand more about the “Account Recovery Provider Marketplaces” and how this EIP could work for a recovering of funds if I lose my device and I’m using, for example, passkeys to generate my SW with SimpleAccount.
Nvm, this work is cool, maybe a zkproof of my face and requesting the control of the previous SW and keep the funds safu moving to another? Could I do it if I lose the previous device to sign the recover transaction?
About the “Recovery Provider Marketplace”. I envision this as either a page inside a wallet or as a standalone dapp. A user will be asked if they want to add a new recovery method to their account, and then call addRecoveryProvider() under the hood.
If you lose your device, you can still download the wallet and recover the account via the previously added recovery providers. You will still remember your account address, won’t you?
In the simplest form, the recovery can be a password as shown in the reference implementation. In a more complex scenario, it can be a ZKML face recognition neural net, but maintaining backward compatibility may be challenging for the wallet.
The name recoveryProviderExists is a bit odd, since the recovery provider exists regardless of whether it has been added to the account. Perhaps recoveryProviderEnabled, recoveryProviderActive, or recoveryProviderAdded?
In your motivation, you should explain why someone would want this standard over, say, setting the recovery provider as the owner (and having it proxy function calls). There’s probably an obvious downside, but it is the de facto approach that works today.
Thanks for a comprehensive review. Appreciate it very much!
Agree. Renamed the function to recoveryProviderAdded.
Actually, I have thought about adding it to the recovery provider interface, but currently I see no obvious upside for this requirement. Wallets and dapps would probably support recovery providers on an individual integration basis. And adding ERC-165 to an account looks like unnecessary complexity.
That’s a good question. Implicitly, the standard requires some kind of ownership, but I don’t want to restrict it solely to EIP-173. For example, a smart account may support some kind of 2FA or ZK2FA upon every execute action. In case of a private key compromise, a user could still recover the account ownership using EIP-7947. However, if an account inherited EIP-173, the transferOwnership function would not allow that, and a hacker would gain full control over the account.