aere-contracts/contracts/interop/AereOutboundOutbox.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

89 lines
4.6 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
/// @title AereOutboundOutbox
/// @notice AERE-side (chain 2800) source of the canonical OUTBOUND cross-chain
/// message. It emits exactly the log the `zk-interop` SP1 guest proves
/// inclusion of ("event R included in a QBFT-final AERE block"), so a
/// destination-chain `AereOutboundVerifier` can admit the message with
/// no bridge multisig — only a zk proof against AERE's finality vkey.
///
/// @dev CANONICAL MESSAGE BINDING (must stay byte-identical to the guest).
/// The guest's `interop_core::ReceiptInclusion::log_commitment(0)` commits:
///
/// commitment =
/// keccak256(
/// cid(32) // AERE chainId, left-padded
/// ‖ blockHash(32) // the QBFT-final block that carries the log
/// ‖ addr(32) // this Outbox address, left-padded
/// ‖ topic0(32) // keccak256(event signature)
/// ‖ topic1(32) // messageId (indexed)
/// ‖ topic2(32) // destChainId (indexed)
/// ‖ keccak256(data) // keccak(abi.encode(nonce,sender,handler,payload))
/// )
///
/// The verifier re-derives this from the delivered fields + the proof's
/// committed `blockHash`, and requires it to equal the proof commitment.
/// That is what binds "the message you are delivering" to "the log AERE's
/// validators finalised". If you change this event's shape, `messageId`,
/// or the field order, you MUST update the guest and the verifier together.
///
/// @dev LOG-INDEX-0 CONSTRAINT (honest integration limitation). The guest's
/// `verify_interop` commits `log_commitment(0)` — the FIRST log of the
/// proven receipt. So the prover must prove a receipt whose log[0] is this
/// `AereCrossChainMessage`. The safe pattern is a direct EOA -> Outbox
/// `send` call (receipt has exactly one log, at index 0). If `send` is
/// invoked as a sub-call after other logs in the same transaction, that
/// message is NOT at log index 0 and cannot be delivered with the current
/// guest/verifier. Extending to an arbitrary log index needs a matching
/// change in both the guest and `AereOutboundVerifier`.
///
/// SECURITY MODEL — read before quoting anywhere.
/// Delivery security equals a `>= quorum` of AERE's 5 QBFT validators sealing
/// the source block. Those seals are classical ECDSA today (quantum-vulnerable;
/// a Falcon seal slot is reserved but NOT active — mainnet consensus is not
/// post-quantum). The destination-side SP1 Groth16 wrapper is BN254, also
/// quantum-vulnerable. This is TRUST-MINIMISED, not trustless: it removes the
/// bridge multisig, it does not remove trust in AERE's validator set.
contract AereOutboundOutbox {
/// @notice AERE mainnet chain id. Bound into every message commitment.
uint256 public constant AERE_CHAIN_ID = 2800;
/// @notice Monotonic per-outbox message counter. `(AERE_CHAIN_ID, nonce)` is
/// unique, so no two distinct messages share a `messageId`.
uint256 public nonce;
/// @notice The canonical cross-chain message the guest proves inclusion of.
/// @dev Signature string (topic0 preimage):
/// "AereCrossChainMessage(bytes32,uint256,uint256,address,address,bytes)"
event AereCrossChainMessage(
bytes32 indexed messageId,
uint256 indexed destChainId,
uint256 nonce,
address sender,
address targetHandler,
bytes payload
);
/// @notice Emit an outbound message for `destChainId`, to be delivered to
/// `targetHandler` on that chain by an `AereOutboundVerifier`.
/// @param destChainId the destination chain id (bound into `messageId`).
/// @param targetHandler the handler contract on the destination chain.
/// @param payload opaque application bytes handed to the target handler.
/// @return messageId content commitment / replay key on the destination.
function send(uint256 destChainId, address targetHandler, bytes calldata payload)
external
returns (bytes32 messageId)
{
require(targetHandler != address(0), "outbox: zero handler");
uint256 n = nonce;
nonce = n + 1;
// messageId binds source chain, destination chain, nonce, sender and
// the full payload. The verifier re-computes it identically.
messageId = keccak256(
abi.encode(AERE_CHAIN_ID, destChainId, n, msg.sender, targetHandler, payload)
);
emit AereCrossChainMessage(messageId, destChainId, n, msg.sender, targetHandler, payload);
}
}