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

Solution #69

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions src/recovery-module-prototype/SimpleRecoveryModule.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}
82 changes: 82 additions & 0 deletions test/SimpleRecoveryModule.t.sol
Original file line number Diff line number Diff line change
@@ -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);
}
}