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.
344 lines
18 KiB
Solidity
344 lines
18 KiB
Solidity
// 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);
|
|
}
|
|
}
|