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.
266 lines
13 KiB
Solidity
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);
|
|
}
|
|
}
|
|
}
|