// SPDX-License-Identifier: MIT pragma solidity 0.8.23; import {AerePQCAccount} from "./AerePQCAccount.sol"; /** * @title AerePQCAccountFactory, CREATE2 factory for post-quantum smart accounts (roadmap #270) * @notice Deterministically deploys AerePQCAccount instances keyed on a NIST * Falcon-512 public key (897 bytes) plus a salt, mirroring * AerePasskeyAccountFactoryV2. The account address is a pure function of * (falconPubKey, salt): the same Falcon key with the same salt always * maps to the same address, so an account can be funded at its * counterfactual address before it is deployed, and re-deploying is a * no-op that returns the existing account. * * @dev All accounts from one factory share the factory's EntryPoint and * Falcon-512 verifier (both immutable, set at factory deployment), so the * account creationCode carries no constructor args and the CREATE2 init * code hash is a constant. */ contract AerePQCAccountFactory { /// @notice keccak256 of the AerePQCAccount creation bytecode (no constructor args). bytes32 public constant ACCOUNT_INIT_CODE_HASH = keccak256(type(AerePQCAccount).creationCode); /// @notice NIST Falcon-512 public key length + header (0x00 | logn=9). uint256 internal constant FALCON512_PK_LEN = 897; uint8 internal constant FALCON512_PK_HEADER = 0x09; /// @notice The EntryPoint every account created by this factory trusts. address public immutable defaultEntryPoint; /// @notice The live AereFalcon512Verifier every account delegates verification to. address public immutable falconVerifier; event AccountCreated(address indexed account, address indexed entryPoint, bytes falconPubKey, uint256 salt); error InvalidEntryPoint(); error InvalidFalconVerifier(); error InvalidFalconPubKey(); error AddressMismatch(); constructor(address _defaultEntryPoint, address _falconVerifier) { if (_defaultEntryPoint == address(0)) revert InvalidEntryPoint(); if (_falconVerifier == address(0)) revert InvalidFalconVerifier(); defaultEntryPoint = _defaultEntryPoint; falconVerifier = _falconVerifier; } /// @notice Deterministic account address for (falconPubKey, salt). function predictAddress(bytes calldata falconPubKey, uint256 salt) public view returns (address) { bytes32 saltMix = _saltMix(falconPubKey, salt); return address(uint160(uint256(keccak256(abi.encodePacked( bytes1(0xff), address(this), saltMix, ACCOUNT_INIT_CODE_HASH ))))); } /// @notice Idempotent: deploys + initialises the account for `falconPubKey`, or /// returns the already-deployed account at the same address. function createAccount(bytes calldata falconPubKey, uint256 salt) external returns (AerePQCAccount account) { if (falconPubKey.length != FALCON512_PK_LEN || uint8(falconPubKey[0]) != FALCON512_PK_HEADER) { revert InvalidFalconPubKey(); } address predicted = predictAddress(falconPubKey, salt); if (predicted.code.length > 0) { return AerePQCAccount(payable(predicted)); } bytes32 saltMix = _saltMix(falconPubKey, salt); account = new AerePQCAccount{salt: saltMix}(); account.initialize(defaultEntryPoint, falconVerifier, falconPubKey); if (address(account) != predicted) revert AddressMismatch(); emit AccountCreated(address(account), defaultEntryPoint, falconPubKey, salt); } function _saltMix(bytes calldata falconPubKey, uint256 salt) internal pure returns (bytes32) { return keccak256(abi.encode(falconPubKey, salt)); } }