aere-contracts/contracts/AereIdentity.sol
Aere Network a13a649b77
Some checks are pending
contracts-ci / Install (lockfile) → compile → full test suite (push) Waiting to run
contracts-ci / PQC known-answer tests (NIST vectors) (push) Waiting to run
contracts-ci / Coverage (scoped, with artifacts) (push) Waiting to run
Initial public release
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.
2026-07-20 01:02:37 +03:00

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;
}
}