From 0d3ce136c4dd7a317fb9cc8565ca9eec0310cdaa Mon Sep 17 00:00:00 2001 From: soloking1412 Date: Tue, 24 Dec 2024 01:03:56 +0530 Subject: [PATCH] Solution --- .../SimpleRecoveryModule.sol | 126 ++++++++++++++++++ test/SimpleRecoveryModule.t.sol | 82 ++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 src/recovery-module-prototype/SimpleRecoveryModule.sol create mode 100644 test/SimpleRecoveryModule.t.sol diff --git a/src/recovery-module-prototype/SimpleRecoveryModule.sol b/src/recovery-module-prototype/SimpleRecoveryModule.sol new file mode 100644 index 0000000..73e23c4 --- /dev/null +++ b/src/recovery-module-prototype/SimpleRecoveryModule.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +// Core interfaces +interface IERC7579Account { + function changeOwner(address newOwner) external; +} + +struct EmailProof { + string domainName; + bytes32 publicKeyHash; + uint timestamp; + string maskedCommand; + bytes32 emailNullifier; + bytes32 accountSalt; + bool isCodeExist; + bytes proof; +} + +interface IVerifier { + function commandBytes() external view returns (uint256); + function verifyEmailProof(EmailProof memory proof) external view returns (bool); +} + +interface IDKIMRegistry { + function isDKIMPublicKeyHashValid( + string memory domainName, + bytes32 publicKeyHash + ) external view returns (bool); +} + +/** + * @title SimpleRecoveryModule + * @notice A simplified recovery module supporting both ZK Email and EOA guardians + */ +contract SimpleRecoveryModule { + // State variables + mapping(address => address) public accountOwners; + mapping(address => address) public accountGuardians; + mapping(address => bytes32) public accountNullifiers; + + IVerifier public immutable zkEmailVerifier; + IDKIMRegistry public immutable dkimRegistry; + + // Events + event RecoveryInitiated(address indexed account, address newOwner); + event GuardianSet(address indexed account, address guardian); + event NullifierUsed(bytes32 indexed nullifier, address indexed account); + + error InvalidGuardian(); + error InvalidProof(); + error InvalidDKIM(); + error NullifierAlreadyUsed(); + error InvalidNewOwner(); + + constructor(address _verifier, address _dkimRegistry) { + zkEmailVerifier = IVerifier(_verifier); + dkimRegistry = IDKIMRegistry(_dkimRegistry); + } + + /** + * @notice Set up EOA guardian for an account + * @param guardian EOA guardian address + */ + function setupGuardian(address guardian) external { + if (guardian == address(0)) revert InvalidGuardian(); + accountGuardians[msg.sender] = guardian; + accountOwners[msg.sender] = msg.sender; + + emit GuardianSet(msg.sender, guardian); + } + + /** + * @notice Recover account using EOA guardian + * @param account Account to recover + * @param newOwner New owner address + */ + function recoverByGuardian(address account, address newOwner) external { + if (msg.sender != accountGuardians[account]) revert InvalidGuardian(); + if (newOwner == address(0)) revert InvalidNewOwner(); + + _executeRecovery(account, newOwner); + } + + /** + * @notice Recover account using ZK Email proof + * @param account Account to recover + * @param newOwner New owner address + * @param emailProof Email proof data + */ + function recoverByEmail( + address account, + address newOwner, + EmailProof calldata emailProof + ) external { + if (newOwner == address(0)) revert InvalidNewOwner(); + + // Verify DKIM + if (!dkimRegistry.isDKIMPublicKeyHashValid( + emailProof.domainName, + emailProof.publicKeyHash + )) revert InvalidDKIM(); + + // Check nullifier hasn't been used + if (accountNullifiers[account] == emailProof.emailNullifier) revert NullifierAlreadyUsed(); + + // Verify ZK proof + if (!zkEmailVerifier.verifyEmailProof(emailProof)) revert InvalidProof(); + + // Store nullifier + accountNullifiers[account] = emailProof.emailNullifier; + emit NullifierUsed(emailProof.emailNullifier, account); + + _executeRecovery(account, newOwner); + } + + /** + * @notice Internal function to execute recovery + */ + function _executeRecovery(address account, address newOwner) internal { + IERC7579Account(account).changeOwner(newOwner); + accountOwners[account] = newOwner; + + emit RecoveryInitiated(account, newOwner); + } +} \ No newline at end of file diff --git a/test/SimpleRecoveryModule.t.sol b/test/SimpleRecoveryModule.t.sol new file mode 100644 index 0000000..8eaae07 --- /dev/null +++ b/test/SimpleRecoveryModule.t.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "forge-std/Test.sol"; +import "../src/recovery-module-prototype/SimpleRecoveryModule.sol"; + +contract MockAccount { + address public owner; + + function changeOwner(address newOwner) external { + owner = newOwner; + } +} + +contract MockVerifier { + function commandBytes() external pure returns (uint256) { + return 32; + } + + function verifyEmailProof(EmailProof calldata) external pure returns (bool) { + return true; + } +} + +contract MockDKIMRegistry { + function isDKIMPublicKeyHashValid( + string memory, + bytes32 + ) external pure returns (bool) { + return true; + } +} + +contract SimpleRecoveryModuleTest is Test { + SimpleRecoveryModule public module; + MockAccount public account; + MockVerifier public verifier; + MockDKIMRegistry public dkimRegistry; + + address guardian = address(0x123); + address newOwner = address(0x456); + + function setUp() public { + verifier = new MockVerifier(); + dkimRegistry = new MockDKIMRegistry(); + module = new SimpleRecoveryModule(address(verifier), address(dkimRegistry)); + account = new MockAccount(); + + // Setup recovery parameters + vm.prank(address(account)); + module.setupGuardian(guardian); + } + + function testRecoverByGuardian() public { + // Perform recovery using guardian + vm.prank(guardian); + module.recoverByGuardian(address(account), newOwner); + + // Verify owner was changed + assertEq(account.owner(), newOwner); + assertEq(module.accountOwners(address(account)), newOwner); + } + + function testRecoverByEmail() public { + EmailProof memory proof = EmailProof({ + domainName: "example.com", + publicKeyHash: bytes32(uint256(1)), + timestamp: block.timestamp, + maskedCommand: "recover", + emailNullifier: bytes32(uint256(2)), + accountSalt: bytes32(uint256(3)), + isCodeExist: true, + proof: new bytes(0) + }); + + module.recoverByEmail(address(account), newOwner, proof); + + // Verify owner was changed + assertEq(account.owner(), newOwner); + assertEq(module.accountOwners(address(account)), newOwner); + } +} \ No newline at end of file