aere-contracts/contracts/erc8004/AereValidationRegistry8004.sol
Aere Network a13a649b77
Some checks are pending
contracts-ci / Install (lockfile) → compile → full test suite (push) Waiting to run
contracts-ci / PQC known-answer tests (NIST vectors) (push) Waiting to run
contracts-ci / Coverage (scoped, with artifacts) (push) Waiting to run
Initial public release
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.
2026-07-20 01:02:37 +03:00

266 lines
13 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import "./IERC8004.sol";
/// @dev The subset of the LIVE, deployed AerePQCKeyRegistry (chain 2800, 0x1eCa…3691) this adapter
/// uses. verifyWithKey routes to the live native precompile for the key's scheme (0x0AE1
/// Falcon-512, 0x0AE3 ML-DSA-44) and verifies the signature IN FULL on-chain, fail-closed.
interface IAerePQCKeyRegistryValidation {
function verifyWithKey(uint256 keyId, bytes32 message, bytes calldata signature) external view returns (bool);
function statusOf(uint256 keyId) external view returns (uint8);
function schemeOf(uint256 keyId) external view returns (uint8);
}
/// @dev The one AereAgentDID read used to confirm the server agent being validated actually exists.
interface IAereAgentDIDValidation {
function agentExists(uint256 rootKeyId) external view returns (bool);
}
/**
* @title AereValidationRegistry8004, the post-quantum ERC-8004 Validation registry
*
* @notice The differentiator. Exposes the ERC-8004 Validation surface (request / respond with a
* proof) where the validator's RESPONSE proof is a POST-QUANTUM signature, Falcon-512 or
* ML-DSA-44, verified in full ON-CHAIN by Aere Network's LIVE native precompiles
* (0x0AE1 Falcon-512, 0x0AE3 ML-DSA-44) through the already-deployed AerePQCKeyRegistry.
* Every other agent-validation stack in the ecosystem authenticates validator responses
* with ECDSA, which a quantum computer forges; this one does not.
*
* FLOW.
* 1. requestValidation(validatorKeyId, serverAgentId, dataHash): anyone records a request
* that the PQC validator identified by `validatorKeyId` validate the work the server
* agent committed to via `dataHash`. The validator key must be an ACTIVE Falcon-512 or
* ML-DSA-44 key in the registry; the server agent must exist in the live AereAgentDID.
* 2. respondValidation(requestId, response, pqcProof): the validator answers with a score
* (0..100, the ERC-8004 convention) and a PQC signature over this contract's
* domain-separated challenge binding (requestId, dataHash, serverAgentId,
* validatorKeyId, response). The signature is verified on-chain via the live
* precompile. On success the response is recorded once; on any invalid or tampered
* proof the call REVERTS and records nothing (FAIL-CLOSED).
*
* @dev NON-REPLAYABLE / NON-FORGEABLE. The challenge binds chainId + this contract + the exact
* request tuple + the response, so a proof for one (request, response) can never be reused
* for another, another contract, or another chain. A request completes exactly once
* (respond reverts once Status != Pending). Authorship is the PQC signature alone: respond
* may be relayed by any msg.sender, since only the validator private key can produce a valid
* signature over the contract-bound challenge. An empty / zero precompile return (a stale,
* un-activated chain) is treated as INVALID.
*
* @dev [VERIFY: confirm against the finalized ERC-8004 interface] ERC-8004 validation in the base
* draft identifies the validator by an agent id and carries an opaque response byte. Aere
* keeps request/respond SEMANTICS but binds the validator to a REGISTERED POST-QUANTUM KEY
* (so the response is cryptographically authenticated on-chain, not merely emitted) and
* keeps the response in the ERC-8004 0..100 range. Reconcile exact selectors with the final
* ERC before claiming selector-level conformance.
*
* HONEST SCOPE. Application-layer validation only. This does NOT make AERE consensus
* post-quantum (blocks are still Besu QBFT with classical secp256k1 ECDSA). It holds no
* funds and has no admin. What is post-quantum here is the AUTHENTICITY of a validator's
* response, verified by the live precompiles.
*/
contract AereValidationRegistry8004 is IERC8004Validation {
/// @notice PQC schemes accepted for a validator key (match AerePQCKeyRegistry scheme ids).
uint8 public constant SCHEME_FALCON512 = 1; // live precompile 0x0AE1
uint8 public constant SCHEME_MLDSA44 = 3; // live precompile 0x0AE3
uint8 internal constant REGISTRY_STATUS_ACTIVE = 1;
uint8 public constant MAX_RESPONSE = 100; // ERC-8004 response score range 0..100
/// @notice Domain separator for the response challenge the validator PQC-signs.
bytes32 public constant VALIDATION_DOMAIN = keccak256("AereValidationRegistry8004.v1.response");
/// @notice The LIVE AerePQCKeyRegistry that verifies validator PQC signatures via the precompiles.
IAerePQCKeyRegistryValidation public immutable KEY_REGISTRY;
/// @notice The LIVE AereAgentDID used to confirm the server agent under validation exists.
IAereAgentDIDValidation public immutable DID;
struct Request {
uint256 validatorKeyId;
uint256 serverAgentId;
bytes32 dataHash;
Status status;
uint8 response; // valid only once status == Completed
uint8 pqcScheme; // the validator key's scheme at response time
uint64 requestedBlock;
uint64 respondedBlock;
}
mapping(bytes32 => Request) private _requests; // requestId => request
mapping(uint256 => uint256) public requestNonce; // validatorKeyId => monotonic request counter
bytes32[] private _requestIds; // append-only list of every requestId
error ZeroAddress();
error ValidatorKeyNotActive(uint256 validatorKeyId);
error ValidatorSchemeUnsupported(uint256 validatorKeyId, uint8 scheme);
error ServerAgentUnknown(uint256 serverAgentId);
error UnknownRequest(bytes32 requestId);
error RequestNotPending(bytes32 requestId);
error ResponseOutOfRange(uint8 response);
error PQCValidationFailed();
constructor(address keyRegistry_, address did_) {
if (keyRegistry_ == address(0) || did_ == address(0)) revert ZeroAddress();
KEY_REGISTRY = IAerePQCKeyRegistryValidation(keyRegistry_);
DID = IAereAgentDIDValidation(did_);
}
// ==========================================================================
// Request
// ==========================================================================
/// @inheritdoc IERC8004Validation
function requestValidation(uint256 validatorKeyId, uint256 serverAgentId, bytes32 dataHash)
external
returns (bytes32 requestId)
{
uint8 scheme = _requireActivePQCValidator(validatorKeyId);
if (!DID.agentExists(serverAgentId)) revert ServerAgentUnknown(serverAgentId);
uint256 n = requestNonce[validatorKeyId];
requestNonce[validatorKeyId] = n + 1;
requestId = keccak256(
abi.encode(VALIDATION_DOMAIN, block.chainid, address(this), validatorKeyId, serverAgentId, dataHash, n)
);
_requests[requestId] = Request({
validatorKeyId: validatorKeyId,
serverAgentId: serverAgentId,
dataHash: dataHash,
status: Status.Pending,
response: 0,
pqcScheme: scheme,
requestedBlock: uint64(block.number),
respondedBlock: 0
});
_requestIds.push(requestId);
emit ValidationRequest(requestId, validatorKeyId, serverAgentId, dataHash);
}
// ==========================================================================
// Respond (PQC-verified)
// ==========================================================================
/// @inheritdoc IERC8004Validation
/// @notice Respond to a pending validation with a score and a POST-QUANTUM proof. The proof is a
/// Falcon-512 or ML-DSA-44 signature by the validator key over this contract's challenge,
/// verified in full on-chain by the live precompile. Reverts (records nothing) on an
/// invalid or tampered proof (FAIL-CLOSED).
/// @param requestId the pending request being answered.
/// @param response the validation score, 0..100 (ERC-8004 convention).
/// @param pqcProof the validator's PQC signature envelope for the key's scheme.
function respondValidation(bytes32 requestId, uint8 response, bytes calldata pqcProof) external {
Request storage r = _requests[requestId];
if (r.status == Status.None) revert UnknownRequest(requestId);
if (r.status != Status.Pending) revert RequestNotPending(requestId);
if (response > MAX_RESPONSE) revert ResponseOutOfRange(response);
// The validator key must still be an ACTIVE Falcon-512 / ML-DSA-44 key (it could have been
// rotated or revoked between request and response).
uint8 scheme = _requireActivePQCValidator(r.validatorKeyId);
bytes32 challenge = responseChallenge(requestId, response);
// Full on-chain post-quantum verification via the live precompile through the registry.
if (!KEY_REGISTRY.verifyWithKey(r.validatorKeyId, challenge, pqcProof)) revert PQCValidationFailed();
// Effects: record exactly once.
r.status = Status.Completed;
r.response = response;
r.pqcScheme = scheme;
r.respondedBlock = uint64(block.number);
emit ValidationResponse(requestId, r.validatorKeyId, r.serverAgentId, response, scheme);
}
/**
* @notice The exact 32-byte challenge a validator must PQC-sign to answer `requestId` with
* `response`. Off-chain validators query this to know what to sign; the contract binds
* the request tuple so a proof cannot be moved to a different request or response.
*/
function responseChallenge(bytes32 requestId, uint8 response) public view returns (bytes32) {
Request storage r = _requests[requestId];
return keccak256(
abi.encode(
VALIDATION_DOMAIN,
block.chainid,
address(this),
requestId,
r.dataHash,
r.serverAgentId,
r.validatorKeyId,
response
)
);
}
// ==========================================================================
// Views
// ==========================================================================
/// @notice Read a validation request/response by id.
function getValidation(bytes32 requestId)
external
view
returns (
uint256 validatorKeyId,
uint256 serverAgentId,
bytes32 dataHash,
Status status,
uint8 response,
uint8 pqcScheme,
uint64 requestedBlock,
uint64 respondedBlock
)
{
Request storage r = _requests[requestId];
if (r.status == Status.None) revert UnknownRequest(requestId);
return (
r.validatorKeyId,
r.serverAgentId,
r.dataHash,
r.status,
r.response,
r.pqcScheme,
r.requestedBlock,
r.respondedBlock
);
}
/// @notice True iff `requestId` has a recorded, PQC-verified validation response.
function isValidated(bytes32 requestId) external view returns (bool) {
return _requests[requestId].status == Status.Completed;
}
/// @notice Total number of validation requests ever recorded.
function requestCount() external view returns (uint256) {
return _requestIds.length;
}
/// @notice The requestId at an index in the append-only list.
function requestIdAt(uint256 index) external view returns (bytes32) {
return _requestIds[index];
}
/// @notice The live precompile address that verifies a validator key's scheme (informational).
function precompileFor(uint8 scheme) external pure returns (address) {
if (scheme == SCHEME_FALCON512) return address(0x0AE1);
if (scheme == SCHEME_MLDSA44) return address(0x0AE3);
return address(0);
}
// ==========================================================================
// Internal
// ==========================================================================
/// @dev Require `keyId` is an ACTIVE Falcon-512 or ML-DSA-44 key in the live registry. Returns
/// the scheme.
function _requireActivePQCValidator(uint256 keyId) internal view returns (uint8 scheme) {
if (KEY_REGISTRY.statusOf(keyId) != REGISTRY_STATUS_ACTIVE) revert ValidatorKeyNotActive(keyId);
scheme = KEY_REGISTRY.schemeOf(keyId);
if (scheme != SCHEME_FALCON512 && scheme != SCHEME_MLDSA44) {
revert ValidatorSchemeUnsupported(keyId, scheme);
}
}
}