// 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); } }