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