Aere Network public source. Everything here can be checked against the live chain (chain id 2800, https://rpc.aere.network). Scope note, stated up front rather than buried: consensus on chain 2800 is classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at the signature, precompile, account and transport layers. Nothing here makes the consensus post-quantum, and no document in it should be read as claiming so.
171 lines
6.3 KiB
Solidity
171 lines
6.3 KiB
Solidity
// 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]);
|
|
}
|
|
}
|