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