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

/**
 * @title AereKZGVerifier — on-chain anchor for EIP-4844 KZG point evaluations
 * @notice Thin, admin-less registry over the POINT_EVALUATION precompile at
 *         address 0x0A (EIP-4844, live on AERE chain 2800: confirmed via the
 *         official go-ethereum known-answer test vector on 2026-07-10).
 *
 *         The precompile takes a 192-byte input
 *           versioned_hash (32) | z (32) | y (32) | commitment (48) | proof (48)
 *         and, on a VALID proof that P(z) == y for the blob polynomial P
 *         committed to by `commitment`, returns exactly 64 bytes:
 *           uint256(FIELD_ELEMENTS_PER_BLOB = 4096) | uint256(BLS_MODULUS)
 *         On an INVALID proof the precompile call itself fails (Besu raises a
 *         precompile error, so the staticcall returns success=false).
 *
 *         verifyPointEvaluation() forwards the raw input, validates BOTH the
 *         success flag and the canonical 64-byte return payload, and only then
 *         records (sender, versionedHash, z, y, block, time) and emits an
 *         event. checkPointEvaluation() is the gas-cheap view twin that
 *         records nothing.
 *
 *         NOTE: precompiles have no code (extcodesize == 0), so this contract
 *         deliberately performs NO codesize check on the precompile address;
 *         correctness is enforced by the strict 64-byte canonical-constant
 *         return check, which no empty account and no accidental contract can
 *         satisfy by chance.
 *
 *         IMMUTABILITY: no owner, no admin, no pause. Anyone may verify;
 *         records are append-only.
 */
contract AereKZGVerifier {

    /// @notice EIP-4844 point-evaluation precompile address.
    address public constant POINT_EVALUATION = address(0x0A);

    /// @notice Exact input length the precompile accepts.
    uint256 public constant INPUT_LENGTH = 192;

    /// @notice First word of the canonical precompile return.
    uint256 public constant FIELD_ELEMENTS_PER_BLOB = 4096;

    /// @notice Second word of the canonical precompile return (BLS12-381 scalar
    ///         field modulus).
    uint256 public constant BLS_MODULUS =
        52435875175126190479447740508185965837690552500527637822603658699938581184513;

    struct Verification {
        address sender;
        bytes32 versionedHash; // sha256(commitment) with first byte 0x01
        bytes32 z;             // evaluation point (field element)
        bytes32 y;             // claimed evaluation P(z) (field element)
        uint64 blockNumber;
        uint64 timestamp;
    }

    Verification[] private _verifications;

    /// @notice Number of successful verifications recorded per versioned hash.
    mapping(bytes32 => uint256) public verificationCountByHash;

    /// @notice Index (+1) of the most recent verification for a versioned
    ///         hash; 0 means never verified.
    mapping(bytes32 => uint256) public latestIndexPlusOneByHash;

    event PointEvaluationVerified(
        address indexed sender,
        bytes32 indexed versionedHash,
        bytes32 z,
        bytes32 y,
        uint256 index
    );

    error InvalidInputLength(uint256 provided);
    error PrecompileCallFailed();
    error InvalidPrecompileReturn(bytes returned);

    /**
     * @notice Verify an EIP-4844 KZG point-evaluation proof and record it.
     * @param input Raw 192-byte precompile input:
     *              versioned_hash | z | y | commitment | proof.
     * @return index Index of the stored verification record.
     */
    function verifyPointEvaluation(bytes calldata input)
        external
        returns (uint256 index)
    {
        (bytes32 versionedHash, bytes32 z, bytes32 y) = _verify(input);

        index = _verifications.length;
        _verifications.push(Verification({
            sender: msg.sender,
            versionedHash: versionedHash,
            z: z,
            y: y,
            blockNumber: uint64(block.number),
            timestamp: uint64(block.timestamp)
        }));
        verificationCountByHash[versionedHash] += 1;
        latestIndexPlusOneByHash[versionedHash] = index + 1;

        emit PointEvaluationVerified(msg.sender, versionedHash, z, y, index);
    }

    /**
     * @notice View-only twin of verifyPointEvaluation: verifies the proof via
     *         the precompile but records nothing. Reverts on any failure, so a
     *         `true` return means the proof is valid.
     */
    function checkPointEvaluation(bytes calldata input)
        external
        view
        returns (bool)
    {
        _verify(input);
        return true;
    }

    /// @notice Total number of recorded verifications.
    function verificationCount() external view returns (uint256) {
        return _verifications.length;
    }

    /// @notice Full record at `index` (reverts if out of range).
    function getVerification(uint256 index)
        external
        view
        returns (Verification memory)
    {
        return _verifications[index];
    }

    /// @notice Most recent record for `versionedHash` (reverts if none).
    function getLatestVerificationForHash(bytes32 versionedHash)
        external
        view
        returns (Verification memory)
    {
        uint256 idxPlusOne = latestIndexPlusOneByHash[versionedHash];
        require(idxPlusOne != 0, "AereKZGVerifier: hash never verified");
        return _verifications[idxPlusOne - 1];
    }

    /// @dev Shared verification core: staticcall the precompile, enforce the
    ///      canonical 64-byte return, and split out the recorded fields.
    function _verify(bytes calldata input)
        internal
        view
        returns (bytes32 versionedHash, bytes32 z, bytes32 y)
    {
        if (input.length != INPUT_LENGTH) {
            revert InvalidInputLength(input.length);
        }

        (bool ok, bytes memory ret) = POINT_EVALUATION.staticcall(input);
        if (!ok) revert PrecompileCallFailed();
        if (ret.length != 64) revert InvalidPrecompileReturn(ret);

        (uint256 fieldElements, uint256 modulus) =
            abi.decode(ret, (uint256, uint256));
        if (fieldElements != FIELD_ELEMENTS_PER_BLOB || modulus != BLS_MODULUS) {
            revert InvalidPrecompileReturn(ret);
        }

        versionedHash = bytes32(input[0:32]);
        z = bytes32(input[32:64]);
        y = bytes32(input[64:96]);
    }
}
