Skip to content
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

feat: Abstracting proof verifier in account recovery #84

Draft
wants to merge 11 commits into
base: 7702
Choose a base branch
from

Conversation

Dhruv-2003
Copy link
Contributor

Introduction

The current account-recovery flow is specifically design for email based guardians, using email proofs to recover the accounts.

Solution

The idea is to abstract the proof verfier in the account recovery flow. It will support different verifier like zk-email, zk-jwt, OAuth, zk-email.nr

Mutliple guardian verifier implementations are supported for the guardians using the same module, i.e. guardians with different verifier can recover a single account

Key changes

  • Added IGuardianVerifier interface for implementation of verifiers based on different schemes
  • Removed the email-guardian logic from recovery modules

Notes

  • EmailAccountRecovery.sol contract was moved temporarily into this repo
  • Module naming can be updated

Copy link

openzeppelin-code bot commented Mar 15, 2025

feat: Abstracting proof verifier in account recovery

Generated at commit: 3034a9432b2a0a6e5879e938314cdc80fbdd7099

🚨 Report Summary

Severity Level Results
Contracts Critical
High
Medium
Low
Note
Total
0
0
0
4
19
23
Dependencies Critical
High
Medium
Low
Note
Total
0
0
0
0
0
0

For more details view the full report in OpenZeppelin Code Inspector

Copy link
Collaborator

@JohnGuilding JohnGuilding left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks Dhruv. Let me get back to you tomorrow about where this should be merged. Some thoughts on those questions:

Who should choose between different verifiers?
The end user chooses the guardian type e.g. ethereum address, email, JWT. But the developer/client side code makes any lower level decisions e.g. circom or noir

How should we choose between different verifiers?
Not sure yet. When choosing between circom and noir, maybe a guardian is on a phone when handling acceptance but is on their computer when when handling recovery, in which case, would you want to dynamically choose between client side (noir) or server side (circom) proving?

Can keep this as an open question. For now lets keep it simple and pass something to indicate a guardian type in when adding a guardian. All of the guardian types can be supported by default

How do we handle nullifiers/replay protection?
haven't had enough time to think about this. Will keep you posted

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can ignore zksync completely. Feel free to leave zksync files as they are or revert all changes (apart from minimal changes required for EmailRecoveryManagerZkSync to compile)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I have done minimal changes to get it to compile, can remove it from PR if required

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed, whatever you prefer

/// @param accountSalt A bytes32 salt value used to ensure the uniqueness of the deployed proxy address.
/// @param verifierInitData The initialization data for the guardian verifier.
/// @return address The computed address.
function computeGuardianVerifierAddress(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we want to support non-upgradeable guardian verifiers, in which case this logic would change. Hmm let’s leave it for now, but will think more on this

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaning towards non-upgradeable guardians

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we were having similar conversations when designing the EmailSigner, and came to the conclusion we would remove upgradeability by default and only use it where it is needed (email signer would not be upgradeable, but the underlying verifier implementation is used would be)

For this PR, if you have time, would simplify and switch to non-upgradeable guardians

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand, but I would still want to use proxy-clone method for guardian verifiers, so that the deployment cost is minimal.

I can sure remove the upgradeability part here. Let me know if clones work according to you - https://docs.openzeppelin.com/contracts/4.x/api/proxy#Clones

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's fair, Clones work

function initialize(
address recoveredAccount,
bytes32 accountSalt,
bytes calldata initData
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What use cases did you have in mind for this initData again?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On top of my mind, it could be to setup access restriction based parameters, mostly taking reference from EmailAuth.sol

I am not sure about it's user in other cases

* @return isVerified if the proof is valid
*
*/
function verifyProofStrict(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain the rationale again for having verifyProofStrict and verifyProof

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verifyProofStrict - Recommended to use when proof verification is done on-chain or when called from another contract. This avoids extra gas costs from handling failure cases manually.

verifyProof - Recommended to use when proof verification is done off-chain, saves gas cost. This allows clients to check errors without using extra gas.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understood, in that case, would propose 2 changes:

  1. Not against code duplication necessarily, but because the duplicate code is relatively large and complex, we could implement a design similar to OZ's ECDSA library where we have single function for the core logic which returns error strings, then that can be wrapped in the function that is called onchain which reverts with the appropriate errors like so
  2. rename verifyProof -> tryVerifyProof & rename verifyProofStrict -> verifyProof

This avoids extra gas costs from handling failure cases manually.

Not sure I get this, can you elaborate? You mean gas costs from doing something like _throwError?

saves gas cost. This allows clients to check errors without using extra gas.

verifyProofStrict is view so we wouldn't have to spend gas. The benefit is UX here? (which I'm in favour of)

Copy link
Contributor Author

@Dhruv-2003 Dhruv-2003 Mar 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I get this, can you elaborate? You mean gas costs from doing something like _throwError?

yeah user wouldn't have to revert conditionally, but in an ideal scenario revert is always better from the verifier itself

Actually, now when I think more about it, It is not making much sense to return the error string instead of revert. Both offer similar UX and there is no gas benefit of tryVerifyProof

@JohnGuilding
Copy link
Collaborator

If you bump modulekit to latest that should resolve the CI failure

@Dhruv-2003
Copy link
Contributor Author

If you bump modulekit to latest that should resolve the CI failure

After update, the CI failures are because of the incompleted tests for now, will work on them.

@Dhruv-2003 Dhruv-2003 changed the base branch from main to 7702 March 20, 2025 16:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants