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

/**
 * @title AerePQCTxAccount, a post-quantum-authorized ERC-4337 smart account (deployable TODAY)
 *
 * @notice The deploy-today reference path for AIP-PQ-TX (docs/AIP-PQ-TX-2026-07-18.md). Where that
 *         AIP specifies a NEW base-layer EIP-2718 typed transaction (type 0x2A) whose SENDER
 *         AUTHORIZATION is a NIST post-quantum signature (and which therefore needs a coordinated
 *         Besu hard fork before it can be used), THIS contract delivers the same user-visible
 *         guarantee, "a transaction is authorized by a post-quantum signature and nothing else",
 *         entirely at the account layer, with NO base-layer change. It is an ERC-4337 v0.7 smart
 *         account whose sole owner is a NIST PQC public key, and every fund-moving authorization is
 *         decided by the LIVE native PQC precompiles on AERE mainnet chain 2800 (activated at block
 *         9,189,161):
 *
 *           scheme 1 = Falcon-512     verify at 0x0AE1   (NIST round-3 Falcon, 897-byte pk)
 *           scheme 2 = Falcon-1024    verify at 0x0AE2   (NIST round-3 Falcon, 1793-byte pk)
 *           scheme 3 = ML-DSA-44      verify at 0x0AE3   (FIPS 204, 1312-byte pk)
 *           scheme 4 = SLH-DSA-128s   verify at 0x0AE4   (FIPS 205 SPHINCS+, 32-byte pk)
 *
 *         The account calls the precompile DIRECTLY via staticcall, assembling the exact packed
 *         input the live precompiles parse (the byte layout is taken VERBATIM from the mainnet-proven
 *         AerePQCAttestation / AerePQCKeyRegistry, so a positive result here is a real on-chain PQC
 *         verification, not a stand-in). There is no classical ECDSA fallback: the account is
 *         controlled by, and only by, a quantum-resistant key. A userOp can be relayed by any bundler
 *         or any EOA; only the holder of the PQC private key can produce the authorizing signature,
 *         so a relayer only pays gas and can never forge or replay. Because the ECDSA tx sender is
 *         irrelevant to authorization, an adversary who can forge secp256k1 (the exact thing a
 *         quantum computer breaks) still cannot move this account's funds.
 *
 * @dev    HONEST SCOPE. Application / account layer only. This changes nothing about AERE consensus,
 *         which remains classical ECDSA QBFT (7 Foundation validators, f=2). What is post-quantum
 *         here is the TRANSACTION AUTHORIZATION for this account: a spend is gated on a NIST PQC
 *         signature verified by the live precompiles, not on a secp256k1 key. This is NOT post-quantum
 *         consensus and must never be described as such. The precompiles carry an internal self-audit
 *         only (see AIP-7); an external audit is pending, so this account should not secure material
 *         value until that audit lands.
 *
 * @dev    SIGNATURE ENVELOPE (identical to the live precompiles / AerePQCAttestation, NOT the
 *         abi.encoded shape AerePQCAccount uses, because this account speaks to the precompile
 *         directly). Wherever a signature is consumed:
 *           Falcon (scheme 1,2):     signature = nonce(40) || esig   (esig starts 0x29 / 0x2A)
 *           ML-DSA-44 (scheme 3):    signature = sig, exactly 2420 bytes
 *           SLH-DSA-128s (scheme 4): signature = sig, exactly 7856 bytes
 *
 * @dev    DOMAIN SEPARATION (mirrors the adversarial-review HIGH fixed in AerePQCAccount, 2026-07-12).
 *         A fund-moving authorization (validateUserOp / executeWithPQC) is a PQC signature over the
 *         64-byte message `abi.encodePacked(domain, hash)` (domain = USEROP_DOMAIN / EXEC_DOMAIN),
 *         while the EIP-1271 isValidSignature surface verifies the RAW 32-byte message. A 64-byte
 *         message can never equal a 32-byte message under the precompile's exact-bytes check, so a
 *         signature phished through an EIP-1271 "login" over an attacker-chosen 32-byte hash (which
 *         the attacker could set equal to an execute-challenge or userOpHash, both computable from
 *         public state since execNonce is public) can NEVER be replayed as a spend.
 *
 * @dev    GAS / EIP-7825. On mainnet the native precompile verify is cheap relative to the
 *         pure-Solidity verifiers: per AIP-7's scratch-fork measurements the marginal verify-op is
 *         ~40,000 gas (Falcon-512), ~75,000 (Falcon-1024), ~55,000 (ML-DSA-44), ~350,000
 *         (SLH-DSA-128s). A full handleOps -> validateUserOp path is one ordinary transaction that
 *         must stay under the Fusaka EIP-7825 per-transaction cap of 2^24 = 16,777,216 gas; every one
 *         of these schemes leaves ample headroom. isValidSignature is a `view`, so integrators can
 *         verify a PQC signature for free via eth_call regardless of the cap.
 *
 *         No owner, no admin, no upgrade. The PQC owner key is fixed at construction. Key loss /
 *         rotation is handled by composing at a higher layer (e.g. moving assets to a fresh account,
 *         or an ERC-7579 modular account guarded by AerePQCSocialRecoveryModule), deliberately kept
 *         out of this contract so its authorization surface stays minimal and auditable.
 */
contract AerePQCTxAccount {
    // ----- scheme identifiers (match AerePQCAttestation / AerePQCKeyRegistry) -----
    uint8 public constant SCHEME_FALCON512 = 1;
    uint8 public constant SCHEME_FALCON1024 = 2;
    uint8 public constant SCHEME_MLDSA44 = 3;
    uint8 public constant SCHEME_SLHDSA128S = 4;

    // ----- live precompile addresses on chain 2800 (activated block 9,189,161) -----
    address internal constant PRECOMPILE_FALCON512 = address(0x0AE1);
    address internal constant PRECOMPILE_FALCON1024 = address(0x0AE2);
    address internal constant PRECOMPILE_MLDSA44 = address(0x0AE3);
    address internal constant PRECOMPILE_SLHDSA128S = address(0x0AE4);

    // ----- public key sizes / headers -----
    uint256 internal constant FALCON512_PK_LEN = 897;
    uint256 internal constant FALCON1024_PK_LEN = 1793;
    uint256 internal constant MLDSA44_PK_LEN = 1312;
    uint256 internal constant SLHDSA128S_PK_LEN = 32;
    uint8 internal constant FALCON512_PK_HEADER = 0x09; // 0x00 | logn=9
    uint8 internal constant FALCON1024_PK_HEADER = 0x0A; // 0x00 | logn=10

    // ----- signature sizes (fixed-length schemes) -----
    uint256 internal constant MLDSA44_SIG_LEN = 2420;
    uint256 internal constant SLHDSA128S_SIG_LEN = 7856;
    uint256 internal constant FALCON_NONCE_LEN = 40;

    /// @notice Signing-domain separators. A fund-moving authorization is a PQC signature over
    ///         `abi.encodePacked(domain, hash)` (64 bytes); the EIP-1271 surface verifies the raw 32
    ///         bytes. See the DOMAIN SEPARATION note above.
    bytes32 public constant USEROP_DOMAIN = keccak256("AerePQCTxAccount.validateUserOp.v1");
    bytes32 public constant EXEC_DOMAIN = keccak256("AerePQCTxAccount.executeWithPQC.v1");

    /// @notice EIP-1271 magic value returned by isValidSignature on success.
    bytes4 internal constant EIP1271_MAGIC = 0x1626ba7e;

    /// @notice ERC-4337 signature-validation-failed sentinel (validationData != 0).
    uint256 internal constant SIG_VALIDATION_FAILED = 1;

    /// @notice The NIST PQC scheme (1..4) that owns this account. Fixed at construction.
    uint8 public immutable pqcScheme;

    /// @notice ERC-4337 EntryPoint allowed to call validateUserOp + executeFromEntryPoint. Immutable.
    address public immutable entryPoint;

    /// @notice The raw NIST PQC public key that owns this account (length/header validated at
    ///         construction against `pqcScheme`). Set once; never mutated.
    bytes public pqcPubKey;

    /// @notice Monotonic replay counter for the direct PQC-authorized self-relay path.
    uint256 public execNonce;

    /// @notice Generic call struct (used by executeBatchFromEntryPoint).
    struct Call {
        address target;
        uint256 value;
        bytes data;
    }

    /// @notice ERC-4337 v0.7 PackedUserOperation shape (matches AereEntryPointV2 / AerePQCAccount).
    struct PackedUserOperation {
        address sender;
        uint256 nonce;
        bytes initCode;
        bytes callData;
        bytes32 accountGasLimits;
        uint256 preVerificationGas;
        bytes32 gasFees;
        bytes paymasterAndData;
        bytes signature;
    }

    event Initialized(address indexed entryPoint, uint8 indexed scheme, uint256 pubKeyLen);
    event Executed(uint256 indexed nonce, address indexed target, uint256 value, bytes data);
    /// @notice Emitted by the EntryPoint-gated batch path. AUD-CONTRACT-3: the `nonce` field carries
    ///         the current self-relay `execNonce`, which the 4337 path does NOT advance, so it is a
    ///         snapshot of the self-relay counter and is NOT a per-batch identifier (two consecutive
    ///         batches emit the same value). Batch authorization is enforced by `msg.sender ==
    ///         entryPoint` and userOp ordering by the EntryPoint's own account nonce, never by this
    ///         field; off-chain indexers must not key on it as a unique batch id.
    event ExecutedBatch(uint256 indexed nonce, uint256 count);

    error Unauthorized();
    error NotInitialized();
    error InvalidEntryPoint();
    error InvalidScheme(uint8 scheme);
    error InvalidPqcPubKey();
    error InvalidSignature();
    error CallFailed(bytes returnData);

    /// @param _entryPoint the ERC-4337 EntryPoint this account trusts (e.g. AereEntryPointV2
    ///        0x8D6f40598d552fF0Cb358b6012cF4227B86aF770 on chain 2800).
    /// @param _scheme     1=Falcon-512, 2=Falcon-1024, 3=ML-DSA-44, 4=SLH-DSA-128s.
    /// @param _pqcPubKey  the raw NIST public key for `_scheme` (length/header validated per scheme).
    constructor(address _entryPoint, uint8 _scheme, bytes memory _pqcPubKey) {
        if (_entryPoint == address(0)) revert InvalidEntryPoint();
        _validatePubKey(_scheme, _pqcPubKey);
        entryPoint = _entryPoint;
        pqcScheme = _scheme;
        pqcPubKey = _pqcPubKey;
        emit Initialized(_entryPoint, _scheme, _pqcPubKey.length);
    }

    // ─── ERC-4337 path ────────────────────────────────────────────────────────

    /// @notice Called by the EntryPoint to validate a user operation.
    /// @dev    Returns 0 on success and SIG_VALIDATION_FAILED (1) on a bad PQC signature, per
    ///         ERC-4337 (does NOT revert on a bad signature). Replay/ordering are enforced by the
    ///         EntryPoint's account nonce: `userOpHash` already binds sender + chainid + nonce, and
    ///         the signature is domain-separated so it cannot be replayed on the exec or EIP-1271
    ///         surfaces.
    function validateUserOp(
        PackedUserOperation calldata userOp,
        bytes32 userOpHash,
        uint256 missingAccountFunds
    ) external returns (uint256 validationData) {
        if (msg.sender != entryPoint) revert Unauthorized();

        if (!_verifyDomained(USEROP_DOMAIN, userOpHash, userOp.signature)) {
            return SIG_VALIDATION_FAILED;
        }

        // Pay the EntryPoint any prefund it needs (the EntryPoint enforces the gas budget).
        if (missingAccountFunds > 0) {
            (bool sent, ) = payable(entryPoint).call{value: missingAccountFunds}("");
            (sent); // ignore — EntryPoint reverts if underfunded
        }
        return 0;
    }

    /// @notice Called by the EntryPoint after validation to dispatch the user's call.
    function executeFromEntryPoint(address target, uint256 value, bytes calldata data) external {
        if (msg.sender != entryPoint) revert Unauthorized();
        (bool ok, bytes memory ret) = target.call{value: value}(data);
        if (!ok) revert CallFailed(ret);
    }

    /// @notice Batch variant of executeFromEntryPoint.
    function executeBatchFromEntryPoint(Call[] calldata calls) external {
        if (msg.sender != entryPoint) revert Unauthorized();
        for (uint256 i = 0; i < calls.length; i++) {
            (bool ok, bytes memory ret) = calls[i].target.call{value: calls[i].value}(calls[i].data);
            if (!ok) revert CallFailed(ret);
        }
        // AUD-CONTRACT-3: `execNonce` is the self-relay replay counter and is intentionally NOT
        // advanced on the 4337 path (replay/ordering is the EntryPoint's account nonce). The logged
        // value is therefore a snapshot, not a per-batch id; see the ExecutedBatch NatSpec.
        emit ExecutedBatch(execNonce, calls.length);
    }

    // ─── Direct PQC-authorized path (self-relay, no bundler required) ──────────

    /// @notice The 32-byte challenge the owner's PQC key must sign for an executeWithPQC() call.
    ///         Binds this account + chainid + the current execNonce + the call, so a leg is scoped to
    ///         one account, one chain, one nonce, and one exact call.
    function getExecuteChallenge(
        address target,
        uint256 value,
        bytes calldata data,
        uint256 expectedNonce
    ) public view returns (bytes32) {
        return keccak256(abi.encode(address(this), block.chainid, expectedNonce, target, value, data));
    }

    /// @notice Execute a single call, authorized by a PQC signature over the current execute-challenge.
    ///         Lets the account be driven directly (e.g. by a Foundation relayer) without routing
    ///         through a 4337 bundler. Domain-separated (EXEC_DOMAIN): a signature phished via the
    ///         raw-hash isValidSignature surface cannot authorize a spend here.
    function executeWithPQC(
        bytes calldata signature,
        address target,
        uint256 value,
        bytes calldata data
    ) external returns (bytes memory result) {
        bytes32 challenge = getExecuteChallenge(target, value, data, execNonce);
        if (!_verifyDomained(EXEC_DOMAIN, challenge, signature)) revert InvalidSignature();

        uint256 n = execNonce;
        execNonce = n + 1;

        (bool ok, bytes memory ret) = target.call{value: value}(data);
        if (!ok) revert CallFailed(ret);
        emit Executed(n, target, value, data);
        return ret;
    }

    // ─── EIP-1271 ─────────────────────────────────────────────────────────────

    /// @notice EIP-1271: returns the magic value iff `signature` is a valid PQC signature over the
    ///         RAW 32 bytes of `hash` under this account's owner key. A `view`, so integrators
    ///         (Permit2, OpenSea, Snapshot, dapp logins) can verify a post-quantum signature for free
    ///         via eth_call. The raw-hash message is length-distinct from the 64-byte fund-moving
    ///         messages, so a login signature can never authorize a spend.
    function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4) {
        return _verify(abi.encodePacked(hash), signature) ? EIP1271_MAGIC : bytes4(0);
    }

    // ─── PQC verification (internal) ──────────────────────────────────────────

    /// @dev Domain-separated PQC verify for the fund-moving paths: verifies over the 64-byte message
    ///      `abi.encodePacked(domain, hash)`.
    function _verifyDomained(bytes32 domain, bytes32 hash, bytes calldata signature)
        internal
        view
        returns (bool)
    {
        return _verify(abi.encodePacked(domain, hash), signature);
    }

    /// @dev Build the exact precompile input for this account's scheme with `message` as the signed
    ///      message, staticcall the live precompile, and return true iff it answers 0x..01. Fail-
    ///      closed: any malformed length, staticcall failure, or empty/short/zero return is invalid
    ///      (never reverts), so a bad signature yields `false` rather than a revert (required by
    ///      validateUserOp).
    function _verify(bytes memory message, bytes calldata signature) internal view returns (bool) {
        (bool built, address precompileAddr, bytes memory input) = _buildInput(message, signature);
        if (!built) return false;
        (bool ok, bytes memory ret) = precompileAddr.staticcall(input);
        return ok && ret.length >= 32 && ret[31] == 0x01;
    }

    /// @dev Assemble (precompile, input) for this account's scheme with `message` as the signed
    ///      message. Byte layout is identical to AerePQCAttestation._buildInput (proven against the
    ///      live mainnet precompiles), generalised so `message` may be any length (32 or 64 bytes
    ///      here). Returns built=false on any length that cannot form a valid input.
    function _buildInput(bytes memory message, bytes calldata signature)
        internal
        view
        returns (bool built, address precompileAddr, bytes memory input)
    {
        uint8 scheme = pqcScheme;
        bytes memory pk = pqcPubKey; // load the owner key once into memory
        if (scheme == SCHEME_FALCON512 || scheme == SCHEME_FALCON1024) {
            // signature = nonce(40) || esig
            if (signature.length <= FALCON_NONCE_LEN) return (false, address(0), "");
            uint256 sigLen = signature.length - FALCON_NONCE_LEN; // esig length
            if (sigLen > type(uint16).max) return (false, address(0), "");

            bytes memory nonce = signature[0:FALCON_NONCE_LEN];
            bytes memory esig = signature[FALCON_NONCE_LEN:];

            // sm = sigLen(2, big-endian) || nonce(40) || message || esig
            bytes memory sm = abi.encodePacked(uint16(sigLen), nonce, message, esig);
            input = abi.encodePacked(pk, sm);
            precompileAddr = scheme == SCHEME_FALCON512 ? PRECOMPILE_FALCON512 : PRECOMPILE_FALCON1024;
        } else if (scheme == SCHEME_MLDSA44) {
            if (signature.length != MLDSA44_SIG_LEN) return (false, address(0), "");
            input = abi.encodePacked(pk, signature, message); // pk(1312) || sig(2420) || message
            precompileAddr = PRECOMPILE_MLDSA44;
        } else if (scheme == SCHEME_SLHDSA128S) {
            if (signature.length != SLHDSA128S_SIG_LEN) return (false, address(0), "");
            input = abi.encodePacked(pk, signature, message); // pk(32) || sig(7856) || message
            precompileAddr = PRECOMPILE_SLHDSA128S;
        } else {
            return (false, address(0), "");
        }
        built = true;
    }

    function _validatePubKey(uint8 scheme, bytes memory pubKey) internal pure {
        if (scheme == SCHEME_FALCON512) {
            if (pubKey.length != FALCON512_PK_LEN || uint8(pubKey[0]) != FALCON512_PK_HEADER) revert InvalidPqcPubKey();
        } else if (scheme == SCHEME_FALCON1024) {
            if (pubKey.length != FALCON1024_PK_LEN || uint8(pubKey[0]) != FALCON1024_PK_HEADER) revert InvalidPqcPubKey();
        } else if (scheme == SCHEME_MLDSA44) {
            if (pubKey.length != MLDSA44_PK_LEN) revert InvalidPqcPubKey();
        } else if (scheme == SCHEME_SLHDSA128S) {
            if (pubKey.length != SLHDSA128S_PK_LEN) revert InvalidPqcPubKey();
        } else {
            revert InvalidScheme(scheme);
        }
    }

    // ─── Views ────────────────────────────────────────────────────────────────

    /// @notice The live precompile address this account's scheme verifies through.
    function precompileAddress() external view returns (address) {
        if (pqcScheme == SCHEME_FALCON512) return PRECOMPILE_FALCON512;
        if (pqcScheme == SCHEME_FALCON1024) return PRECOMPILE_FALCON1024;
        if (pqcScheme == SCHEME_MLDSA44) return PRECOMPILE_MLDSA44;
        return PRECOMPILE_SLHDSA128S;
    }

    /// @notice The exact 64-byte message the owner PQC key must sign to authorize a userOp with hash
    ///         `userOpHash`. Off-chain signers query this to know what to sign.
    function userOpSigningMessage(bytes32 userOpHash) external pure returns (bytes memory) {
        return abi.encodePacked(USEROP_DOMAIN, userOpHash);
    }

    receive() external payable {}
}
