// 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]); } }