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

/**
 * @title AereHybridAuth, defense-in-depth ECDSA + NIST Falcon-512 authorization (roadmap #59)
 * @notice Authorizes an action only if BOTH cryptographic legs verify over the
 *         SAME 32-byte message hash:
 *
 *           1. a classical secp256k1 ECDSA signature (EIP-191 personal-sign of the
 *              32-byte messageHash) that recovers to the identity's registered
 *              classical address, AND
 *           2. a real NIST Falcon-512 (post-quantum lattice) signature over the
 *              same 32-byte messageHash, verified through the LIVE on-chain
 *              AereFalcon512Verifier against the identity's registered 897-byte
 *              Falcon public key.
 *
 *         This is a hybrid, transition-era authorization primitive: an action is
 *         authorized iff a holder can produce a valid signature under BOTH a
 *         pre-quantum (ECDSA) and a post-quantum (Falcon-512) key. A break of
 *         either scheme alone is insufficient; an attacker must break both.
 *
 * @dev    GAS / EIP-7825: a Falcon-512 verify is roughly 10.5M gas. The
 *         state-changing `authorize` call therefore lands well under the Fusaka
 *         EIP-7825 per-transaction cap of 2^24 = 16,777,216 gas, so authorization
 *         is a normal transaction. `checkHybrid` is additionally exposed as a
 *         `view` so integrators can pre-flight (or fully demonstrate) the result
 *         via eth_call for free, and see exactly which leg passed or failed.
 *
 *         The Falcon-512 message is exactly `abi.encodePacked(messageHash)` (the
 *         raw 32 bytes). The ECDSA leg signs the EIP-191 personal-sign wrapper of
 *         the same 32 bytes. Both legs are therefore bound to the identical
 *         32-byte messageHash.
 *
 *         No owner, no admin, no upgrade. Identity registration is permissionless
 *         and append-only (an identityId cannot be re-bound once registered).
 */
interface IFalcon512Verifier {
    /// @notice Full on-chain Falcon-512 verification. Returns true iff (pk, message,
    ///         nonce, compSig) is a valid Falcon-512 signature.
    function verify(
        bytes memory pk,
        bytes memory message,
        bytes memory nonce,
        bytes memory compSig
    ) external view returns (bool);
}

contract AereHybridAuth {
    /* ------------------------------- immutable ------------------------------- */

    /// @notice The live AereFalcon512Verifier (chain 2800: 0x4E8e9682…D8fFC).
    IFalcon512Verifier public immutable FALCON;

    /* --------------------------------- state -------------------------------- */

    struct Identity {
        bool registered;
        address ecdsaSigner;   // classical secp256k1 authority
        bytes falconPubKey;    // 897-byte NIST Falcon-512 public key (0x09 header)
    }

    mapping(bytes32 => Identity) private _identities; // identityId => Identity
    bytes32[] public identityIds;

    /// @notice Successful-authorization ledger (state-changing proof surface).
    uint256 public authCount;
    mapping(bytes32 => mapping(bytes32 => bool)) public authorized; // identityId => messageHash => authorized
    bytes32 public lastIdentityId;
    bytes32 public lastMessageHash;

    /* --------------------------------- events ------------------------------- */

    event IdentityRegistered(bytes32 indexed identityId, address indexed ecdsaSigner);
    event Authorized(bytes32 indexed identityId, bytes32 indexed messageHash, address caller, uint256 index);

    /* --------------------------------- errors ------------------------------- */

    error AlreadyRegistered();
    error NotRegistered();
    error BadFalconPubKey();
    error ZeroSigner();
    error HybridAuthFailed(bool ecdsaOk, bool falconOk);

    /* ----------------------------- constructor ------------------------------ */

    constructor(address falconVerifier) {
        require(falconVerifier != address(0), "falcon=0");
        FALCON = IFalcon512Verifier(falconVerifier);
    }

    /* ----------------------------- registration ----------------------------- */

    /// @notice Permissionlessly bind a hybrid identity: a classical ECDSA address
    ///         plus a 897-byte NIST Falcon-512 public key. Append-only: an
    ///         identityId can be registered exactly once.
    function registerIdentity(
        bytes32 identityId,
        address ecdsaSigner,
        bytes calldata falconPubKey
    ) external {
        if (_identities[identityId].registered) revert AlreadyRegistered();
        if (ecdsaSigner == address(0)) revert ZeroSigner();
        // NIST Falcon-512 public key: 897 bytes, header byte 0x09 (0x00 | logn=9).
        if (falconPubKey.length != 897 || uint8(falconPubKey[0]) != 0x09) revert BadFalconPubKey();

        _identities[identityId] = Identity({
            registered: true,
            ecdsaSigner: ecdsaSigner,
            falconPubKey: falconPubKey
        });
        identityIds.push(identityId);
        emit IdentityRegistered(identityId, ecdsaSigner);
    }

    /* ------------------------------- checking ------------------------------- */

    /// @notice Pure verification of a hybrid authorization. Safe for eth_call.
    /// @return ecdsaOk  true iff the ECDSA leg recovers to the registered signer.
    /// @return falconOk true iff the Falcon-512 leg verifies against the registered key.
    /// @return ok       true iff BOTH legs verify (ecdsaOk && falconOk).
    function checkHybrid(
        bytes32 identityId,
        bytes32 messageHash,
        bytes calldata ecdsaSig,
        bytes calldata falconNonce,
        bytes calldata falconCompSig
    ) public view returns (bool ecdsaOk, bool falconOk, bool ok) {
        Identity storage id = _identities[identityId];
        if (!id.registered) revert NotRegistered();

        // Leg 1: ECDSA over EIP-191 personal-sign of the 32-byte messageHash.
        bytes32 ethHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", messageHash));
        address rec = _recover(ethHash, ecdsaSig);
        ecdsaOk = (rec != address(0) && rec == id.ecdsaSigner);

        // Leg 2: Falcon-512 over the SAME 32-byte messageHash (raw bytes).
        falconOk = FALCON.verify(id.falconPubKey, abi.encodePacked(messageHash), falconNonce, falconCompSig);

        ok = ecdsaOk && falconOk;
    }

    /* ------------------------------ authorizing ----------------------------- */

    /// @notice State-changing hybrid authorization. Reverts unless BOTH the ECDSA
    ///         and Falcon-512 legs verify over the same 32-byte messageHash; on
    ///         success it records the authorization and emits `Authorized`.
    function authorize(
        bytes32 identityId,
        bytes32 messageHash,
        bytes calldata ecdsaSig,
        bytes calldata falconNonce,
        bytes calldata falconCompSig
    ) external returns (bool) {
        (bool e, bool f, bool okBoth) = checkHybrid(identityId, messageHash, ecdsaSig, falconNonce, falconCompSig);
        if (!okBoth) revert HybridAuthFailed(e, f);

        authorized[identityId][messageHash] = true;
        uint256 idx = authCount;
        authCount = idx + 1;
        lastIdentityId = identityId;
        lastMessageHash = messageHash;
        emit Authorized(identityId, messageHash, msg.sender, idx);
        return true;
    }

    /* ---------------------------------- views ------------------------------- */

    function getIdentity(bytes32 identityId)
        external
        view
        returns (bool registered, address ecdsaSigner, bytes memory falconPubKey)
    {
        Identity storage id = _identities[identityId];
        return (id.registered, id.ecdsaSigner, id.falconPubKey);
    }

    function identityCount() external view returns (uint256) {
        return identityIds.length;
    }

    function isAuthorized(bytes32 identityId, bytes32 messageHash) external view returns (bool) {
        return authorized[identityId][messageHash];
    }

    /* ------------------------------- internal ------------------------------- */

    function _recover(bytes32 hash, bytes calldata sig) internal pure returns (address) {
        if (sig.length != 65) return address(0);
        bytes32 r;
        bytes32 s;
        uint8 v;
        assembly {
            r := calldataload(sig.offset)
            s := calldataload(add(sig.offset, 32))
            v := byte(0, calldataload(add(sig.offset, 64)))
        }
        if (v < 27) v += 27;
        if (v != 27 && v != 28) return address(0);
        return ecrecover(hash, v, r, s);
    }
}
