// SPDX-License-Identifier: MIT pragma solidity 0.8.23; import "@openzeppelin/contracts/access/Ownable.sol"; /// @dev The subset of the LIVE, deployed AerePQCKeyRegistry (chain 2800, 0x1eCa...3691) this registry /// uses to verify an issuer's POST-QUANTUM attestation. verifyWithKey routes to the live native /// precompile for the key's scheme (0x0AE1 Falcon-512) and verifies the signature IN FULL /// on-chain, fail-closed. interface IAerePQCKeyRegistryTrust { 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); } /** * @title AereTrustRegistry, a federated registry of ACCREDITED credential issuers for Aere Network * * @notice The trust root for Aere's institution-grade compliance passport. Compliance trust is NOT * a single Foundation key: it is a FEDERATED SET of accredited issuers, each authorized to * issue a defined set of credential types. This mirrors, in spirit, the eIDAS 2.0 / EUDI * "trusted list" model, in which a supervisory body maintains a machine-readable list of * qualified trust service providers and the services they are accredited for. Here the Aere * Foundation is the trusted-list operator (the registry owner); each entry is an accredited * issuer with a DID, an accreditation SCOPE (the credential types it may issue), a lifecycle * STATUS, and an OPTIONAL post-quantum (Falcon-512) key reference so the issuer's attestations * can be PQC-signed and verified on-chain via the live precompile 0x0AE1. * * WHY POST-QUANTUM. Every accreditation stack in production today authenticates an issuer's * attestation with a classical ECDSA / RSA signature, which a cryptographically relevant * quantum computer forges. An Aere issuer additionally binds a Falcon-512 key here, so its * attestations are verified with a NIST post-quantum signature scheme in full on-chain. That * is the differentiator this registry exists to carry. * * DESIGN PRINCIPLES. * - FAIL-CLOSED. An unknown issuer, a suspended or revoked issuer, or a credential type * outside an issuer's scope all resolve to NOT accredited. isAccredited never returns a * false positive. * - APPEND-ONLY HISTORY, NO SILENT REWRITE. Every status transition and every scope change * is pushed to an append-only history. The current status is a pointer; the record of how * it got there is immutable and fully auditable. * - TERMINAL REVOCATION. Accreditation revocation is terminal (a revoked issuer can never * be reinstated). Suspension is reversible. * * @dev HONEST SCOPE. This is application-layer compliance tooling. It changes NOTHING about Aere * consensus, which stays classical secp256k1 ECDSA QBFT. What is post-quantum here is the * AUTHENTICITY of an issuer attestation, verified by the live precompile. This contract holds * no funds. Whether any REAL institution is accredited, and whether such accreditation carries * legal weight under eIDAS 2.0, is a partnership / regulatory matter that is external to this * code and is NOT asserted by deploying it. * * @dev No PII on chain. An issuer entry stores a DID (a public identifier, not personal data) and * opaque bytes32 credential-type tags. Reason codes on status transitions are opaque bytes32. */ contract AereTrustRegistry is Ownable { /// @notice Falcon-512 scheme id in AerePQCKeyRegistry (live precompile 0x0AE1). uint8 public constant SCHEME_FALCON512 = 1; /// @notice AerePQCKeyRegistry.STATUS_ACTIVE (only an ACTIVE key is a current key). uint8 internal constant KEY_STATUS_ACTIVE = 1; /// @notice Sentinel for "no post-quantum key linked to this issuer". uint256 public constant NO_KEY = type(uint256).max; /// @notice Issuer accreditation lifecycle. None=never accredited; Active=may issue in scope; /// Suspended=temporarily halted (reversible); Revoked=terminal. enum Status { None, Active, Suspended, Revoked } /// @notice The LIVE AerePQCKeyRegistry used to verify an issuer's Falcon-512 attestations. IAerePQCKeyRegistryTrust public immutable KEY_REGISTRY; struct Issuer { address controller; // the issuer id / controlling address Status status; // current lifecycle status (history is append-only, see below) uint256 pqcKeyId; // Falcon-512 keyId in KEY_REGISTRY, or NO_KEY uint64 accreditedAt; // block timestamp of first accreditation string did; // the issuer's did:aere DID (public identifier, not PII) } /// @notice One append-only record per status transition. No entry is ever rewritten. struct StatusEvent { Status status; uint64 blockNumber; uint64 timestamp; bytes32 reason; // opaque reason code (e.g. keccak256("audit-lapsed")); never PII } /// @notice One append-only record per scope change. struct ScopeEvent { bytes32 credentialType; bool granted; // true = granted, false = removed uint64 timestamp; } // issuerId is the index into _issuers. Index 0 is a reserved placeholder so issuerIdByAddress's // default 0 unambiguously means "no such issuer"; real issuers have issuerId >= 1. Issuer[] private _issuers; mapping(address => uint256) public issuerIdByAddress; // controller => issuerId (0 = none) mapping(uint256 => mapping(bytes32 => bool)) private _inScope; // issuerId => credentialType => granted mapping(uint256 => bytes32[]) private _scopeList; // issuerId => append-only granted-type list (may repeat across grant/revoke cycles) mapping(uint256 => StatusEvent[]) private _statusHistory; // issuerId => append-only status log mapping(uint256 => ScopeEvent[]) private _scopeHistory; // issuerId => append-only scope log event IssuerAccredited(uint256 indexed issuerId, address indexed controller, string did, uint256 pqcKeyId); event IssuerStatusChanged(uint256 indexed issuerId, Status indexed oldStatus, Status indexed newStatus, bytes32 reason); event IssuerScopeChanged(uint256 indexed issuerId, bytes32 indexed credentialType, bool granted); event IssuerPqcKeySet(uint256 indexed issuerId, uint256 oldKeyId, uint256 newKeyId); error ZeroAddress(); error EmptyDid(); error AlreadyAccredited(address controller); error UnknownIssuer(uint256 issuerId); error NotActive(uint256 issuerId); error NotSuspended(uint256 issuerId); error AlreadyRevoked(uint256 issuerId); error PqcKeyNotActiveFalcon(uint256 keyId); error ScopeAlreadyGranted(uint256 issuerId, bytes32 credentialType); error ScopeNotGranted(uint256 issuerId, bytes32 credentialType); constructor(address keyRegistry_) { if (keyRegistry_ == address(0)) revert ZeroAddress(); KEY_REGISTRY = IAerePQCKeyRegistryTrust(keyRegistry_); // Reserve index 0 as an inert placeholder so issuerId 0 is never a real issuer. _issuers.push(Issuer({controller: address(0), status: Status.None, pqcKeyId: NO_KEY, accreditedAt: 0, did: ""})); } // ========================================================================== // Accreditation (owner) // ========================================================================== /** * @notice Accredit a new issuer with an initial scope and an optional Falcon-512 key reference. * Owner-only (the Foundation as trusted-list operator). The controller address is the * issuer id and the party that controls the linked credential-issuing keys. * @param controller the issuer's controlling address (also its issuer id / address). * @param did the issuer's did:aere DID (public identifier; not validated for full DID * syntax here, that is an off-chain concern, see [VERIFY] in the docs). * @param scopes the credential types this issuer may issue (opaque bytes32 tags). * @param pqcKeyId a Falcon-512 keyId in KEY_REGISTRY, or NO_KEY for no post-quantum key. * @return issuerId the assigned issuer id (>= 1). */ function accreditIssuer(address controller, string calldata did, bytes32[] calldata scopes, uint256 pqcKeyId) external onlyOwner returns (uint256 issuerId) { if (controller == address(0)) revert ZeroAddress(); if (bytes(did).length == 0) revert EmptyDid(); if (issuerIdByAddress[controller] != 0) revert AlreadyAccredited(controller); if (pqcKeyId != NO_KEY) _requireActiveFalconKey(pqcKeyId); issuerId = _issuers.length; _issuers.push( Issuer({ controller: controller, status: Status.Active, pqcKeyId: pqcKeyId, accreditedAt: uint64(block.timestamp), did: did }) ); issuerIdByAddress[controller] = issuerId; for (uint256 i = 0; i < scopes.length; i++) { _grantScope(issuerId, scopes[i]); } _statusHistory[issuerId].push( StatusEvent({status: Status.Active, blockNumber: uint64(block.number), timestamp: uint64(block.timestamp), reason: bytes32(0)}) ); emit IssuerAccredited(issuerId, controller, did, pqcKeyId); } /// @notice Grant an additional credential type to an issuer's accreditation scope (owner-only). function grantScope(uint256 issuerId, bytes32 credentialType) external onlyOwner { _requireIssuer(issuerId); _grantScope(issuerId, credentialType); } /// @notice Remove a credential type from an issuer's scope (owner-only). Append-only history keeps /// the record of the grant and the removal; the current scope simply no longer includes it. function revokeScope(uint256 issuerId, bytes32 credentialType) external onlyOwner { _requireIssuer(issuerId); if (!_inScope[issuerId][credentialType]) revert ScopeNotGranted(issuerId, credentialType); _inScope[issuerId][credentialType] = false; _scopeHistory[issuerId].push(ScopeEvent({credentialType: credentialType, granted: false, timestamp: uint64(block.timestamp)})); emit IssuerScopeChanged(issuerId, credentialType, false); } /// @notice Suspend an active issuer (reversible). All its credentials fail verification until /// reinstated (fail-closed). Owner-only. function suspendIssuer(uint256 issuerId, bytes32 reason) external onlyOwner { _requireIssuer(issuerId); if (_issuers[issuerId].status != Status.Active) revert NotActive(issuerId); _transition(issuerId, Status.Suspended, reason); } /// @notice Reinstate a suspended issuer to Active (owner-only). function reinstateIssuer(uint256 issuerId, bytes32 reason) external onlyOwner { _requireIssuer(issuerId); if (_issuers[issuerId].status != Status.Suspended) revert NotSuspended(issuerId); _transition(issuerId, Status.Active, reason); } /// @notice Permanently revoke an issuer's accreditation (terminal, not reversible). Owner-only. function revokeIssuer(uint256 issuerId, bytes32 reason) external onlyOwner { _requireIssuer(issuerId); if (_issuers[issuerId].status == Status.Revoked) revert AlreadyRevoked(issuerId); _transition(issuerId, Status.Revoked, reason); } /// @notice Set or clear an issuer's Falcon-512 key reference (owner-only). Pass NO_KEY to clear. function setIssuerPqcKey(uint256 issuerId, uint256 pqcKeyId) external onlyOwner { _requireIssuer(issuerId); if (pqcKeyId != NO_KEY) _requireActiveFalconKey(pqcKeyId); uint256 old = _issuers[issuerId].pqcKeyId; _issuers[issuerId].pqcKeyId = pqcKeyId; emit IssuerPqcKeySet(issuerId, old, pqcKeyId); } // ========================================================================== // Accreditation + PQC verification (views) // ========================================================================== /// @notice True iff `issuerId` is ACTIVE and `credentialType` is currently in its scope. /// Fail-closed for unknown / suspended / revoked issuers and out-of-scope types. function isAccredited(uint256 issuerId, bytes32 credentialType) external view returns (bool) { if (issuerId == 0 || issuerId >= _issuers.length) return false; if (_issuers[issuerId].status != Status.Active) return false; return _inScope[issuerId][credentialType]; } /** * @notice Verify a POST-QUANTUM (Falcon-512) attestation by an issuer over a 32-byte message, * via the live precompile 0x0AE1 through the AerePQCKeyRegistry. Fail-closed: returns * false (never reverts) if the issuer is not Active, has no linked Falcon key, the key is * not an ACTIVE Falcon-512 key, or the signature is invalid / tampered. * @param issuerId the accredited issuer. * @param message the 32-byte message the issuer's Falcon key signed. * @param signature the Falcon-512 signature envelope (nonce(40) || esig). */ function verifyIssuerSignature(uint256 issuerId, bytes32 message, bytes calldata signature) external view returns (bool) { if (issuerId == 0 || issuerId >= _issuers.length) return false; Issuer storage iss = _issuers[issuerId]; if (iss.status != Status.Active) return false; uint256 keyId = iss.pqcKeyId; if (keyId == NO_KEY) return false; if (KEY_REGISTRY.statusOf(keyId) != KEY_STATUS_ACTIVE) return false; if (KEY_REGISTRY.schemeOf(keyId) != SCHEME_FALCON512) return false; return KEY_REGISTRY.verifyWithKey(keyId, message, signature); } // ========================================================================== // Reads // ========================================================================== /// @notice Number of issuer ids ever assigned (issuerId runs 1..issuerCount, id 0 is reserved). function issuerCount() external view returns (uint256) { return _issuers.length - 1; } /// @notice The current lifecycle status of an issuer (Status.None for an unknown id). function issuerStatus(uint256 issuerId) external view returns (Status) { if (issuerId == 0 || issuerId >= _issuers.length) return Status.None; return _issuers[issuerId].status; } /// @notice Read an issuer entry. Reverts on an unknown id. function getIssuer(uint256 issuerId) external view returns (address controller, Status status, uint256 pqcKeyId, uint64 accreditedAt, string memory did) { _requireIssuer(issuerId); Issuer storage iss = _issuers[issuerId]; return (iss.controller, iss.status, iss.pqcKeyId, iss.accreditedAt, iss.did); } /// @notice The Falcon-512 keyId linked to an issuer (NO_KEY if none / unknown issuer). function pqcKeyOf(uint256 issuerId) external view returns (uint256) { if (issuerId == 0 || issuerId >= _issuers.length) return NO_KEY; return _issuers[issuerId].pqcKeyId; } /// @notice The append-only status-history length for an issuer. function statusHistoryLength(uint256 issuerId) external view returns (uint256) { return _statusHistory[issuerId].length; } /// @notice Read one append-only status-history entry. function statusHistoryAt(uint256 issuerId, uint256 index) external view returns (StatusEvent memory) { return _statusHistory[issuerId][index]; } /// @notice The append-only scope-history length for an issuer. function scopeHistoryLength(uint256 issuerId) external view returns (uint256) { return _scopeHistory[issuerId].length; } /// @notice Read one append-only scope-history entry. function scopeHistoryAt(uint256 issuerId, uint256 index) external view returns (ScopeEvent memory) { return _scopeHistory[issuerId][index]; } // ========================================================================== // Internal // ========================================================================== function _requireIssuer(uint256 issuerId) internal view { if (issuerId == 0 || issuerId >= _issuers.length) revert UnknownIssuer(issuerId); } function _requireActiveFalconKey(uint256 keyId) internal view { if (KEY_REGISTRY.statusOf(keyId) != KEY_STATUS_ACTIVE || KEY_REGISTRY.schemeOf(keyId) != SCHEME_FALCON512) { revert PqcKeyNotActiveFalcon(keyId); } } function _grantScope(uint256 issuerId, bytes32 credentialType) internal { if (_inScope[issuerId][credentialType]) revert ScopeAlreadyGranted(issuerId, credentialType); _inScope[issuerId][credentialType] = true; _scopeList[issuerId].push(credentialType); _scopeHistory[issuerId].push(ScopeEvent({credentialType: credentialType, granted: true, timestamp: uint64(block.timestamp)})); emit IssuerScopeChanged(issuerId, credentialType, true); } function _transition(uint256 issuerId, Status newStatus, bytes32 reason) internal { Status old = _issuers[issuerId].status; _issuers[issuerId].status = newStatus; _statusHistory[issuerId].push( StatusEvent({status: newStatus, blockNumber: uint64(block.number), timestamp: uint64(block.timestamp), reason: reason}) ); emit IssuerStatusChanged(issuerId, old, newStatus, reason); } }