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.
212 lines
10 KiB
Solidity
212 lines
10 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.23;
|
|
|
|
import "@openzeppelin/contracts/utils/Strings.sol";
|
|
import "./IERC8004.sol";
|
|
|
|
/// @dev The subset of the LIVE, deployed AereAgentDID (chain 2800, 0xce64…22C5) this adapter reads.
|
|
/// The DID is the Aere-native agent identity: an agent whose ROOT authority is a Falcon key in
|
|
/// AerePQCKeyRegistry. This adapter never writes to the DID and never redeploys it.
|
|
interface IAereAgentDIDIdentity {
|
|
function agentExists(uint256 rootKeyId) external view returns (bool);
|
|
|
|
function getAgent(uint256 rootKeyId)
|
|
external
|
|
view
|
|
returns (address controller, uint64 createdBlock, uint64 sessionNonce, uint256 sessionCountForAgent);
|
|
|
|
function keyRegistry() external view returns (address);
|
|
}
|
|
|
|
/// @dev The one AerePQCKeyRegistry read used to resolve an agent's CURRENT controller.
|
|
interface IAerePQCKeyRegistryOwner {
|
|
function ownerOf(uint256 keyId) external view returns (address);
|
|
}
|
|
|
|
/**
|
|
* @title AereIdentityRegistry8004, an ERC-8004 Identity adapter over the deployed AereAgentDID
|
|
*
|
|
* @notice Exposes the ERC-8004 Identity registry surface (register / resolve an agent with a DID
|
|
* plus an AgentCard metadata URI) over Aere Network's ALREADY-DEPLOYED agent identity
|
|
* primitive, AereAgentDID. It does NOT redeploy or modify the live DID: it reads the live
|
|
* DID for agent existence and control, and stores only the ERC-8004 metadata (domain +
|
|
* AgentCard URI) that the DID itself does not carry.
|
|
*
|
|
* IDENTITY ANCHOR. The ERC-8004 agentId IS the AereAgentDID agent id, which is the Falcon
|
|
* ROOT key id in AerePQCKeyRegistry (agentId == rootKeyId). So an ERC-8004 resolve is a
|
|
* direct, honest pass-through to the live DID: the agent's on-chain root of authority is a
|
|
* quantum-durable Falcon key, not a bare secp256k1 address. The DID string is
|
|
* "did:aere:<chainId>:<agentId>".
|
|
*
|
|
* REGISTER = BIND METADATA TO AN EXISTING DID AGENT. `register` does not mint a new
|
|
* identity (the identity already exists in the live DID). It binds an ERC-8004 AgentCard
|
|
* URI + domain to an existing DID agent, and is gated to that agent's CURRENT controller
|
|
* (the registry owner of the Falcon root key), so only the party who proved Falcon
|
|
* possession can publish the agent's ERC-8004 metadata.
|
|
*
|
|
* @dev [VERIFY: confirm against the finalized ERC-8004 interface] The newer ERC-8004 Identity
|
|
* draft leans on an ERC-721 registration (register(tokenURI) minting a sequential id). Aere
|
|
* anchors the ERC-8004 identity to the pre-existing Falcon-rooted DID id instead of minting
|
|
* a fresh sequential id, because the trust root is the on-chain Falcon key, not a token.
|
|
* The resolve / getAgent / AgentCard-URI SEMANTICS match ERC-8004; the id-minting selector
|
|
* differs by design and is documented here.
|
|
*
|
|
* HONEST SCOPE. Application-layer identity only. This does NOT change AERE consensus (blocks
|
|
* are still Besu QBFT with classical secp256k1 ECDSA). It holds no funds and has no admin.
|
|
*/
|
|
contract AereIdentityRegistry8004 is IERC8004Identity {
|
|
using Strings for uint256;
|
|
|
|
/// @notice The LIVE, deployed AereAgentDID this adapter presents an ERC-8004 face over.
|
|
IAereAgentDIDIdentity public immutable DID;
|
|
|
|
/// @notice The AerePQCKeyRegistry the DID roots into (read from the DID at construction). Used
|
|
/// only to resolve an agent's current controller (the registry owner of the root key).
|
|
IAerePQCKeyRegistryOwner public immutable KEY_REGISTRY;
|
|
|
|
struct Record {
|
|
bool registered;
|
|
string agentDomain;
|
|
string agentCardURI;
|
|
address boundController; // controller captured at registration (immutable per Falcon root key)
|
|
}
|
|
|
|
mapping(uint256 => Record) private _records; // agentId (== rootKeyId) => ERC-8004 metadata
|
|
mapping(bytes32 => uint256) private _domainToAgentPlus1; // keccak(domain) => agentId + 1 (0 = unset)
|
|
mapping(address => uint256[]) private _agentsOfAddress; // controller => agentIds it registered
|
|
uint256[] private _registeredAgentIds; // append-only list of all registered agentIds
|
|
|
|
error ZeroAddress();
|
|
error UnknownAgent(uint256 agentId);
|
|
error NotController(uint256 agentId);
|
|
error AlreadyRegistered(uint256 agentId);
|
|
error NotRegistered(uint256 agentId);
|
|
error EmptyDomain();
|
|
error DomainTaken(string agentDomain);
|
|
|
|
constructor(address did) {
|
|
if (did == address(0)) revert ZeroAddress();
|
|
DID = IAereAgentDIDIdentity(did);
|
|
address reg = IAereAgentDIDIdentity(did).keyRegistry();
|
|
if (reg == address(0)) revert ZeroAddress();
|
|
KEY_REGISTRY = IAerePQCKeyRegistryOwner(reg);
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Registration (metadata binding)
|
|
// ==========================================================================
|
|
|
|
/**
|
|
* @notice Bind ERC-8004 metadata (domain + AgentCard URI) to an existing AereAgentDID agent.
|
|
* Gated to the agent's CURRENT controller (the registry owner of the Falcon root key).
|
|
* Does not mint a new identity: the identity already lives in the live DID.
|
|
* @param agentId the AereAgentDID agent id (== the Falcon root key id).
|
|
* @param agentDomain the agent's domain (non-empty, unique across this adapter).
|
|
* @param agentCardURI the AgentCard / registration metadata document URI.
|
|
*/
|
|
function register(uint256 agentId, string calldata agentDomain, string calldata agentCardURI) external {
|
|
if (!DID.agentExists(agentId)) revert UnknownAgent(agentId);
|
|
address controller = KEY_REGISTRY.ownerOf(agentId);
|
|
if (controller == address(0) || msg.sender != controller) revert NotController(agentId);
|
|
if (_records[agentId].registered) revert AlreadyRegistered(agentId);
|
|
if (bytes(agentDomain).length == 0) revert EmptyDomain();
|
|
|
|
bytes32 dk = keccak256(bytes(agentDomain));
|
|
if (_domainToAgentPlus1[dk] != 0) revert DomainTaken(agentDomain);
|
|
|
|
_records[agentId] =
|
|
Record({registered: true, agentDomain: agentDomain, agentCardURI: agentCardURI, boundController: controller});
|
|
_domainToAgentPlus1[dk] = agentId + 1;
|
|
_agentsOfAddress[controller].push(agentId);
|
|
_registeredAgentIds.push(agentId);
|
|
|
|
emit AgentRegistered(agentId, agentDomain, agentCardURI, controller);
|
|
}
|
|
|
|
/**
|
|
* @notice Update the ERC-8004 metadata for an already-registered agent. Controller-gated.
|
|
* Re-indexes the domain (the new domain must be free unless unchanged).
|
|
*/
|
|
function updateAgent(uint256 agentId, string calldata newDomain, string calldata newCardURI) external {
|
|
Record storage r = _records[agentId];
|
|
if (!r.registered) revert NotRegistered(agentId);
|
|
address controller = KEY_REGISTRY.ownerOf(agentId);
|
|
if (controller == address(0) || msg.sender != controller) revert NotController(agentId);
|
|
if (bytes(newDomain).length == 0) revert EmptyDomain();
|
|
|
|
bytes32 oldKey = keccak256(bytes(r.agentDomain));
|
|
bytes32 newKey = keccak256(bytes(newDomain));
|
|
if (newKey != oldKey) {
|
|
if (_domainToAgentPlus1[newKey] != 0) revert DomainTaken(newDomain);
|
|
delete _domainToAgentPlus1[oldKey];
|
|
_domainToAgentPlus1[newKey] = agentId + 1;
|
|
r.agentDomain = newDomain;
|
|
}
|
|
r.agentCardURI = newCardURI;
|
|
|
|
emit AgentUpdated(agentId, newDomain, newCardURI, controller);
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Resolve
|
|
// ==========================================================================
|
|
|
|
/// @inheritdoc IERC8004Identity
|
|
function getAgent(uint256 agentId) public view returns (AgentInfo memory) {
|
|
Record storage r = _records[agentId];
|
|
if (!r.registered) revert NotRegistered(agentId);
|
|
return AgentInfo({
|
|
agentId: agentId,
|
|
did: didString(agentId),
|
|
agentDomain: r.agentDomain,
|
|
agentCardURI: r.agentCardURI,
|
|
agentAddress: KEY_REGISTRY.ownerOf(agentId), // current controller, read live
|
|
registered: true
|
|
});
|
|
}
|
|
|
|
/// @inheritdoc IERC8004Identity
|
|
function resolveByDomain(string calldata agentDomain) external view returns (AgentInfo memory) {
|
|
uint256 plus1 = _domainToAgentPlus1[keccak256(bytes(agentDomain))];
|
|
if (plus1 == 0) revert NotRegistered(0);
|
|
return getAgent(plus1 - 1);
|
|
}
|
|
|
|
/// @inheritdoc IERC8004Identity
|
|
/// @dev Returns the MOST RECENT agent registered by `agentAddress`. Use agentsOf() for the full set.
|
|
function resolveByAddress(address agentAddress) external view returns (AgentInfo memory) {
|
|
uint256[] storage ids = _agentsOfAddress[agentAddress];
|
|
if (ids.length == 0) revert NotRegistered(0);
|
|
return getAgent(ids[ids.length - 1]);
|
|
}
|
|
|
|
/// @inheritdoc IERC8004Identity
|
|
function agentCount() external view returns (uint256) {
|
|
return _registeredAgentIds.length;
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Views
|
|
// ==========================================================================
|
|
|
|
/// @notice True iff ERC-8004 metadata has been bound for `agentId` (and the DID agent exists).
|
|
function isRegistered(uint256 agentId) external view returns (bool) {
|
|
return _records[agentId].registered && DID.agentExists(agentId);
|
|
}
|
|
|
|
/// @notice All agentIds registered by a controller (append-only; ids are stable Falcon roots).
|
|
function agentsOf(address controller) external view returns (uint256[] memory) {
|
|
return _agentsOfAddress[controller];
|
|
}
|
|
|
|
/// @notice The full list of registered agentIds (append-only).
|
|
function registeredAgentIds() external view returns (uint256[] memory) {
|
|
return _registeredAgentIds;
|
|
}
|
|
|
|
/// @notice The DID string for an agent: "did:aere:<chainId>:<agentId>".
|
|
function didString(uint256 agentId) public view returns (string memory) {
|
|
return string.concat("did:aere:", block.chainid.toString(), ":", agentId.toString());
|
|
}
|
|
}
|