pragma solidity ^0.8.0; import "@gnosis.pm/safe-contracts/contracts/base/Module.sol"; contract PasswordRecoveryModule is Module { bytes32 private passwordHash; uint256 private constant BLOCKS_VALID = 200; uint256 private constant RECOVERY_DELAY = 50; uint256 private recoveryInitiatedBlock; address private recoveryInitiator; mapping(address => bool) private recoveryCancelers; event PasswordHashUpdated(bytes32 newPasswordHash); event RecoveryInitiated(address indexed initiator, uint256 validUntilBlock); event RecoveryCancelled(address indexed canceler); event CancelerAdded(address indexed canceler); event CancelerRemoved(address indexed canceler); constructor( address _safe, bytes32 _passwordHash, address[] memory _recoveryCancelers ) { require(_safe != address(0), "Invalid Safe address"); require(_passwordHash != bytes32(0), "Invalid password hash"); safe = Safe(_safe); passwordHash = _passwordHash; for (uint256 i = 0; i < _recoveryCancelers.length; i++) { require(_recoveryCancelers[i] != address(0), "Invalid recovery canceler address"); recoveryCancelers[_recoveryCancelers[i]] = true; } } function setup(bytes memory initParams) public override { // No setup required for this module } function updatePasswordHash(bytes32 newPasswordHash) public { require(safe.isOwner(msg.sender), "Not authorized"); require(newPasswordHash != bytes32(0), "Invalid password hash"); passwordHash = newPasswordHash; emit PasswordHashUpdated(newPasswordHash); } function initiateRecovery() public { recoveryInitiatedBlock = block.number; recoveryInitiator = msg.sender; emit RecoveryInitiated(msg.sender, recoveryInitiatedBlock + BLOCKS_VALID); } function cancelRecovery() public { require( safe.isOwner(msg.sender) || recoveryCancelers[msg.sender], "Not authorized" ); require(recoveryInitiator != address(0), "No recovery initiated"); emit RecoveryCancelled(msg.sender); // Reset recovery state recoveryInitiatedBlock = 0; recoveryInitiator = address(0); } function recoverAccess(address newOwner, string memory password) public { require(msg.sender == recoveryInitiator, "Not authorized"); require(recoveryInitiatedBlock + RECOVERY_DELAY <= block.number, "Recovery delay not met"); require(recoveryInitiatedBlock + BLOCKS_VALID >= block.number, "Recovery reservation expired"); require(passwordHash == keccak256(abi.encodePacked(password)), "Invalid password"); address oldOwner = safe.getOwner(msg.sender); require(oldOwner != address(0), "Not an owner"); safe.swapOwner(msg.sender, oldOwner, newOwner); // Reset recovery state and password hash recoveryInitiatedBlock = 0; recoveryInitiator = address(0); passwordHash = bytes32(0); } function addCanceler(address canceler) public { require(safe.isOwner(msg.sender), "Not authorized"); require(canceler != address(0), "Invalid canceler address"); require(!recoveryCancelers[canceler], "Address is already a canceler"); recoveryCancelers[canceler] = true; emit CancelerAdded(canceler); } function removeCanceler(address canceler) public { require(safe.isOwner(msg.sender), "Not authorized"); require(canceler != address(0), "Invalid canceler address"); require(recoveryCancelers[canceler], "Address is not a canceler"); delete recoveryCancelers[canceler]; emit CancelerRemoved(canceler); } }