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.
229 lines
13 KiB
Solidity
229 lines
13 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.23;
|
|
|
|
/// @dev The one AerePQCKeyRegistry read used to verify a mandate's post-quantum signature. Live at
|
|
/// 0x1eCa…3691 on chain 2800; verifyWithKey routes to the native precompile for the key's
|
|
/// scheme (0x0AE1 Falcon-512) and verifies in full on-chain, fail-closed.
|
|
interface IAerePQCKeyRegistryMandate {
|
|
function verifyWithKey(uint256 keyId, bytes32 message, bytes calldata signature) external view returns (bool);
|
|
function statusOf(uint256 keyId) external view returns (uint8);
|
|
function schemeOf(uint256 keyId) external view returns (uint8);
|
|
}
|
|
|
|
/**
|
|
* @title AereAP2MandateVerifier, a post-quantum x402 / AP2 payment-mandate verifier
|
|
*
|
|
* @notice A minimal verifier for AP2-style payment MANDATES whose authorizing signature is
|
|
* POST-QUANTUM (Falcon-512), verified in full on-chain by Aere Network's LIVE native
|
|
* precompile (0x0AE1) through the already-deployed AerePQCKeyRegistry. Settlement of the
|
|
* authorized spend is routed through the EXISTING AERE402 rail (AERE402FacilitatorPQC /
|
|
* AERE402FacilitatorV2), which is NOT redeployed or modified here.
|
|
*
|
|
* WHAT AN AP2 / x402 MANDATE IS. A signed, standing authorization of the form
|
|
* "agent X may spend up to N of token T per period P for purpose Q". In AP2 (Google) and
|
|
* x402 (Coinbase) today that authorization is an ECDSA signature, which a quantum computer
|
|
* forges. Here the authorizing key is a Falcon-512 key in AerePQCKeyRegistry, so the mandate
|
|
* is quantum-durable. The struct and the verify path are below.
|
|
*
|
|
* THE STRUCT. A Mandate binds the paying agent's Falcon key, the token, the spend cap, the
|
|
* validity window, an opaque purpose tag, and a mandate nonce (so an agent can issue several
|
|
* independent standing mandates). Its `mandateHash` binds chainId + this contract so a
|
|
* signed mandate cannot be replayed against another verifier or chain.
|
|
*
|
|
* THE VERIFY PATH.
|
|
* - verifyMandate(m, falconSig): pure on-chain PQC check that the agent's Falcon key
|
|
* signed `m` (view; anyone can check for free via eth_call).
|
|
* - authorizeSpend(m, amount, falconSig): the enforced path. Verifies the Falcon mandate
|
|
* signature via the live precompile, checks the validity window and the cumulative cap,
|
|
* records the spend against the mandate, and emits MandateAuthorized. Reverts fail-closed
|
|
* on an invalid / tampered signature, an out-of-window mandate, or a cap overflow.
|
|
*
|
|
* SETTLEMENT ROUTING. authorizeSpend records that `amount` is authorized under the mandate;
|
|
* the actual token movement + 25-bps fee to AereSink is performed by the live AERE402 rail.
|
|
* To keep the mandate's cumulative cap honest (only real settlements should consume it),
|
|
* authorizeSpend is gated to an immutable SETTLEMENT_EXECUTOR when one is configured (the
|
|
* AERE402 facilitator, which calls authorizeSpend atomically with settlement). When
|
|
* SETTLEMENT_EXECUTOR is the zero address the accounting path is open (for verification and
|
|
* testing); the pure PQC check verifyMandate is always open.
|
|
*
|
|
* [DEPLOY CONSTRAINT, accepted-and-documented risk] A Falcon mandate signature is a STANDING
|
|
* authorization by design: one signature authorizes repeated spends up to the cap, and the
|
|
* signature is public. So in the OPEN accounting path (SETTLEMENT_EXECUTOR == address(0)) ANY
|
|
* address that observes the signature can replay authorizeSpend to consume the mandate's
|
|
* remaining cap WITHOUT a real settlement (cap-exhaustion griefing; no custody, no theft, since
|
|
* this contract moves no funds). The mitigation is built in and MUST be used in production: set
|
|
* SETTLEMENT_EXECUTOR to the AERE402 facilitator so cap consumption only ever happens ALONGSIDE
|
|
* a real settlement (the gate is enforced in authorizeSpend and proven in the tests). The open
|
|
* path is intended ONLY for eth_call verification and unit testing and MUST NOT be used for a
|
|
* production deployment. A per-authorization nonce is deliberately NOT added here because it
|
|
* would break the intended standing-mandate ("sign once, spend repeatedly up to the cap")
|
|
* semantics; the executor gate closes the vector without that trade-off.
|
|
*
|
|
* @dev [VERIFY: confirm against the finalized x402 / AP2 mandate schema] The x402 and AP2 mandate
|
|
* field sets are still evolving (AP2 distinguishes Intent vs Cart mandates; x402 carries an
|
|
* HTTP-402 payment payload). This struct captures the common "spend up to N per period for
|
|
* purpose Q" authorization; reconcile the exact field set and encoding with the final specs
|
|
* before claiming wire-level conformance.
|
|
*
|
|
* HONEST SCOPE. Application-layer payment authorization only. Does NOT change AERE consensus
|
|
* (still Besu QBFT classical ECDSA). Holds NO funds (the cap is accounting, not custody; the
|
|
* AERE402 rail custodies and moves value). No admin.
|
|
*/
|
|
contract AereAP2MandateVerifier {
|
|
/// @notice Falcon-512 is the mandate signing scheme (live precompile 0x0AE1).
|
|
uint8 public constant SCHEME_FALCON512 = 1;
|
|
uint8 internal constant REGISTRY_STATUS_ACTIVE = 1;
|
|
|
|
/// @notice Domain separator binding a mandate hash to this verifier.
|
|
bytes32 public constant MANDATE_DOMAIN = keccak256("AereAP2MandateVerifier.v1.mandate");
|
|
|
|
/// @notice The LIVE AerePQCKeyRegistry that verifies the Falcon mandate signature via 0x0AE1.
|
|
IAerePQCKeyRegistryMandate public immutable KEY_REGISTRY;
|
|
|
|
/// @notice The AERE402 settlement executor allowed to consume a mandate's cap. Zero = open path.
|
|
address public immutable SETTLEMENT_EXECUTOR;
|
|
|
|
/// @notice An AP2 / x402 payment mandate: "agentKey may spend up to maxAmount of token per the
|
|
/// [periodStart, periodEnd] window for `purpose`". mandateNonce distinguishes an agent's
|
|
/// several standing mandates.
|
|
struct Mandate {
|
|
uint256 agentKeyId; // the paying agent's Falcon-512 key in AerePQCKeyRegistry
|
|
address token; // the settlement token the cap is denominated in
|
|
uint256 maxAmount; // cumulative spend cap over the window
|
|
uint64 periodStart; // unix seconds, inclusive
|
|
uint64 periodEnd; // unix seconds, inclusive (>= periodStart)
|
|
bytes32 purpose; // opaque purpose tag, e.g. keccak256("inference:anthropic")
|
|
uint256 mandateNonce; // per-agent mandate index (lets an agent hold several mandates)
|
|
}
|
|
|
|
/// @notice mandateHash => cumulative amount already authorized under that mandate.
|
|
mapping(bytes32 => uint256) public spentUnder;
|
|
|
|
event MandateAuthorized(
|
|
bytes32 indexed mandateHash,
|
|
uint256 indexed agentKeyId,
|
|
address indexed token,
|
|
uint256 amount,
|
|
uint256 cumulative,
|
|
bytes32 purpose
|
|
);
|
|
|
|
error ZeroAddress();
|
|
error NotSettlementExecutor();
|
|
error AgentKeyNotActiveFalcon(uint256 agentKeyId);
|
|
error InvalidMandateWindow(uint64 periodStart, uint64 periodEnd);
|
|
error MandateNotYetValid(uint64 periodStart);
|
|
error MandateExpired(uint64 periodEnd);
|
|
error ZeroAmount();
|
|
error MandateCapExceeded(uint256 cap, uint256 attempted);
|
|
error MandateSignatureInvalid();
|
|
|
|
constructor(address keyRegistry_, address settlementExecutor_) {
|
|
if (keyRegistry_ == address(0)) revert ZeroAddress();
|
|
KEY_REGISTRY = IAerePQCKeyRegistryMandate(keyRegistry_);
|
|
SETTLEMENT_EXECUTOR = settlementExecutor_; // zero allowed = open accounting path
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Mandate hash + pure verify
|
|
// ==========================================================================
|
|
|
|
/// @notice The canonical hash a Falcon mandate signature must sign. Binds chainId + this
|
|
/// verifier so a mandate cannot be replayed against another verifier or chain.
|
|
function mandateHash(Mandate calldata m) public view returns (bytes32) {
|
|
return keccak256(
|
|
abi.encode(
|
|
MANDATE_DOMAIN,
|
|
block.chainid,
|
|
address(this),
|
|
m.agentKeyId,
|
|
m.token,
|
|
m.maxAmount,
|
|
m.periodStart,
|
|
m.periodEnd,
|
|
m.purpose,
|
|
m.mandateNonce
|
|
)
|
|
);
|
|
}
|
|
|
|
/// @notice Pure on-chain post-quantum check that the agent's Falcon key signed this mandate.
|
|
/// View, permissionless: anyone can verify a mandate via eth_call for free. Returns
|
|
/// false (never reverts) if the key is not an ACTIVE Falcon key or the signature is
|
|
/// invalid.
|
|
function verifyMandate(Mandate calldata m, bytes calldata falconSig) external view returns (bool) {
|
|
if (KEY_REGISTRY.statusOf(m.agentKeyId) != REGISTRY_STATUS_ACTIVE) return false;
|
|
if (KEY_REGISTRY.schemeOf(m.agentKeyId) != SCHEME_FALCON512) return false;
|
|
return KEY_REGISTRY.verifyWithKey(m.agentKeyId, mandateHash(m), falconSig);
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Authorize (enforced path)
|
|
// ==========================================================================
|
|
|
|
/**
|
|
* @notice Authorize `amount` of spend under a Falcon-signed mandate. Verifies the post-quantum
|
|
* mandate signature on-chain via the live precompile, enforces the validity window and
|
|
* the cumulative cap, records the spend, and emits MandateAuthorized. Fail-closed on any
|
|
* invalid / tampered signature, an out-of-window mandate, or a cap overflow.
|
|
*
|
|
* Gated to SETTLEMENT_EXECUTOR when one is configured, so a mandate's cap is only ever
|
|
* consumed alongside a real AERE402 settlement (the executor calls this atomically).
|
|
* PRODUCTION MUST configure SETTLEMENT_EXECUTOR: in the open path (executor == 0) the public
|
|
* standing signature is replayable by anyone to exhaust the cap (see the contract-level
|
|
* DEPLOY CONSTRAINT note). The open path is verification / test only.
|
|
*
|
|
* @param m the mandate being drawn against.
|
|
* @param amount the spend to authorize now (charged against the mandate's cumulative cap).
|
|
* @param falconSig the agent's Falcon-512 signature over mandateHash(m).
|
|
* @return mh the mandate hash the spend was recorded under.
|
|
*/
|
|
function authorizeSpend(Mandate calldata m, uint256 amount, bytes calldata falconSig)
|
|
external
|
|
returns (bytes32 mh)
|
|
{
|
|
if (SETTLEMENT_EXECUTOR != address(0) && msg.sender != SETTLEMENT_EXECUTOR) revert NotSettlementExecutor();
|
|
if (amount == 0) revert ZeroAmount();
|
|
|
|
// Window checks.
|
|
if (m.periodEnd < m.periodStart) revert InvalidMandateWindow(m.periodStart, m.periodEnd);
|
|
if (block.timestamp < m.periodStart) revert MandateNotYetValid(m.periodStart);
|
|
if (block.timestamp > m.periodEnd) revert MandateExpired(m.periodEnd);
|
|
|
|
// The mandate signer must be an ACTIVE Falcon key (rotating / revoking it halts the mandate).
|
|
if (
|
|
KEY_REGISTRY.statusOf(m.agentKeyId) != REGISTRY_STATUS_ACTIVE
|
|
|| KEY_REGISTRY.schemeOf(m.agentKeyId) != SCHEME_FALCON512
|
|
) revert AgentKeyNotActiveFalcon(m.agentKeyId);
|
|
|
|
mh = mandateHash(m);
|
|
|
|
// Full on-chain post-quantum verification of the mandate authorization.
|
|
if (!KEY_REGISTRY.verifyWithKey(m.agentKeyId, mh, falconSig)) revert MandateSignatureInvalid();
|
|
|
|
// Cap check (overflow-safe) then record.
|
|
uint256 already = spentUnder[mh];
|
|
uint256 next = already + amount;
|
|
if (next < already || next > m.maxAmount) revert MandateCapExceeded(m.maxAmount, next);
|
|
spentUnder[mh] = next;
|
|
|
|
emit MandateAuthorized(mh, m.agentKeyId, m.token, amount, next, m.purpose);
|
|
}
|
|
|
|
// ==========================================================================
|
|
// Views
|
|
// ==========================================================================
|
|
|
|
/// @notice Remaining spendable amount under a mandate (0 if exhausted). Does NOT check the
|
|
/// time window or signature; use verifyMandate / authorizeSpend for the full gate.
|
|
function remainingUnder(Mandate calldata m) external view returns (uint256) {
|
|
uint256 already = spentUnder[mandateHash(m)];
|
|
return already >= m.maxAmount ? 0 : m.maxAmount - already;
|
|
}
|
|
|
|
/// @notice True iff `m` is within its validity window at the current block time.
|
|
function isWithinWindow(Mandate calldata m) external view returns (bool) {
|
|
return block.timestamp >= m.periodStart && block.timestamp <= m.periodEnd && m.periodEnd >= m.periodStart;
|
|
}
|
|
}
|