aere-contracts/contracts/validators/AereValidatorManifest.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

125 lines
5.3 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
/**
* @title AereValidatorManifest — public commitment registry for verification nodes
* @notice The 3 → 7 → 21 validator transition requires that anyone, anywhere,
* can verify which binary a validator is running and which operator
* is running it. This contract is the on-chain manifest of those
* commitments. It does NOT enroll validators (consensus enrollment is
* off-chain via Foundation Ledger + QBFT extraData); it makes the
* operator's claim machine-verifiable.
*
* For each validator a node operator registers:
* - validatorAddress QBFT signer (the address Besu uses to sign blocks)
* - operatorLabel human-readable name (e.g. "P2P Validator", "Figment")
* - operatorRegion e.g. "EU-Central / DE / Hetzner FSN-1"
* - binaryDigest SHA256 of the Besu image they're running
* - manifestUri where the binary digest's signed manifest lives
* (typically the published .well-known/besu-image-digest)
* - declaredAt unix seconds at which the operator made the claim
*
* A new commitment FOR THE SAME validatorAddress overwrites the old one,
* keyed by an incrementing serial — history is queryable via events.
* There is NO admin override; only the validator's own QBFT signer key
* can update its commitment. That ties the binary commitment to consensus
* participation: if the signer is compromised, so is the manifest, and
* everyone observes simultaneously.
*
* GOTCHA: this contract does not, and cannot, verify that the validator
* IS actually running the claimed binary. That verification is by
* third-party probing (block signature analysis, P2P handshake
* fingerprint) + the publicly reproducible build (`verify-besu-image.sh`).
* The contract gives a single canonical place to ATTEST so audits don't
* drift across mediums.
*
* WHY no admin: the manifest belongs to the operator, not the Foundation.
* The Foundation publishes its own commitment same as every other
* operator. "The chain nobody owns" is incompatible with a Foundation
* that can rewrite operator manifests.
*/
contract AereValidatorManifest {
struct Commitment {
bytes32 binaryDigest; // SHA256 (or other) of the Besu container image
string manifestUri; // ipfs:// or https:// link to the signed manifest
string operatorLabel;
string operatorRegion;
uint64 declaredAt;
uint256 serial; // monotonic per validator
}
/// @notice validator signer → most recent commitment.
mapping(address => Commitment) public latest;
/// @notice validator signer → next serial. Starts at 1.
mapping(address => uint256) public nextSerial;
/* ================================ events ================================ */
event CommitmentDeclared(
address indexed validator,
uint256 indexed serial,
bytes32 indexed binaryDigest,
string manifestUri,
string operatorLabel,
string operatorRegion,
uint64 declaredAt
);
/* ================================ errors ================================ */
error EmptyManifestUri();
error EmptyOperatorLabel();
error ZeroDigest();
/* =============================== writes =============================== */
/// @notice The validator (msg.sender == signer key) declares which binary
/// it is running + who is running it.
function declare(
bytes32 binaryDigest,
string calldata manifestUri,
string calldata operatorLabel,
string calldata operatorRegion
) external {
if (binaryDigest == bytes32(0)) revert ZeroDigest();
if (bytes(manifestUri).length == 0) revert EmptyManifestUri();
if (bytes(operatorLabel).length == 0) revert EmptyOperatorLabel();
uint256 s = ++nextSerial[msg.sender];
latest[msg.sender] = Commitment({
binaryDigest: binaryDigest,
manifestUri: manifestUri,
operatorLabel: operatorLabel,
operatorRegion: operatorRegion,
declaredAt: uint64(block.timestamp),
serial: s
});
emit CommitmentDeclared(
msg.sender, s, binaryDigest, manifestUri, operatorLabel, operatorRegion, uint64(block.timestamp)
);
}
/* ================================ views ================================ */
/// @notice True when the validator has declared at least one commitment.
function isDeclared(address validator) external view returns (bool) {
return latest[validator].declaredAt != 0;
}
/// @notice Pretty getter for indexers.
function commitmentOf(address validator) external view returns (
bytes32 binaryDigest,
string memory manifestUri,
string memory operatorLabel,
string memory operatorRegion,
uint64 declaredAt,
uint256 serial
) {
Commitment storage c = latest[validator];
return (c.binaryDigest, c.manifestUri, c.operatorLabel, c.operatorRegion, c.declaredAt, c.serial);
}
}