# Hexlink Onchain 2FA Framework
## Summary
Hexlink Account supports on-chain 2FA. Each hexlink account is with a default first auth factor, normally email/phone number. User can update the first factor as they want, e.g. from email to phone number. Each account can also specify one or more second factors. Users must authenticate with the first factor and at least one second factor if any second factors are set.
## Architecture

## Interface
__Auth Factor__: The factor user configured(in its account storage) which defines how to authenticate the user. The struct is defined as:
```
struct AuthFactor {
address authProvider;
address validator;
}
```
The name is the identifier for the auth provider to authenticate itself.
__Auth Provider__: The auth provider is a smart contract to conduct the real validation. Given an validator and name, it checks if the validator is properly registered. The interface is defined as:
```
// public spec
interface IAuthProvider {
function getValidator(address from) external view returns(address);
function getDefaultValidator() external view returns(address);
function getMetadata() external view returns(string memory);
}
```
The *getValidator* function should return a legit validator given address. Each auth provider may maintain onchain validator list(e.g. through some external validator registry contract). Only user operations signed by validators in the list will pass the validation check. Each validator may set a default validator.
## Auth Provider Metadata
```
{
version: number,
rpcUrls: [URI],
params: {
authType: "otp" | "signature",
credentials: string,
requestId: string,
idType: string,
account: string,
}
}
```
## Challenge of EIP-4377
Due to limitation of EIP-4337, we cannot get access to external storage during validation. However, since all validators is stored in the auth provider contract, we need to read the storage to ensure signer is from a valid validator node.
The way we solve this is to follow a three layer cache system.
## Setup
1. Each auth provider can provide a default validator, which is hardcoded into the code and not restricted by the storage access rule.
2. User cache some validators in account contract storage, which is readable during validation.
3. Each auth provider maintains a dynamic list of validators, which is the source of truth.
4. Account contract exposes a __setValidator()__ API for others to cache the validators in account storage. The function is permissionless so as long as the validator is valid per auth provider, it could be set.
## Validation
1. We encode the signer and auth factor at the userOp.signature
2. We check if the auth factor is enabled or not, if yes, go to 3, otherwise fail the validation
3. We check if the signer is default validator, if yes, go to 6, otherwise go to 4
4. We check if there is any cached validators, if yes, go to 5, otherwise go to 6
5. We check if the signer is cached, if yes, go to 6, otherwise fail the validation
6. We check if the signature is signed by the signer, if yes, go to 7, otherwise fail the validation
7. Cache the current signer and auth factor at the account storage
For step 5, it's possible that the signer is a valid per auth provider but not per the cache. In this case, users have to rely on a third-party to set the validator to the account contract before running the simulation.
## Execution
Since the cached validators in account contract may be out of sync with the ones at Auth provider contract, we have to re-check the signer during execution by run __setValidator()__ function with the cached signer and auth factor.
1. Check if the signer is default validator, if yes, continue to exectution, otherwise go to 2
2. Check if the signer is a valid validator per auth provider and is cached, there are 4 cases:
2.1. If the signer is valid and cached, continue to exectution
2.2. If the signer is valid but not cached, cache the validator and continue to execution
2.3. If the signer is not valid but cached, remove the validator from cache and return
2.4. If the signer is not valid and not cached, revert with InvalidSigner error