// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/// @dev The subset of AerePQAggregateVerifier this module depends on. The verifier checks, via ONE
///      SP1 proof through the deployed gateway, that a registered t-of-n post-quantum committee
///      signed a message digest, at a gas cost independent of the committee size n.
interface IAerePQAggregateVerifier {
    function committeeExists(uint256 committeeId) external view returns (bool);
    function tryVerifyAggregate(
        uint256 committeeId,
        bytes32 messageDigest,
        bytes calldata publicValues,
        bytes calldata proofBytes
    ) external view returns (bool);
}

/// @dev Canonical ERC-4337 v0.7 packed user operation. Declared locally so this module is
///      self-contained and portable to ANY EVM chain and account stack (this repo pins an older
///      OpenZeppelin under the top-level import path; the canonical v5.6 layout is reproduced here
///      byte-for-byte). Matches OpenZeppelin interfaces/draft-IERC4337.sol PackedUserOperation and
///      AereEntryPointV2.PackedUserOperation. [VERIFY] confirm field-for-field against the account
///      stack you deploy under (Safe7579 / Kernel / Nexus all consume this exact struct).
struct PackedUserOperation {
    address sender;
    uint256 nonce;
    bytes initCode;
    bytes callData;
    bytes32 accountGasLimits;
    uint256 preVerificationGas;
    bytes32 gasFees;
    bytes paymasterAndData;
    bytes signature;
}

/**
 * @title AerePQAggregateModule, a portable ERC-7579 validator module for t-of-n POST-QUANTUM
 *        committee authorization via ONE succinct SP1 proof.
 *
 * @notice An ERC-7579 validator module (module type 1) that lets a Safe / Kernel / Nexus style
 *         modular smart account, on ANY EVM chain, use "a t-of-n post-quantum committee approved
 *         this" as its validation rule. The account delegates validation here; this module wraps
 *         AerePQAggregateVerifier, so validating a userOp is a SINGLE SP1 proof check whose gas is
 *         INDEPENDENT of the committee size n. The post-quantum committee legs are ML-DSA / Falcon
 *         signatures proven off-chain in the SP1 zkVM (that guest circuit is [MEASURE] and NOT part
 *         of this repository, see docs/AERE-PQ-AGGREGATE.md); on-chain the account checks one small
 *         proof.
 *
 * @dev    ERC-7579 CONFORMANCE. Implements the type-1 validator surface from OpenZeppelin
 *         interfaces/draft-IERC7579.sol (IERC7579Validator):
 *           - onInstall(bytes) / onUninstall(bytes) / isModuleType(uint256)  [IERC7579Module]
 *           - validateUserOp(PackedUserOperation, bytes32) returns (uint256) [ERC-4337 return packing]
 *           - isValidSignatureWithSender(address, bytes32, bytes) returns (bytes4) [ERC-1271]
 *         Singleton design: per-account state is keyed by msg.sender, exactly like
 *         AereSessionKeyValidator, so configuration is owner-only by construction (only the account
 *         itself, via its EntryPoint / self-call / root-owner gated install and execute paths, is
 *         ever msg.sender here).
 *
 *         [VERIFY] The account is expected to pass, in userOp.signature and in the ERC-1271
 *         `signature`, ONLY this module's payload (the ABI encoding of the SP1 public values and
 *         proof). ERC-7579 accounts select the validator by an address prefix carried in the
 *         userOp NONCE key (Safe7579 / Kernel / Nexus) or, in some stacks, stripped from the
 *         signature before the module is called. This module assumes the routing prefix has already
 *         been removed by the account, matching the OZ AccountERC7579 dispatcher. Confirm the exact
 *         unwrapping against the specific account implementation you deploy under.
 *
 * @dev    SCOPE. Account / authorization layer only. This does NOT make Aere Network consensus
 *         post-quantum (blocks stay classical ECDSA QBFT). It holds no funds and has no admin. What
 *         is post-quantum is the AUTHORITY that approves the account's userOps.
 */
contract AerePQAggregateModule {
    // ERC-7579 / ERC-4337 return conventions.
    uint256 internal constant VALIDATION_SUCCESS = 0;
    uint256 internal constant SIG_VALIDATION_FAILED = 1;
    uint256 internal constant MODULE_TYPE_VALIDATOR = 1;

    bytes4 internal constant EIP1271_MAGIC = 0x1626ba7e;
    bytes4 internal constant EIP1271_FAIL = 0xffffffff;

    /// @notice The aggregation verifier this module validates against (immutable; set at deploy).
    IAerePQAggregateVerifier public immutable VERIFIER;

    /// @notice account => the committeeId that authorizes this account's userOps and ERC-1271
    ///         signatures. Zero means the module is not configured for that account.
    mapping(address => uint256) public committeeOf;

    event ModuleConfigured(address indexed account, uint256 indexed committeeId);
    event ModuleDeconfigured(address indexed account, uint256 indexed committeeId);

    error BadVerifier();
    error AlreadyConfigured();
    error NotConfigured();
    error UnknownCommittee(uint256 committeeId);

    constructor(address verifier) {
        if (verifier == address(0)) revert BadVerifier();
        VERIFIER = IAerePQAggregateVerifier(verifier);
    }

    // ==========================================================================
    //                          ERC-7579 module base
    // ==========================================================================

    /// @notice ERC-7579 module-type check: this is a type-1 validator module.
    function isModuleType(uint256 moduleTypeId) external pure returns (bool) {
        return moduleTypeId == MODULE_TYPE_VALIDATOR;
    }

    /// @notice ERC-7579 accountId-style identifier for this module.
    function name() external pure returns (string memory) {
        return "aere.pq-aggregate-validator.1.0.0";
    }

    /// @notice Install on the calling account. initData layout: abi.encode(uint256 committeeId).
    ///         The committee must already be registered in the verifier.
    function onInstall(bytes calldata data) external {
        address account = msg.sender;
        if (committeeOf[account] != 0) revert AlreadyConfigured();
        uint256 committeeId = abi.decode(data, (uint256));
        if (committeeId == 0 || !VERIFIER.committeeExists(committeeId)) revert UnknownCommittee(committeeId);
        committeeOf[account] = committeeId;
        emit ModuleConfigured(account, committeeId);
    }

    /// @notice Uninstall on the calling account, clearing its committee binding.
    function onUninstall(bytes calldata) external {
        address account = msg.sender;
        uint256 committeeId = committeeOf[account];
        if (committeeId == 0) revert NotConfigured();
        delete committeeOf[account];
        emit ModuleDeconfigured(account, committeeId);
    }

    /// @notice Rebind the calling account to a different registered committee (for example after a
    ///         committee rotation). Owner-only by the singleton msg.sender convention.
    function setCommittee(uint256 committeeId) external {
        address account = msg.sender;
        if (committeeOf[account] == 0) revert NotConfigured();
        if (committeeId == 0 || !VERIFIER.committeeExists(committeeId)) revert UnknownCommittee(committeeId);
        committeeOf[account] = committeeId;
        emit ModuleConfigured(account, committeeId);
    }

    // ==========================================================================
    //                          ERC-7579 validation
    // ==========================================================================

    /**
     * @notice Validate a userOp: succeed iff its signature carries a valid aggregate proof that the
     *         account's committee signed `userOpHash`. Called by the account (msg.sender == account).
     *         Returns the ERC-4337 code (0 = success, 1 = failure) and does NOT revert on a bad or
     *         malformed signature, per the ERC-7579 validator SHOULD.
     *
     *         The `userOpHash` IS the message digest the committee signs, so the aggregate proof is
     *         bound to this exact operation (and, through the account nonce inside the userOpHash, is
     *         non-replayable). userOp.signature MUST be abi.encode(bytes publicValues, bytes proofBytes).
     */
    function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash)
        external
        view
        returns (uint256)
    {
        uint256 committeeId = committeeOf[msg.sender];
        if (committeeId == 0) return SIG_VALIDATION_FAILED;
        return _validate(committeeId, userOpHash, userOp.signature) ? VALIDATION_SUCCESS : SIG_VALIDATION_FAILED;
    }

    /**
     * @notice ERC-1271 validation: return the magic value iff `signature` carries a valid aggregate
     *         proof that the account's committee signed `hash`. Called by the account
     *         (msg.sender == account). Never reverts on a bad or malformed signature.
     * @param sender the address that sent the ERC-1271 request to the account (unused here; the
     *               authority is the committee, not the forwarding sender). [VERIFY] some accounts
     *               scope by `sender`; this module intentionally does not.
     */
    function isValidSignatureWithSender(address sender, bytes32 hash, bytes calldata signature)
        external
        view
        returns (bytes4)
    {
        sender; // explicitly unused; committee is the sole authority
        uint256 committeeId = committeeOf[msg.sender];
        if (committeeId == 0) return EIP1271_FAIL;
        return _validate(committeeId, hash, signature) ? EIP1271_MAGIC : EIP1271_FAIL;
    }

    // ==========================================================================
    //                                 Internal
    // ==========================================================================

    /// @dev Parse `signature` as (bytes publicValues, bytes proofBytes) and ask the verifier whether
    ///      the committee signed `digest`. Never reverts: a malformed signature blob is caught and
    ///      treated as a validation failure (the ERC-7579 SHOULD-not-revert rule). The verifier's
    ///      tryVerifyAggregate is itself fail-closed and gas-constant in the committee size n.
    function _validate(uint256 committeeId, bytes32 digest, bytes calldata signature)
        internal
        view
        returns (bool)
    {
        try this.decodeSignature(signature) returns (bytes memory publicValues, bytes memory proofBytes) {
            return VERIFIER.tryVerifyAggregate(committeeId, digest, publicValues, proofBytes);
        } catch {
            return false;
        }
    }

    /// @notice External pure helper so a malformed signature is caught by the internal try/catch
    ///         instead of reverting validateUserOp. Signature layout: abi.encode(bytes publicValues,
    ///         bytes proofBytes).
    function decodeSignature(bytes calldata signature)
        external
        pure
        returns (bytes memory publicValues, bytes memory proofBytes)
    {
        (publicValues, proofBytes) = abi.decode(signature, (bytes, bytes));
    }

    /// @notice Encode a module signature from its parts (off-chain / test helper).
    function encodeSignature(bytes calldata publicValues, bytes calldata proofBytes)
        external
        pure
        returns (bytes memory)
    {
        return abi.encode(publicValues, proofBytes);
    }
}
