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.
492 lines
26 KiB
Solidity
492 lines
26 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.23;
|
|
|
|
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
|
|
/**
|
|
* @title AereCryptoRegistry, a governed cryptographic-agility registry for AERE (chain 2800)
|
|
*
|
|
* @notice AERE has FIVE live native cryptographic precompiles on mainnet 2800
|
|
* (activated at block 9,189,161): four post-quantum SIGNATURE verifiers and one
|
|
* post-quantum HASH function. This registry turns the flat fact "we verify N
|
|
* schemes" into a governed table:
|
|
*
|
|
* algorithmId -> { verifier address, wire format, status, gas, successor }
|
|
*
|
|
* so a consumer integrates against ONE stable surface (this registry) instead of
|
|
* hardcoding a precompile address. When a scheme is deprecated, broken, or
|
|
* superseded, the Foundation (later the Timelock) flips its status and points it
|
|
* at a successor. Consumers that route through {resolveActive} follow the upgrade
|
|
* automatically, WITHOUT any consumer contract being redeployed.
|
|
*
|
|
* @dev HONEST SCOPE. This is an application-layer routing + policy registry. It does
|
|
* NOT change AERE consensus (blocks are still produced and signed by Besu QBFT
|
|
* validators with classical ECDSA / secp256k1). It calls the SAME live native
|
|
* precompiles that {AerePQCAttestation} calls, and it builds the SAME wire-format
|
|
* input those precompiles parse. No funds are ever held. There is no payable
|
|
* function, no selfdestruct, and no fund custody of any kind.
|
|
*
|
|
* @dev THE FIVE LIVE PRECOMPILES (chain 2800):
|
|
* scheme 1 = Falcon-512 at 0x...0AE1 (signature) pk 897, esig header 0x29
|
|
* scheme 2 = Falcon-1024 at 0x...0AE2 (signature) pk 1793, esig header 0x2A
|
|
* scheme 3 = ML-DSA-44 at 0x...0AE3 (signature) pk 1312, sig 2420 (FIPS 204)
|
|
* scheme 4 = SLH-DSA-128s at 0x...0AE4 (signature) pk 32, sig 7856 (FIPS 205)
|
|
* scheme 5 = SHAKE256 at 0x...0AE5 (HASH, NOT a signature scheme)
|
|
*
|
|
* @dev PRECOMPILE INPUT ENCODING (mirrors AerePQCAttestation._buildInput exactly):
|
|
*
|
|
* Falcon family (envelope format, selected iff esigHeader != 0):
|
|
* signature envelope = nonce(40) || esig (esig[0] == esigHeader)
|
|
* input = pk || sm
|
|
* sm = esigLen(uint16 big-endian) || nonce(40) || message(32) || esig
|
|
*
|
|
* Fixed-concatenation family (selected iff esigHeader == 0):
|
|
* signature = the raw fixed-length signature (sigLen bytes)
|
|
* input = pk || signature || message(32)
|
|
*
|
|
* Every verifier returns a 32-byte word: 0x..01 == valid, anything else invalid.
|
|
* An empty/short/zero return (e.g. a pre-fork address with no precompile) is
|
|
* treated as INVALID, so a stale route can never report a false positive.
|
|
*
|
|
* @dev WIRE-FORMAT DISCRIMINATOR. The registry selects the envelope (Falcon) format
|
|
* when the algorithm's `esigHeader` is non-zero, and the fixed-concatenation
|
|
* format otherwise. This keeps the registry agile across future schemes that
|
|
* share one of the two families (e.g. a new Falcon-like or a new fixed-length
|
|
* lattice scheme) without any code change: only a new row is added. A genuinely
|
|
* NEW wire family would require a registry upgrade, but consumers still never
|
|
* change their integration (they keep calling verify(id, ...)).
|
|
*/
|
|
contract AereCryptoRegistry is Ownable {
|
|
// ==========================================================================
|
|
// Types
|
|
// ==========================================================================
|
|
|
|
/// @notice Lifecycle of a registered algorithm.
|
|
/// UNKNOWN = never registered (the zero value; no stored row ever has this).
|
|
/// ACTIVE = registered and recommended for new use; verify() runs.
|
|
/// DEPRECATED= verifiable for legacy signatures but NOT recommended for new use;
|
|
/// verify() still runs, but isActive()==false and isUsable()==false.
|
|
/// REVOKED = broken/withdrawn; verify() always returns false. Terminal by policy.
|
|
enum Status {
|
|
UNKNOWN,
|
|
ACTIVE,
|
|
DEPRECATED,
|
|
REVOKED
|
|
}
|
|
|
|
/// @param name human-readable identifier, e.g. "Falcon-512".
|
|
/// @param scheme the AERE scheme id (1..5 for the live set; informational).
|
|
/// @param verifier the address staticcalled to verify (a precompile or contract).
|
|
/// @param pubKeyLen exact public-key length in bytes the verifier expects.
|
|
/// @param sigLen exact signature length for the fixed-concatenation family
|
|
/// (0 for the variable-length Falcon envelope family).
|
|
/// @param esigHeader Falcon esig header byte; NON-ZERO selects the Falcon envelope
|
|
/// wire format, ZERO selects the fixed-concatenation format.
|
|
/// @param status lifecycle status (see {Status}).
|
|
/// @param gasEstimate ADVISORY metadata only: a rough gas budget hint for consumers.
|
|
/// It does NOT cap or influence the actual staticcall gas.
|
|
/// @param successorId the algorithm id that supersedes this one (0 == none).
|
|
/// @param addedBlock block number the row was added at.
|
|
/// @param isSignature true for signature schemes; false for hash-only entries
|
|
/// (e.g. SHAKE256), which verify() refuses to route.
|
|
struct Algorithm {
|
|
string name;
|
|
uint8 scheme;
|
|
address verifier;
|
|
uint16 pubKeyLen;
|
|
uint32 sigLen;
|
|
uint8 esigHeader;
|
|
Status status;
|
|
uint256 gasEstimate;
|
|
uint256 successorId;
|
|
uint64 addedBlock;
|
|
bool isSignature;
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Constants
|
|
// ==========================================================================
|
|
|
|
uint256 internal constant FALCON_NONCE_LEN = 40;
|
|
uint256 internal constant NAME_MAX_LEN = 64;
|
|
|
|
// Live precompile addresses on chain 2800 (used only by the seed function).
|
|
address internal constant PRECOMPILE_FALCON512 = address(0x0AE1);
|
|
address internal constant PRECOMPILE_FALCON1024 = address(0x0AE2);
|
|
address internal constant PRECOMPILE_MLDSA44 = address(0x0AE3);
|
|
address internal constant PRECOMPILE_SLHDSA128S = address(0x0AE4);
|
|
address internal constant PRECOMPILE_SHAKE256 = address(0x0AE5);
|
|
|
|
// Advisory gas-budget PLACEHOLDERS for the seeded live schemes. These are hints for
|
|
// consumers, not gas caps; the operator can refine them by adding a new row. The
|
|
// magnitudes reflect the native precompile costs on the Besu PQC fork, not the older
|
|
// pure-EVM verifiers.
|
|
uint256 internal constant GAS_FALCON512 = 120_000;
|
|
uint256 internal constant GAS_FALCON1024 = 145_000;
|
|
uint256 internal constant GAS_MLDSA44 = 351_000;
|
|
uint256 internal constant GAS_SLHDSA128S = 400_000;
|
|
uint256 internal constant GAS_SHAKE256 = 30_000;
|
|
|
|
// ==========================================================================
|
|
// Canonical scheme identifiers (crypto-agility, ADDITIVE)
|
|
// ==========================================================================
|
|
//
|
|
// The `scheme` field of an Algorithm row is an informational uint8 tag naming the NIST
|
|
// parameter set a row implements (see the struct doc). These named constants pin the
|
|
// CANONICAL scheme-ID space so governance, consumers, and off-chain tooling all agree
|
|
// on the number. Ids 1..4 match AerePQCKeyRegistry's SCHEME_* exactly; id 5 is this
|
|
// registry's SHAKE256 hash entry; ids 6..14 RESERVE future NIST parameter sets.
|
|
// Numbering rules and the full rationale live in docs/PQ-AGILITY-HARDENING-2026-07-18.md.
|
|
//
|
|
// HONESTY: declaring a scheme id here does NOT register, seed, activate, or route
|
|
// anything, and does NOT assert a verifier exists. Ids 1..5 are the only schemes with a
|
|
// live precompile on chain 2800 today (0x0AE1..0x0AE5, activated block 9,189,161). Ids
|
|
// 6..14 are RESERVED identifiers; a scheme becomes usable only when governance adds a
|
|
// row via addAlgorithm pointing at a real, conformance-gated verifier. These constants
|
|
// are purely additive reference values: seedLiveSchemes, verify, resolveActive, and
|
|
// every other function are unchanged, and the constants occupy no storage.
|
|
|
|
// Genesis block (ids 1..5): the five live schemes seedLiveSchemes registers.
|
|
uint8 public constant SCHEME_FALCON512 = 1; // Falcon-512 (0x0AE1, live, signature)
|
|
uint8 public constant SCHEME_FALCON1024 = 2; // Falcon-1024 (0x0AE2, live, signature)
|
|
uint8 public constant SCHEME_MLDSA44 = 3; // ML-DSA-44 FIPS 204 (0x0AE3, live, signature)
|
|
uint8 public constant SCHEME_SLHDSA128S = 4; // SLH-DSA-128s FIPS 205 (0x0AE4, live, signature)
|
|
uint8 public constant SCHEME_SHAKE256 = 5; // SHAKE256 FIPS 202 (0x0AE5, live, hash/XOF)
|
|
|
|
// Reserved future schemes (ids 6..14): NO live verifier yet, reserved numbers only.
|
|
uint8 public constant SCHEME_MLDSA65 = 6; // ML-DSA-65 FIPS 204 (signature)
|
|
uint8 public constant SCHEME_MLDSA87 = 7; // ML-DSA-87 FIPS 204 (signature)
|
|
uint8 public constant SCHEME_FALCON512_PADDED = 8; // Falcon-512 padded FN-DSA draft (signature, fixed-len)
|
|
uint8 public constant SCHEME_FALCON1024_PADDED = 9; // Falcon-1024 padded FN-DSA draft (signature, fixed-len)
|
|
uint8 public constant SCHEME_SLHDSA192S = 10; // SLH-DSA-192s FIPS 205 (signature)
|
|
uint8 public constant SCHEME_SLHDSA256S = 11; // SLH-DSA-256s FIPS 205 (signature)
|
|
uint8 public constant SCHEME_MLKEM512 = 12; // ML-KEM-512 FIPS 203 (KEM, non-signature, discovery-only)
|
|
uint8 public constant SCHEME_MLKEM768 = 13; // ML-KEM-768 FIPS 203 (KEM, non-signature, discovery-only)
|
|
uint8 public constant SCHEME_MLKEM1024 = 14; // ML-KEM-1024 FIPS 203 (KEM, non-signature, discovery-only)
|
|
|
|
// Auxiliary primitive (id 15): Falcon HashToPoint. Like SHAKE256 (id 5) this names a
|
|
// HASH / auxiliary primitive, NOT a signature or KEM parameter set, so any future
|
|
// registry row for it would be added isSignature == false and is never routed by the
|
|
// signature-only verify() path. Id 15 is the next append-only number after the reserved
|
|
// block (ids 1..14); the earlier soft "recommendation" in PQ-AGILITY-HARDENING-2026-07-18
|
|
// to earmark ids 15+ for SLH-DSA fast variants was non-binding (no constant was ever
|
|
// declared for those), so those parameter sets take the next free ids (16+) when a
|
|
// concrete verifier is proposed. Append-only integrity is intact: no existing id moves.
|
|
uint8 public constant SCHEME_FALCON_HASHTOPOINT = 15; // Falcon HashToPoint (SHAKE256 rejection sampler)
|
|
|
|
// ==========================================================================
|
|
// NEW native precompiles - TESTNET ONLY (chain 2801), NOT mainnet 2800
|
|
// ==========================================================================
|
|
//
|
|
// Two additional native precompiles were built on the AERE Besu PQC fork and are live on
|
|
// the AERE TESTNET ONLY. They are NOT activated on mainnet 2800 as of 2026-07-18: mainnet
|
|
// still has exactly the FIVE precompiles 0x0AE1..0x0AE5 (activated block 9,189,161). Their
|
|
// gated mainnet-activation plan is docs/PQC-PRECOMPILES-MAINNET-ACTIVATION-PLAN-2026-07-18.md
|
|
// (founder + external-audit gated). Wire formats (see AereMLKEM768.sol and AerePQCPrecompiles.sol):
|
|
//
|
|
// 0x0AE6 ML-KEM-768 (FIPS 203) deterministic Encaps:
|
|
// input = ek(1184) || m(32) (1216 bytes)
|
|
// output = ct(1088) || ss(32) (1120 bytes); EMPTY on malformed input
|
|
// KEM, NOT a signature. Canonical scheme id = SCHEME_MLKEM768 (13).
|
|
// 0x0AE7 Falcon HashToPoint (SHAKE256 rejection sampler):
|
|
// input = logn(1) || nonce(40) || message(rest), logn = 9 (n=512) or 10 (n=1024)
|
|
// output = 2n bytes = n big-endian uint16 coefficients in [0, q), q=12289; EMPTY on malformed
|
|
// Auxiliary hash/lattice primitive, NOT a signature. Canonical scheme id = SCHEME_FALCON_HASHTOPOINT (15).
|
|
//
|
|
// HONESTY / ADDITIVE: these are reference constants ONLY. They are NOT seeded by
|
|
// seedLiveSchemes, NOT routed by verify / resolveActive / precompileFor, and add no storage
|
|
// and no code path. Declaring them makes NO on-chain claim that either precompile exists on
|
|
// mainnet 2800; on mainnet a staticcall to 0x0AE6/0x0AE7 returns empty today, so every
|
|
// consumer wrapper fail-closes until a governance-gated activation. A registry row for either
|
|
// is created only after the activation + conformance gate in the plan doc passes (addAlgorithm,
|
|
// onlyOwner). Address-literal style matches the seeded PRECOMPILE_* constants above.
|
|
|
|
/// @notice TESTNET-ONLY address of the ML-KEM-768 (FIPS 203) deterministic-Encaps precompile
|
|
/// on the AERE Besu PQC fork. NOT on mainnet 2800. Reference value only: never seeded
|
|
/// or routed by this registry.
|
|
address public constant PRECOMPILE_MLKEM768_TESTNET = address(0x0AE6);
|
|
|
|
/// @notice TESTNET-ONLY address of the Falcon HashToPoint precompile on the AERE Besu PQC
|
|
/// fork. NOT on mainnet 2800. Reference value only: never seeded or routed here.
|
|
address public constant PRECOMPILE_FALCON_HASHTOPOINT_TESTNET = address(0x0AE7);
|
|
|
|
// ==========================================================================
|
|
// Storage
|
|
// ==========================================================================
|
|
|
|
// Algorithm ids are 1-based and monotonic. Id 0 is the "none"/unknown sentinel
|
|
// (also used as the "no successor" value), so it is never a valid row.
|
|
mapping(uint256 => Algorithm) private _algorithms;
|
|
uint256 private _count;
|
|
bool private _seeded;
|
|
|
|
// ==========================================================================
|
|
// Events
|
|
// ==========================================================================
|
|
|
|
event AlgorithmAdded(
|
|
uint256 indexed id, uint8 indexed scheme, address indexed verifier, string name, bool isSignature
|
|
);
|
|
event StatusChanged(uint256 indexed id, Status oldStatus, Status newStatus);
|
|
event SuccessorSet(uint256 indexed id, uint256 indexed successorId);
|
|
event LiveSchemesSeeded(uint256 count);
|
|
|
|
// ==========================================================================
|
|
// Errors
|
|
// ==========================================================================
|
|
|
|
error UnknownAlgorithm(uint256 id);
|
|
error InvalidAlgorithmParams();
|
|
error InvalidStatusTarget(); // attempt to set UNKNOWN
|
|
error AlgorithmRevoked(uint256 id); // REVOKED is terminal
|
|
error InvalidSuccessor(); // self-successor
|
|
error NoActiveSuccessor(uint256 id); // resolveActive dead-ends with no ACTIVE row
|
|
error SuccessorCycle(uint256 id); // resolveActive detected a cycle
|
|
error UnknownScheme(uint8 scheme);
|
|
error AlreadySeeded();
|
|
|
|
// ==========================================================================
|
|
// Owner: registration
|
|
// ==========================================================================
|
|
|
|
/**
|
|
* @notice Register a new algorithm row. Owner only. Returns the fresh monotonic id.
|
|
* The row is created ACTIVE with no successor.
|
|
* @dev Length sanity: name in (0, 64]; verifier != 0; for a signature scheme
|
|
* pubKeyLen != 0, and for the fixed-concatenation family (esigHeader == 0)
|
|
* sigLen != 0. Hash-only entries (isSignature == false) skip the sig checks.
|
|
*/
|
|
function addAlgorithm(
|
|
string calldata name,
|
|
uint8 scheme,
|
|
address verifier,
|
|
uint16 pubKeyLen,
|
|
uint32 sigLen,
|
|
uint8 esigHeader,
|
|
uint256 gasEstimate,
|
|
bool isSignature
|
|
) external onlyOwner returns (uint256 id) {
|
|
return _addAlgorithm(name, scheme, verifier, pubKeyLen, sigLen, esigHeader, gasEstimate, isSignature);
|
|
}
|
|
|
|
function _addAlgorithm(
|
|
string memory name,
|
|
uint8 scheme,
|
|
address verifier,
|
|
uint16 pubKeyLen,
|
|
uint32 sigLen,
|
|
uint8 esigHeader,
|
|
uint256 gasEstimate,
|
|
bool isSignature
|
|
) internal returns (uint256 id) {
|
|
uint256 nameLen = bytes(name).length;
|
|
if (nameLen == 0 || nameLen > NAME_MAX_LEN) revert InvalidAlgorithmParams();
|
|
if (verifier == address(0)) revert InvalidAlgorithmParams();
|
|
if (isSignature) {
|
|
if (pubKeyLen == 0) revert InvalidAlgorithmParams();
|
|
// Fixed-concatenation family needs an exact signature length.
|
|
if (esigHeader == 0 && sigLen == 0) revert InvalidAlgorithmParams();
|
|
}
|
|
|
|
_count += 1;
|
|
id = _count;
|
|
_algorithms[id] = Algorithm({
|
|
name: name,
|
|
scheme: scheme,
|
|
verifier: verifier,
|
|
pubKeyLen: pubKeyLen,
|
|
sigLen: sigLen,
|
|
esigHeader: esigHeader,
|
|
status: Status.ACTIVE,
|
|
gasEstimate: gasEstimate,
|
|
successorId: 0,
|
|
addedBlock: uint64(block.number),
|
|
isSignature: isSignature
|
|
});
|
|
|
|
emit AlgorithmAdded(id, scheme, verifier, name, isSignature);
|
|
}
|
|
|
|
/**
|
|
* @notice Change an algorithm's lifecycle status. Owner only.
|
|
* @dev May set ACTIVE (reactivate a DEPRECATED row), DEPRECATED, or REVOKED.
|
|
* Cannot set UNKNOWN (the sentinel). REVOKED is TERMINAL: a revoked row can
|
|
* never be changed again. To bring back a fixed variant of a revoked scheme,
|
|
* add a NEW algorithm id and point the revoked row's successor at it.
|
|
*/
|
|
function setStatus(uint256 id, Status newStatus) external onlyOwner {
|
|
if (!_exists(id)) revert UnknownAlgorithm(id);
|
|
if (newStatus == Status.UNKNOWN) revert InvalidStatusTarget();
|
|
Algorithm storage a = _algorithms[id];
|
|
if (a.status == Status.REVOKED) revert AlgorithmRevoked(id);
|
|
Status old = a.status;
|
|
a.status = newStatus;
|
|
emit StatusChanged(id, old, newStatus);
|
|
}
|
|
|
|
/**
|
|
* @notice Point `id`'s successor at `successorId` (the row that supersedes it). Owner
|
|
* only. Both must exist and must differ. Longer cycles are tolerated at write
|
|
* time but are caught and reverted by {resolveActive}'s bounded walk.
|
|
*/
|
|
function setSuccessor(uint256 id, uint256 successorId) external onlyOwner {
|
|
if (!_exists(id)) revert UnknownAlgorithm(id);
|
|
if (!_exists(successorId)) revert UnknownAlgorithm(successorId);
|
|
if (successorId == id) revert InvalidSuccessor();
|
|
_algorithms[id].successorId = successorId;
|
|
emit SuccessorSet(id, successorId);
|
|
}
|
|
|
|
/**
|
|
* @notice Seed the five live chain-2800 schemes. Owner only, one-shot (idempotent
|
|
* guard). Registers Falcon-512/1024, ML-DSA-44 and SLH-DSA-128s as signature
|
|
* schemes pointing at their live precompiles, plus SHAKE256 as a hash-only
|
|
* entry (isSignature == false) that {verify} refuses to route.
|
|
*/
|
|
function seedLiveSchemes() external onlyOwner {
|
|
if (_seeded) revert AlreadySeeded();
|
|
_seeded = true;
|
|
|
|
_addAlgorithm("Falcon-512", 1, PRECOMPILE_FALCON512, 897, 0, 0x29, GAS_FALCON512, true);
|
|
_addAlgorithm("Falcon-1024", 2, PRECOMPILE_FALCON1024, 1793, 0, 0x2A, GAS_FALCON1024, true);
|
|
_addAlgorithm("ML-DSA-44", 3, PRECOMPILE_MLDSA44, 1312, 2420, 0, GAS_MLDSA44, true);
|
|
_addAlgorithm("SLH-DSA-128s", 4, PRECOMPILE_SLHDSA128S, 32, 7856, 0, GAS_SLHDSA128S, true);
|
|
_addAlgorithm("SHAKE256", 5, PRECOMPILE_SHAKE256, 0, 0, 0, GAS_SHAKE256, false);
|
|
|
|
emit LiveSchemesSeeded(_count);
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Verification
|
|
// ==========================================================================
|
|
|
|
/**
|
|
* @notice Verify a signature for `algorithmId` against the routed verifier.
|
|
*
|
|
* POLICY (fail-closed): returns FALSE, never reverts, when the id is unknown,
|
|
* the row is a hash-only entry (isSignature == false), the status is UNKNOWN
|
|
* or REVOKED, or the supplied lengths are malformed. A DEPRECATED row still
|
|
* verifies (legacy support); check {isUsable} before adopting a scheme for new
|
|
* work. Returns TRUE iff the routed verifier answers 0x..01.
|
|
*
|
|
* @param algorithmId the registry id to verify under.
|
|
* @param pubKey the raw public key for the scheme (exactly pubKeyLen bytes).
|
|
* @param messageHash the 32-byte message that was signed.
|
|
* @param signature the signature envelope for the scheme's wire family.
|
|
*/
|
|
function verify(uint256 algorithmId, bytes calldata pubKey, bytes32 messageHash, bytes calldata signature)
|
|
external
|
|
view
|
|
returns (bool)
|
|
{
|
|
if (!_exists(algorithmId)) return false;
|
|
Algorithm storage a = _algorithms[algorithmId];
|
|
if (!a.isSignature) return false;
|
|
Status st = a.status;
|
|
if (st == Status.UNKNOWN || st == Status.REVOKED) return false; // ACTIVE or DEPRECATED proceed
|
|
if (pubKey.length != a.pubKeyLen) return false;
|
|
|
|
bytes memory input;
|
|
if (a.esigHeader != 0) {
|
|
// Falcon envelope family: signature = nonce(40) || esig.
|
|
if (signature.length <= FALCON_NONCE_LEN) return false;
|
|
uint256 esigLen = signature.length - FALCON_NONCE_LEN;
|
|
if (esigLen > type(uint16).max) return false;
|
|
bytes calldata nonce = signature[0:FALCON_NONCE_LEN];
|
|
bytes calldata esig = signature[FALCON_NONCE_LEN:];
|
|
// sm = esigLen(uint16 BE) || nonce(40) || message(32) || esig
|
|
bytes memory sm = abi.encodePacked(uint16(esigLen), nonce, messageHash, esig);
|
|
input = abi.encodePacked(pubKey, sm);
|
|
} else {
|
|
// Fixed-concatenation family: input = pk || sig || message.
|
|
if (signature.length != a.sigLen) return false;
|
|
input = abi.encodePacked(pubKey, signature, messageHash);
|
|
}
|
|
|
|
(bool ok, bytes memory ret) = a.verifier.staticcall(input);
|
|
return ok && ret.length >= 32 && ret[31] == 0x01;
|
|
}
|
|
|
|
/**
|
|
* @notice Follow the successor chain from `algorithmId` to the first ACTIVE row and
|
|
* return its id. Reverts {NoActiveSuccessor} if the chain dead-ends without an
|
|
* ACTIVE row, and {SuccessorCycle} if a cycle is detected. The walk is bounded
|
|
* by the number of registered algorithms, so it always terminates.
|
|
*/
|
|
function resolveActive(uint256 algorithmId) external view returns (uint256) {
|
|
uint256 id = algorithmId;
|
|
// At most _count distinct rows exist; a walk longer than that is a cycle.
|
|
for (uint256 hops = 0; hops <= _count; hops++) {
|
|
if (!_exists(id)) revert UnknownAlgorithm(id);
|
|
Algorithm storage a = _algorithms[id];
|
|
if (a.status == Status.ACTIVE) return id;
|
|
uint256 next = a.successorId;
|
|
if (next == 0) revert NoActiveSuccessor(algorithmId);
|
|
id = next;
|
|
}
|
|
revert SuccessorCycle(algorithmId);
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Views
|
|
// ==========================================================================
|
|
|
|
/// @notice The full row for `id`. Reverts if `id` does not exist.
|
|
function getAlgorithm(uint256 id) external view returns (Algorithm memory) {
|
|
if (!_exists(id)) revert UnknownAlgorithm(id);
|
|
return _algorithms[id];
|
|
}
|
|
|
|
/// @notice Number of registered algorithms. Ids run 1..algorithmCount().
|
|
function algorithmCount() external view returns (uint256) {
|
|
return _count;
|
|
}
|
|
|
|
/// @notice Lifecycle status of `id`; UNKNOWN for any id that was never registered.
|
|
function statusOf(uint256 id) external view returns (Status) {
|
|
if (!_exists(id)) return Status.UNKNOWN;
|
|
return _algorithms[id].status;
|
|
}
|
|
|
|
/// @notice True iff `id` exists and is ACTIVE.
|
|
function isActive(uint256 id) external view returns (bool) {
|
|
return _exists(id) && _algorithms[id].status == Status.ACTIVE;
|
|
}
|
|
|
|
/**
|
|
* @notice True iff `id` is a signature scheme that is safe to adopt for NEW work:
|
|
* it exists, isSignature == true, and status == ACTIVE. A DEPRECATED row is
|
|
* still verifiable (see {verify}) but is NOT usable, and a hash-only entry is
|
|
* never a usable signature verifier.
|
|
*/
|
|
function isUsable(uint256 id) external view returns (bool) {
|
|
if (!_exists(id)) return false;
|
|
Algorithm storage a = _algorithms[id];
|
|
return a.isSignature && a.status == Status.ACTIVE;
|
|
}
|
|
|
|
/// @notice The verifier address currently routing an ACTIVE algorithm for `scheme`.
|
|
/// Reverts {UnknownScheme} if no ACTIVE row serves that scheme.
|
|
function precompileFor(uint8 scheme) external view returns (address) {
|
|
uint256 n = _count;
|
|
for (uint256 id = 1; id <= n; id++) {
|
|
Algorithm storage a = _algorithms[id];
|
|
if (a.scheme == scheme && a.status == Status.ACTIVE) return a.verifier;
|
|
}
|
|
revert UnknownScheme(scheme);
|
|
}
|
|
|
|
/// @notice Whether the one-shot {seedLiveSchemes} has already run.
|
|
function seeded() external view returns (bool) {
|
|
return _seeded;
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Internal
|
|
// ==========================================================================
|
|
|
|
function _exists(uint256 id) internal view returns (bool) {
|
|
return id != 0 && id <= _count;
|
|
}
|
|
}
|