Crypto2FAHook Technical Documentation
Overview
Crypto2FAHook is a Solidity smart contract that implements a two-factor authentication (2FA) mechanism for blockchain wallets. It serves as a security hook that can be integrated with compatible wallet contracts to provide an additional layer of verification for transactions and signatures.
License
MIT
Solidity Version
^0.8.20
Dependencies
- @soulwallet-core/contracts/interface/IHook.sol
- @openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol
- @openzeppelin/contracts/utils/cryptography/ECDSA.sol
Key Components
Constants
TIME_LOCK_DURATION: Set to 1 day (86400 seconds), defines the waiting period for 2FA address changes
Data Structures
User2FA Struct
struct User2FA {
bool initialized; // Whether the user has initialized 2FA
address wallet2FAAddr; // Current 2FA address
address pending2FAAddr; // Proposed new 2FA address (if change is pending)
uint256 effectiveTime; // Timestamp when pending change becomes effective
}
State Variables
mapping(address => User2FA) public user2FA: Maps user addresses to their 2FA configuration
Core Functionality
Interface Implementation
supportsInterface
function supportsInterface(bytes4 interfaceId) external pure returns (bool)
Implements ERC-165 interface detection.
- Returns
trueif the contract implementsIHook
Initialization
Init
function Init(bytes calldata data) external
Initializes 2FA for a wallet.
- Extracts 2FA address from the first 20 bytes of
data - Can only be called once per wallet
- Throws if already initialized
DeInit
function DeInit() external
Removes 2FA configuration for a wallet.
- Throws if not initialized
- Deletes all user 2FA data
Validation Hooks
preIsValidSignatureHook
function preIsValidSignatureHook(bytes32 hash, bytes calldata hookSignature) external view
Validates signatures using 2FA.
- Recovers signer address from
hookSignature - Requires recovered address to match stored 2FA address
preUserOpValidationHook
function preUserOpValidationHook(
PackedUserOperation calldata userOp,
bytes32 userOpHash,
uint256 missingAccountFunds,
bytes calldata hookSignature
) external view
Validates user operations using 2FA.
- Recovers signer address from
hookSignature - Requires recovered address to match stored 2FA address
2FA Management
initiateChange2FA
function initiateChange2FA(address new2FA) external
Initiates the process of changing the 2FA address.
- Sets pending 2FA address
- Starts time lock period
applyChange2FA
function applyChange2FA() external
Applies pending 2FA change after time lock expires.
- Requires pending change and expired time lock
- Updates 2FA address and clears pending state
cancelChange2FA
function cancelChange2FA() external
Cancels pending 2FA change before time lock expires.
- Requires time lock not expired
- Clears pending state
Technical Considerations
-
Security
- Uses OpenZeppelin's ECDSA for secure signature verification
- Implements time lock for 2FA changes to prevent immediate unauthorized changes
- Requires initialization before use
-
Gas Optimization
- Uses
storagepointers for repeated access to the same user data - Implements view functions where possible to save gas
- Uses
-
Limitations
- Cannot change 2FA address instantly
- No recovery mechanism if 2FA address is lost
Integration Guide
To integrate this hook with a compatible wallet:
- Deploy the Crypto2FAHook contract
- Call
Initwith the desired 2FA address - Ensure the wallet contract calls the appropriate hook functions:
preIsValidSignatureHookfor signature validationpreUserOpValidationHookfor user operation validation
Error Messages
- "already initialized": Thrown when trying to initialize an already initialized user
- "cannot deinit": Thrown when trying to de-initialize an uninitialized user
- "Crypto2FAHook: invalid signature": Thrown when signature verification fails
- "User not initialized": Thrown when uninitialized user attempts to change 2FA
- "No pending change": Thrown when trying to apply non-existent 2FA change
- "Time lock not expired": Thrown when trying to apply 2FA change too early
- "Change already effective": Thrown when trying to cancel an expired change
Known Issues
There appear to be two syntax errors in the original code:
*user2fa.effectiveTime = block.timestamp + TIME*LOCK_DURATION;*user2fa.wallet2FAAddr = *user2fa.pending2FAAddr;
These lines should be corrected to:
_user2fa.effectiveTime = block.timestamp + TIME_LOCK_DURATION;_user2fa.wallet2FAAddr = _user2fa.pending2FAAddr;
Events
Note: The current implementation doesn't emit any events. Consider adding events for important state changes:
- 2FA initialization
- 2FA change initiation
- 2FA change application
- 2FA change cancellation