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.
87 lines
4.1 KiB
Solidity
87 lines
4.1 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.19;
|
|
|
|
/**
|
|
* @title AereIdentity
|
|
* @notice Lightweight on-chain DID registry. Each address controls its own identity:
|
|
* off-chain signed credentials, profile metadata URI, and revocable claims
|
|
* issued by trusted attestors (KYC providers, employers, communities).
|
|
* @dev Compatible-in-spirit with W3C DID + ERC-1056. Profile metadata stored as a
|
|
* content-addressed URI (e.g. ipfs://...) so the chain holds only the pointer.
|
|
*/
|
|
contract AereIdentity {
|
|
// ── Profiles ────────────────────────────────────────────────
|
|
mapping(address => string) public profileURI;
|
|
mapping(address => uint64) public profileUpdatedAt;
|
|
|
|
event ProfileUpdated(address indexed identity, string uri, uint64 timestamp);
|
|
|
|
function setProfile(string calldata uri) external {
|
|
profileURI[msg.sender] = uri;
|
|
profileUpdatedAt[msg.sender] = uint64(block.timestamp);
|
|
emit ProfileUpdated(msg.sender, uri, uint64(block.timestamp));
|
|
}
|
|
|
|
// ── Attestors ───────────────────────────────────────────────
|
|
mapping(address => bool) public isAttestor;
|
|
address public owner;
|
|
|
|
event AttestorAdded(address indexed attestor);
|
|
event AttestorRemoved(address indexed attestor);
|
|
|
|
constructor() { owner = msg.sender; isAttestor[msg.sender] = true; }
|
|
|
|
modifier onlyOwner() { require(msg.sender == owner, "owner"); _; }
|
|
|
|
function addAttestor(address a) external onlyOwner { isAttestor[a] = true; emit AttestorAdded(a); }
|
|
function removeAttestor(address a) external onlyOwner { isAttestor[a] = false; emit AttestorRemoved(a); }
|
|
function transferOwnership(address newOwner) external onlyOwner { require(newOwner != address(0)); owner = newOwner; }
|
|
|
|
// ── Claims ──────────────────────────────────────────────────
|
|
/// @dev claimType examples: keccak256("KYC_TIER_1"), keccak256("EMAIL_VERIFIED"),
|
|
/// keccak256("DAO_MEMBER:ENS"), keccak256("AGE_OVER_18"), etc.
|
|
struct Claim {
|
|
address attestor;
|
|
uint64 issuedAt;
|
|
uint64 expiresAt; // 0 = never
|
|
bytes32 evidence; // optional hash of off-chain document
|
|
bool revoked;
|
|
}
|
|
|
|
// subject => claimType => attestor => Claim
|
|
mapping(address => mapping(bytes32 => mapping(address => Claim))) public claims;
|
|
|
|
event ClaimIssued(address indexed subject, bytes32 indexed claimType, address indexed attestor, uint64 expiresAt, bytes32 evidence);
|
|
event ClaimRevoked(address indexed subject, bytes32 indexed claimType, address indexed attestor);
|
|
|
|
function issueClaim(address subject, bytes32 claimType, uint64 expiresAt, bytes32 evidence) external {
|
|
require(isAttestor[msg.sender], "not attestor");
|
|
require(subject != address(0), "subject");
|
|
claims[subject][claimType][msg.sender] = Claim({
|
|
attestor: msg.sender,
|
|
issuedAt: uint64(block.timestamp),
|
|
expiresAt: expiresAt,
|
|
evidence: evidence,
|
|
revoked: false
|
|
});
|
|
emit ClaimIssued(subject, claimType, msg.sender, expiresAt, evidence);
|
|
}
|
|
|
|
function revokeClaim(address subject, bytes32 claimType) external {
|
|
Claim storage c = claims[subject][claimType][msg.sender];
|
|
require(c.attestor == msg.sender, "not issuer");
|
|
require(!c.revoked, "already");
|
|
c.revoked = true;
|
|
emit ClaimRevoked(subject, claimType, msg.sender);
|
|
}
|
|
|
|
/// @notice Check if a subject has a non-expired, non-revoked claim of a given type
|
|
/// from a specific attestor.
|
|
function hasValidClaim(address subject, bytes32 claimType, address attestor) external view returns (bool) {
|
|
Claim memory c = claims[subject][claimType][attestor];
|
|
if (c.attestor == address(0) || c.revoked) return false;
|
|
if (c.expiresAt != 0 && block.timestamp > c.expiresAt) return false;
|
|
return true;
|
|
}
|
|
}
|