aere-contracts/contracts/agentic/AereVectorStore.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

650 lines
30 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
/// @dev The one AERE402Facilitator surface paidQuery consumes. The facilitator is the
/// existing agentic payment rail (see contracts/agentic/AERE402Facilitator.sol); this
/// contract references it and never redeploys it. `settle` debits the paying agent's
/// prepaid balance (via the AereAgent registry the facilitator is bound to), takes the
/// 25-bps protocol fee to AereSink, and transfers the remainder to `payee`. paidQuery
/// passes address(this) as `payee` so the facilitator's own `msg.sender == payee` guard
/// is satisfied, then forwards the received tokens to the store's payment recipient.
interface IAERE402Facilitator {
function settle(
uint256 agentId,
address token,
address payee,
uint256 amount,
bytes32 resourceId,
uint256 nonce,
uint256 deadline,
bytes calldata signature
) external;
function consumed(uint256 agentId, uint256 nonce) external view returns (bool);
}
/**
* @title AereVectorStore, verifiable semantic memory for AI agents on AERE (chain 2800)
*
* @notice On-chain provenance, ownership and attested-retrieval for an AI agent's vector
* store (its long-term semantic memory). An owner (an account or an agent identity)
* commits a VERSIONED commitment root over the vectors + metadata it stores off-chain;
* a retrieval provider later attests "for this query, against store version V, these
* are the results (resultRoot)". A consumer can then verify, on-chain, that a returned
* vector was actually a member of a committed store version, that the retrieval named a
* committed version, and who owns the memory. Query access is metered through the
* existing AERE402 agentic payment rail.
*
* WHAT THE CHAIN GUARANTEES (buildable NOW, tested):
* 1. AUTHENTICITY of the committed root. A commit to a post-quantum store must carry
* a Falcon-512 signature that verifies, in full, on-chain, through AERE's LIVE
* native precompile at 0x0AE1 over this contract's per-version challenge. An
* invalid signature is rejected fail-closed; nothing is recorded.
* 2. APPEND-ONLY HISTORY. Each commit appends a new version; no function can rewrite
* or delete a committed root. There is no admin, no owner override, no upgrade
* hook. The current root and every past version stay queryable forever.
* 3. RETRIEVAL-AGAINST-A-COMMITTED-VERSION. attestRetrieval reverts unless the named
* version exists (1..current). A recorded attestation therefore always references a
* real, committed store state, and carries an inclusion-proof shape so a consumer
* can check a returned vector was in the committed root.
* 4. OWNERSHIP. Every store is bound to an owner at creation; a classical store's
* commits require msg.sender == owner, a post-quantum store's commits require the
* Falcon key (authorship IS the PQC signature, so a relayer only pays gas).
* 5. PAID, ATTESTED RETRIEVAL. On a priced store, a retrieval attestation must be
* backed by an AERE402 payment (paidQuery), so retrieval is metered on the rail.
*
* WHAT THE CHAIN DOES NOT GUARANTEE (off-chain, [MEASURE]):
* - It does NOT compute embeddings, does NOT run the vector similarity search, and
* does NOT hold the vectors. The embedding model + the nearest-neighbour index +
* the raw vector data all live off-chain. The chain proves PROVENANCE and OWNERSHIP
* of a committed root and that a retrieval named a committed version; it does NOT
* and CANNOT prove that the embeddings are semantically good, that the index is
* well-built, or that the provider ran an honest similarity search. Semantic
* quality is an off-chain property, measured off-chain.
*
* HONEST SCOPE. Application-layer PQC authentication + settlement. This does NOT change
* AERE consensus: blocks are still produced and signed by Besu QBFT validators with
* classical ECDSA (secp256k1). PQC-authenticated memory commits do not make consensus
* post-quantum. What is post-quantum here is the AUTHENTICITY of a store commit, via the
* live Falcon-512 precompile. No new token is introduced; paid queries settle on the
* existing AERE402 rail in whatever ERC-20 the store owner selects.
*
* @dev Falcon-512 precompile I/O (0x0AE1), identical to AerePQCAttestation:
* input = pk(897) || sm
* sm = sigLen(2, big-endian) || nonce(40) || message(32) || esig
* esig = 0x29 || compressedSig, sigLen == esig.length
* falconSig = nonce(40) || esig (the envelope handed to commit)
* Output: a 32-byte word, 0x..01 valid else 0x..00. An empty/zero return (a stale
* caller hitting an address with no precompile) is treated as INVALID, so a pre-fork
* node can never record a commit.
*/
contract AereVectorStore is ReentrancyGuard {
using MerkleProof for bytes32[];
/* ------------------------------ Falcon-512 ------------------------------ */
uint8 internal constant SCHEME_FALCON512 = 1;
address public constant PRECOMPILE_FALCON512 = address(0x0AE1);
uint256 internal constant FALCON512_PK_LEN = 897;
uint8 internal constant FALCON512_PK_HEADER = 0x09; // 0x00 | logn=9
uint256 internal constant FALCON_NONCE_LEN = 40;
/// @notice Domain tag binding a commit challenge to THIS contract + chain + store + version.
bytes32 public constant COMMIT_DOMAIN = keccak256("AereVectorStore.v1.commit");
/// @notice Domain tag binding a paid-query resourceId to THIS contract + chain + store + query.
bytes32 public constant QUERY_DOMAIN = keccak256("AereVectorStore.v1.query");
/* ------------------------------- payment rail ---------------------------- */
/// @notice The existing AERE402 facilitator. Immutable; referenced, never redeployed.
IAERE402Facilitator public immutable FACILITATOR;
/* --------------------------------- types -------------------------------- */
struct Store {
bool exists;
address owner; // account / agent identity that owns the memory (fixed at creation)
address settlementToken; // ERC-20 that priced queries settle in (0 => free store)
address paymentRecipient; // where paidQuery net proceeds go (owner or retrieval provider)
uint256 queryPrice; // minimum AERE402 payment per query (0 => free, no payment gate)
uint64 version; // current version count (0 => no commits yet)
bytes falconPubKey; // empty => classical store; 897-byte Falcon-512 key => PQC store
}
struct Version {
bytes32 vectorRoot; // Merkle/commitment root over the stored vectors + metadata
uint64 vectorCount; // number of vectors the root commits to
uint64 committedAt; // block.timestamp of the commit
uint64 blockNumber; // block the commit landed in
string uri; // pointer to the off-chain index snapshot (ipfs:// / https://)
}
struct Retrieval {
bool exists;
uint64 version; // the committed store version this retrieval was against
uint64 attestedAt;
uint64 blockNumber;
address provider; // the attester (retrieval provider)
bytes32 queryHash; // commitment to the query the results answer
bytes32 resultRoot; // Merkle root over the returned result set
}
struct QueryReceipt {
bool exists;
bool consumed; // flipped true when an attestRetrieval binds to it (one-shot)
uint64 paidAtVersion; // store version at payment time (informational)
bytes32 storeId; // the store the payment was for (binds a receipt to one store)
bytes32 queryHash; // the query the payment was for
uint256 amount; // gross amount paid through the rail
address payer; // msg.sender of paidQuery (provider relayer or consumer)
address provider; // the ONLY address allowed to consume this receipt in attestRetrieval
}
/* -------------------------------- storage ------------------------------- */
mapping(bytes32 => Store) internal _stores;
mapping(bytes32 => Version[]) internal _versions; // storeId => append-only version history
mapping(bytes32 => Retrieval) internal _retrievals; // retrievalId => Retrieval
mapping(bytes32 => uint64) public retrievalCount; // storeId => number of attestations
mapping(bytes32 => QueryReceipt) internal _receipts; // receiptId => QueryReceipt
/// @notice Total commits recorded across all stores (monotonic).
uint256 public commitCount;
/* --------------------------------- events ------------------------------- */
event StoreCreated(
bytes32 indexed storeId,
address indexed owner,
bool postQuantum,
address settlementToken,
uint256 queryPrice,
address paymentRecipient
);
event Committed(
bytes32 indexed storeId,
uint64 indexed version,
bytes32 vectorRoot,
uint64 vectorCount,
bytes32 challenge,
string uri
);
event RetrievalAttested(
bytes32 indexed storeId,
bytes32 indexed retrievalId,
uint64 indexed version,
address provider,
bytes32 queryHash,
bytes32 resultRoot
);
event QueryPaid(
bytes32 indexed storeId,
bytes32 indexed receiptId,
address indexed payer,
uint256 agentId,
uint256 amount,
bytes32 queryHash
);
/* --------------------------------- errors ------------------------------- */
error ZeroAddress();
error StoreExists();
error UnknownStore();
error InvalidPubKey();
error InvalidConfig();
error NotOwner();
error EmptyRoot();
error InvalidSignatureLength();
error PQCVerificationFailed();
error UnknownVersion();
error StoreIsFree();
error PaymentRequired();
error ReceiptExists();
error ReceiptConsumed();
error ReceiptMismatch();
error NotReceiptProvider();
error Underpaid(uint256 paid, uint256 price);
error UnknownRetrieval();
error TransferFailed();
/* ------------------------------- constructor ---------------------------- */
/// @param facilitator the existing AERE402Facilitator (the agentic payment rail).
constructor(address facilitator) {
if (facilitator == address(0)) revert ZeroAddress();
FACILITATOR = IAERE402Facilitator(facilitator);
}
/* ================================ stores ================================ */
/**
* @notice Create a vector store bound to msg.sender as owner. Config is fixed at creation:
* there is no setter for the Falcon key, the price, the token or the recipient, so
* a store's authentication mode and payment terms cannot be silently changed later.
* @param storeId caller-chosen identifier (must be unused).
* @param falconPubKey empty for a classical (owner-signed) store, or a 897-byte
* Falcon-512 public key (header 0x09) for a post-quantum store whose
* commits are authenticated by the live 0x0AE1 precompile.
* @param settlementToken ERC-20 that priced queries settle in (must be set iff queryPrice>0).
* @param queryPrice minimum AERE402 payment per query, in settlementToken units
* (0 => free store, retrieval attestations need no payment).
* @param paymentRecipient where paidQuery net proceeds are forwarded (must be set iff priced).
*/
function createStore(
bytes32 storeId,
bytes calldata falconPubKey,
address settlementToken,
uint256 queryPrice,
address paymentRecipient
) external {
if (_stores[storeId].exists) revert StoreExists();
bool postQuantum = falconPubKey.length != 0;
if (postQuantum) {
// Fail-closed on a malformed PQC key: a store can never be created with a Falcon
// key the precompile could not parse.
if (falconPubKey.length != FALCON512_PK_LEN || uint8(falconPubKey[0]) != FALCON512_PK_HEADER) {
revert InvalidPubKey();
}
}
// Pricing config must be internally consistent: a priced store needs a token to be
// paid in and a recipient to be paid to; a free store must set neither.
if (queryPrice != 0) {
if (settlementToken == address(0) || paymentRecipient == address(0)) revert InvalidConfig();
} else {
if (settlementToken != address(0) || paymentRecipient != address(0)) revert InvalidConfig();
}
Store storage s = _stores[storeId];
s.exists = true;
s.owner = msg.sender;
s.settlementToken = settlementToken;
s.paymentRecipient = paymentRecipient;
s.queryPrice = queryPrice;
s.falconPubKey = falconPubKey;
emit StoreCreated(storeId, msg.sender, postQuantum, settlementToken, queryPrice, paymentRecipient);
}
/**
* @notice Append a new versioned commitment root for a store. Version numbers start at 1 and
* increase by exactly one per commit; the challenge binds the version, so a signature
* is single-use and history is append-only.
*
* AUTHENTICATION:
* - post-quantum store: `falconSig` MUST verify, on-chain via 0x0AE1, over
* commitChallenge(...). An invalid or malformed signature reverts
* PQCVerificationFailed / InvalidSignatureLength and records nothing (fail-closed).
* msg.sender is unrestricted: authorship is the Falcon signature, so any relayer
* may submit and only the key holder can produce a valid commit.
* - classical store: msg.sender MUST be the owner; `falconSig` is ignored.
*
* @return version the new version number (== the store's current version after this call).
*/
function commit(
bytes32 storeId,
bytes32 vectorRoot,
uint64 vectorCount,
string calldata uri,
bytes calldata falconSig
) external returns (uint64 version) {
Store storage s = _stores[storeId];
if (!s.exists) revert UnknownStore();
if (vectorRoot == bytes32(0)) revert EmptyRoot();
version = s.version + 1;
bytes32 challenge = commitChallenge(storeId, version, vectorRoot, vectorCount, uri);
if (s.falconPubKey.length != 0) {
// Fail-closed post-quantum authentication.
if (!_verifyFalcon512(s.falconPubKey, challenge, falconSig)) revert PQCVerificationFailed();
} else {
// Classical owner authentication.
if (msg.sender != s.owner) revert NotOwner();
}
// Effects: append the new version. No path can overwrite an earlier entry.
_versions[storeId].push(
Version({
vectorRoot: vectorRoot,
vectorCount: vectorCount,
committedAt: uint64(block.timestamp),
blockNumber: uint64(block.number),
uri: uri
})
);
s.version = version;
commitCount += 1;
emit Committed(storeId, version, vectorRoot, vectorCount, challenge, uri);
}
/* ============================== retrieval =============================== */
/**
* @notice Record a retrieval attestation: a provider (msg.sender) attests that, for `queryHash`
* against committed store `version`, the returned result set is committed by `resultRoot`.
* The named version MUST exist (1..current) or the call reverts fail-closed, so an
* attestation can never reference a store state that was never committed.
*
* On a PRICED store, the attestation MUST be backed by a matching, unconsumed AERE402
* payment receipt (see paidQuery); the receipt is consumed one-shot AND only its bound
* provider (recorded at paidQuery) may consume it, so a third party cannot front-run the
* honest provider to burn the receipt with a junk resultRoot. On a FREE store, `receiptId`
* is ignored.
*
* @param version the committed store version the retrieval ran against.
* @param queryHash commitment to the query (e.g. keccak256 of the normalized query embedding).
* @param resultRoot Merkle root over the returned result set (leaves = per-result commitments).
* @param receiptId the paidQuery receipt backing this retrieval (priced stores only).
* @return retrievalId deterministic id of the recorded attestation.
*/
function attestRetrieval(
bytes32 storeId,
uint64 version,
bytes32 queryHash,
bytes32 resultRoot,
bytes32 receiptId
) external returns (bytes32 retrievalId) {
Store storage s = _stores[storeId];
if (!s.exists) revert UnknownStore();
// Fail-closed: the store version must have been committed.
if (version == 0 || version > s.version) revert UnknownVersion();
if (resultRoot == bytes32(0)) revert EmptyRoot();
// Payment gate for priced stores.
if (s.queryPrice != 0) {
QueryReceipt storage r = _receipts[receiptId];
if (!r.exists) revert PaymentRequired();
if (r.consumed) revert ReceiptConsumed();
// Only the provider the payer bound at paidQuery may consume this receipt. Without this a
// third party could front-run the honest provider and burn the receipt with a junk
// resultRoot (griefing / denial-of-service on a paid-for retrieval).
if (r.provider != msg.sender) revert NotReceiptProvider();
if (r.storeId != storeId || r.queryHash != queryHash) revert ReceiptMismatch();
if (r.amount < s.queryPrice) revert Underpaid(r.amount, s.queryPrice);
r.consumed = true;
}
uint64 idx = retrievalCount[storeId];
retrievalId = keccak256(abi.encode(address(this), storeId, idx));
_retrievals[retrievalId] = Retrieval({
exists: true,
version: version,
attestedAt: uint64(block.timestamp),
blockNumber: uint64(block.number),
provider: msg.sender,
queryHash: queryHash,
resultRoot: resultRoot
});
retrievalCount[storeId] = idx + 1;
emit RetrievalAttested(storeId, retrievalId, version, msg.sender, queryHash, resultRoot);
}
/* ============================== paid query ============================== */
/**
* @notice Pay for a query against a priced store, on the existing AERE402 rail, and mint a
* receipt that a retrieval provider can later cite in attestRetrieval. The paying
* AGENT authorizes the payment with its EIP-712 signature to the facilitator (exactly
* as any AERE402 payment); this contract is the `payee`, so the facilitator debits the
* agent, takes the protocol fee to AereSink, and transfers the remainder here, which is
* then forwarded to the store's payment recipient in the same transaction. This contract
* custodies no balance across calls.
*
* @param queryHash commitment to the query being paid for (bound into the receipt + resourceId).
* @param provider the retrieval provider this payment is for; the ONLY address that may later
* consume the minted receipt in attestRetrieval (binds the receipt so a third
* party cannot grief the honest provider). Must be non-zero.
* @param agentId the paying AERE402 agent id (in the AereAgent registry the facilitator uses).
* @param amount gross payment; must be >= the store's queryPrice.
* @param nonce the agent's AERE402 nonce for this payment (facilitator replay guard).
* @param deadline unix seconds after which the payment auth is stale.
* @param authSig the agent's EIP-712 PaymentAuth signature over (agentId, token, this, amount,
* resourceId, nonce, deadline) for the facilitator.
* @return receiptId deterministic id of the minted receipt.
*/
function paidQuery(
bytes32 storeId,
bytes32 queryHash,
address provider,
uint256 agentId,
uint256 amount,
uint256 nonce,
uint256 deadline,
bytes calldata authSig
) external nonReentrant returns (bytes32 receiptId) {
Store storage s = _stores[storeId];
if (!s.exists) revert UnknownStore();
if (s.queryPrice == 0) revert StoreIsFree();
if (provider == address(0)) revert ZeroAddress();
if (amount < s.queryPrice) revert Underpaid(amount, s.queryPrice);
receiptId = keccak256(abi.encode(address(this), storeId, queryHash, agentId, nonce));
if (_receipts[receiptId].exists) revert ReceiptExists();
address token = s.settlementToken;
address recipient = s.paymentRecipient;
// Bind the payment to THIS store + query so the facilitator's resourceId is meaningful.
bytes32 resourceId = queryResourceId(storeId, queryHash);
// Settle on the AERE402 rail. address(this) is the payee, satisfying the facilitator's
// msg.sender == payee guard. The facilitator sends (amount - fee) here; measure the delta
// so a changed fee never strands tokens, then forward the whole receipt to the recipient.
uint256 balBefore = IERC20(token).balanceOf(address(this));
FACILITATOR.settle(agentId, token, address(this), amount, resourceId, nonce, deadline, authSig);
uint256 received = IERC20(token).balanceOf(address(this)) - balBefore;
if (received > 0) {
if (!IERC20(token).transfer(recipient, received)) revert TransferFailed();
}
_receipts[receiptId] = QueryReceipt({
exists: true,
consumed: false,
paidAtVersion: s.version,
storeId: storeId,
queryHash: queryHash,
amount: amount,
payer: msg.sender,
provider: provider
});
emit QueryPaid(storeId, receiptId, msg.sender, agentId, amount, queryHash);
}
/* ============================= inclusion ============================== */
/**
* @notice Verify that `leaf` is a member of a committed store version's vectorRoot. This is the
* consumer's check that a returned vector was ACTUALLY in the committed store at version
* V. The leaf is the caller-supplied commitment to the vector + its metadata; the proof
* is a standard OpenZeppelin (sorted-pair keccak256) Merkle proof against vectorRoot.
* @dev Proves membership in a committed root only. It says nothing about semantic relevance;
* relevance is an off-chain property.
*/
function verifyVectorInclusion(bytes32 storeId, uint64 version, bytes32 leaf, bytes32[] calldata proof)
external
view
returns (bool)
{
Store storage s = _stores[storeId];
if (!s.exists) revert UnknownStore();
if (version == 0 || version > s.version) revert UnknownVersion();
bytes32 root = _versions[storeId][version - 1].vectorRoot;
return proof.verify(root, leaf);
}
/**
* @notice Verify that `leaf` is a member of a recorded retrieval's resultRoot, i.e. that the
* returned result the consumer holds was in the set the provider attested to.
*/
function verifyResultInclusion(bytes32 retrievalId, bytes32 leaf, bytes32[] calldata proof)
external
view
returns (bool)
{
Retrieval storage r = _retrievals[retrievalId];
if (!r.exists) revert UnknownRetrieval();
return proof.verify(r.resultRoot, leaf);
}
/* ================================ views ================================ */
/// @notice The exact 32-byte challenge a Falcon-512 commit must sign for (storeId, version).
/// Off-chain signers query this to know what to sign.
function commitChallenge(bytes32 storeId, uint64 version, bytes32 vectorRoot, uint64 vectorCount, string calldata uri)
public
view
returns (bytes32)
{
return keccak256(
abi.encode(COMMIT_DOMAIN, block.chainid, address(this), storeId, version, vectorRoot, vectorCount, keccak256(bytes(uri)))
);
}
/// @notice The resourceId a paidQuery binds into its AERE402 settlement for (storeId, queryHash).
function queryResourceId(bytes32 storeId, bytes32 queryHash) public view returns (bytes32) {
return keccak256(abi.encode(QUERY_DOMAIN, block.chainid, address(this), storeId, queryHash));
}
/// @notice True iff the store exists.
function storeExists(bytes32 storeId) external view returns (bool) {
return _stores[storeId].exists;
}
/// @notice Read a store's configuration and current version count.
function getStore(bytes32 storeId)
external
view
returns (
bool exists,
address owner,
bool postQuantum,
address settlementToken,
uint256 queryPrice,
address paymentRecipient,
uint64 version
)
{
Store storage s = _stores[storeId];
return (s.exists, s.owner, s.falconPubKey.length != 0, s.settlementToken, s.queryPrice, s.paymentRecipient, s.version);
}
/// @notice The registered Falcon-512 public key for a store (empty for a classical store).
function falconKeyOf(bytes32 storeId) external view returns (bytes memory) {
return _stores[storeId].falconPubKey;
}
/// @notice The number of committed versions for a store (== current version; 0 if none).
function versionCount(bytes32 storeId) external view returns (uint64) {
return _stores[storeId].version;
}
/// @notice The current (latest) committed root for a store. Reverts if nothing committed yet.
function currentRoot(bytes32 storeId)
external
view
returns (uint64 version, bytes32 vectorRoot, uint64 vectorCount, string memory uri)
{
Store storage s = _stores[storeId];
if (!s.exists) revert UnknownStore();
if (s.version == 0) revert UnknownVersion();
Version storage v = _versions[storeId][s.version - 1];
return (s.version, v.vectorRoot, v.vectorCount, v.uri);
}
/// @notice Read any committed version (1..current) of a store.
function getVersion(bytes32 storeId, uint64 version)
external
view
returns (bytes32 vectorRoot, uint64 vectorCount, uint64 committedAt, uint64 blockNumber, string memory uri)
{
Store storage s = _stores[storeId];
if (!s.exists) revert UnknownStore();
if (version == 0 || version > s.version) revert UnknownVersion();
Version storage v = _versions[storeId][version - 1];
return (v.vectorRoot, v.vectorCount, v.committedAt, v.blockNumber, v.uri);
}
/// @notice Read a recorded retrieval attestation.
function getRetrieval(bytes32 retrievalId)
external
view
returns (
bool exists,
uint64 version,
address provider,
bytes32 queryHash,
bytes32 resultRoot,
uint64 attestedAt,
uint64 blockNumber
)
{
Retrieval storage r = _retrievals[retrievalId];
return (r.exists, r.version, r.provider, r.queryHash, r.resultRoot, r.attestedAt, r.blockNumber);
}
/// @notice Read a paid-query receipt.
function getReceipt(bytes32 receiptId)
external
view
returns (
bool exists,
bool consumed,
bytes32 storeId,
bytes32 queryHash,
uint256 amount,
address payer,
uint64 paidAtVersion,
address provider
)
{
QueryReceipt storage r = _receipts[receiptId];
return (r.exists, r.consumed, r.storeId, r.queryHash, r.amount, r.payer, r.paidAtVersion, r.provider);
}
/* ============================ Falcon-512 verify ========================== */
/**
* @dev Build the exact Falcon-512 precompile input for `message`, staticcall 0x0AE1, and return
* true iff it answers 0x..01. `falconSig` = nonce(40) || esig. Any empty/short/zero return
* (a pre-fork node) is invalid. Same construction as AerePQCAttestation, restricted to
* Falcon-512.
*/
function _verifyFalcon512(bytes memory pubKey, bytes32 message, bytes memory falconSig)
internal
view
returns (bool)
{
if (falconSig.length <= FALCON_NONCE_LEN) revert InvalidSignatureLength();
uint256 sigLen = falconSig.length - FALCON_NONCE_LEN; // esig length
if (sigLen > type(uint16).max) revert InvalidSignatureLength();
bytes memory nonce = _slice(falconSig, 0, FALCON_NONCE_LEN);
bytes memory esig = _slice(falconSig, FALCON_NONCE_LEN, sigLen);
// sm = sigLen(2, big-endian) || nonce(40) || message(32) || esig
bytes memory sm = abi.encodePacked(uint16(sigLen), nonce, message, esig);
bytes memory input = abi.encodePacked(pubKey, sm);
(bool ok, bytes memory ret) = PRECOMPILE_FALCON512.staticcall(input);
return ok && ret.length >= 32 && ret[31] == 0x01;
}
function _slice(bytes memory data, uint256 start, uint256 len) internal pure returns (bytes memory out) {
out = new bytes(len);
for (uint256 i = 0; i < len; i++) {
out[i] = data[start + i];
}
}
}