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.
650 lines
30 KiB
Solidity
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];
|
|
}
|
|
}
|
|
}
|