// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

/**
 * @title AereShutterMempoolV2 - threshold-BLS anti-MEV encrypted mempool (PoC)
 * @notice APPLICATION-LEVEL, OPT-IN Shutter-style encrypted mempool. This is NOT
 *         an L1 base-layer change: a Hyperledger Besu QBFT chain cannot gain a
 *         protocol-level encrypted mempool without a client fork. Instead this
 *         contract implements the commit -> order -> threshold-decrypt pipeline
 *         at the application layer, so a proposer/sequencer cannot see plaintext
 *         transactions BEFORE the ordering for an epoch is fixed.
 *
 *         Unlike the V0 orchestration contract (which used a keccak placeholder
 *         for the "decryption key"), V2 verifies a REAL (t,N) threshold-BLS
 *         scheme on-chain using the EIP-2537 BLS12-381 pairing precompile at
 *         address 0x0f (PROBED LIVE on AERE chain 2800: a valid pairing identity
 *         returns 1, garbage input reverts).
 *
 *   Cryptography (Boldyreva threshold BLS over BLS12-381):
 *     - Committee master secret s is Shamir (t,N)-shared off-chain by a dealer.
 *     - Group public key   PK  = s * g2    (G2) : the epoch encryption pubkey.
 *     - Keyper pubkeys      P_i = s_i * g2  (G2) : published VSS commitments.
 *     - Epoch identity      H1  = hashToCurve(epoch tag) in G1.
 *     - Epoch decrypt key   DK  = s * H1    (G1) : the threshold signature.
 *     - Keyper share        sig_i = s_i * H1 (G1).
 *     - DK is reconstructed off-chain from t shares via Lagrange interpolation.
 *
 *   On-chain pairing checks (all via 0x0f):
 *     share valid : e(sig_i, g2) == e(H1, P_i)  <=>  e(sig_i,g2)*e(-H1,P_i)==1
 *     DK    valid : e(DK,   g2) == e(H1, PK)    <=>  e(DK,  g2)*e(-H1,PK )==1
 *   A share or a reconstructed DK that does not satisfy its pairing REVERTS,
 *   so a keyper cannot post an inconsistent share and no forged DK is accepted.
 *
 *   Anti-MEV invariant:
 *     1. Users submit ciphertexts + a hiding/binding commitment while the epoch
 *        is Open. The sequencer sees only ciphertexts.
 *     2. The sequencer commits an ordering (Merkle root over positions) BEFORE
 *        any decryption share exists. submitDecryptionShare REVERTS until then.
 *     3. Keypers post t valid shares; anyone reconstructs DK off-chain; the
 *        reconstruction is pairing-verified on-chain at finalizeEpoch.
 *     4. Reveal-and-execute walks the committed ordering strictly in sequence;
 *        a plaintext that does not open its committed value REVERTS, and an
 *        out-of-order reveal REVERTS.
 *
 *   HONEST SCOPE / ASSUMPTIONS:
 *     - The symmetric unmasking step (hashing the GT pairing element to a byte
 *       mask for XOR) is performed OFF-CHAIN by the executor: the 0x0f precompile
 *       returns only a pairing-equality boolean, it cannot output a GT element to
 *       hash. On-chain, plaintext<->commitment binding is a keccak commitment.
 *       DK correctness itself is fully pairing-verified on-chain, so decryption is
 *       deterministic and cannot be produced before t keypers act.
 *     - PK, P_i, H1 and its negation H1neg are published by the committee dealer
 *       at registration/epoch start (the "VSS commitments"). A malformed published
 *       point only breaks LIVENESS (an honest DK/share would fail its pairing); it
 *       cannot let a non-committee actor forge DK, which still requires the master
 *       secret s that only t-of-N shares reconstruct.
 *     - Trusted-dealer Shamir setup is used for the PoC; a production system would
 *       run a DKG so no single party ever holds s.
 */
contract AereShutterMempoolV2 {
    /* ================================ roles ================================ */
    address public coordinator; // committee dealer / admin
    address public sequencer;   // commits the epoch ordering
    address public executor;    // optional consumer that executes revealed txs

    /* ============================== precompile ============================= */
    address internal constant BLS_PAIRING = address(0x0f); // EIP-2537 pairing check

    /* =============================== committee ============================= */
    bool   public committeeSet;
    uint16 public threshold;   // t
    uint16 public keyperCount; // N
    mapping(uint16 => address) public keyperAddr; // 0-based
    mapping(uint16 => bytes)   public keyperPub;  // P_i = s_i*g2, 256-byte G2

    bytes public G2_GEN; // canonical BLS12-381 G2 generator (256-byte EIP-2537)

    /* ================================ epochs ============================== */
    enum Phase { None, Open, Ordered, Finalized }

    struct Epoch {
        Phase   phase;
        uint64  startBlock;
        bytes   pk;            // 256-byte G2 group/encryption pubkey
        bytes   h1;            // 128-byte G1 epoch identity
        bytes   h1neg;         // 128-byte G1 -h1
        bytes32 orderingRoot;  // Merkle root over committed execution positions
        bytes   dk;            // 128-byte G1 reconstructed decryption key
        uint256 envelopeCount;
        uint256 shareCount;
        uint256 nextExecIndex; // strict sequential execution cursor
    }

    mapping(uint64 => Epoch) internal _epochs;
    mapping(uint64 => mapping(uint16 => bytes)) internal _shareOf;
    mapping(uint64 => mapping(uint16 => bool))  public shareSubmitted;
    mapping(uint64 => mapping(bytes32 => bool)) public committedCommitment;
    mapping(uint64 => mapping(bytes32 => bool)) public executedCommitment;

    /* ================================ events ============================== */
    event CommitteeRegistered(uint16 threshold, uint16 keyperCount);
    event SequencerSet(address indexed sequencer);
    event ExecutorSet(address indexed executor);
    event EpochStarted(uint64 indexed epochId, bytes pk, bytes h1);
    event Encrypted(uint64 indexed epochId, uint256 indexed index, address indexed sender, bytes32 commitment, bytes ciphertext);
    event OrderingCommitted(uint64 indexed epochId, bytes32 orderingRoot, uint256 envelopeCount);
    event ShareSubmitted(uint64 indexed epochId, uint16 indexed keyperIndex, bytes32 shareHash);
    event EpochFinalized(uint64 indexed epochId, bytes32 dkHash, uint256 shareCount);
    event Executed(uint64 indexed epochId, uint256 indexed index, bytes32 plaintextHash, bytes32 commitment);
    event KeyperFlagged(uint64 indexed epochId, uint16 indexed keyperIndex, address indexed keyper);

    /* ================================ errors ============================== */
    error NotCoordinator();
    error NotSequencer();
    error CommitteeAlreadySet();
    error CommitteeNotSet();
    error BadArgs();
    error BadPointLength();
    error EpochExists();
    error EpochNotOpen();
    error OrderingNotCommitted();
    error OrderingAlreadyCommitted();
    error NoEnvelopes();
    error NotFinalized();
    error AlreadyFinalized();
    error DuplicateShare();
    error BadShare();          // share fails on-chain pairing check
    error NotEnoughShares();
    error BadReconstruction(); // DK fails on-chain pairing check
    error KeyperIndexRange();
    error DuplicateCommitment();
    error UnknownCommitment();
    error AlreadyExecuted();
    error OutOfOrder();
    error BadMerkleProof();
    error PlaintextMismatch();
    error ShareIsConsistent();

    /* ============================== modifiers ============================= */
    modifier onlyCoordinator() { if (msg.sender != coordinator) revert NotCoordinator(); _; }
    modifier onlySequencer()   { if (msg.sender != sequencer)   revert NotSequencer();   _; }

    /* ============================= constructor ============================ */
    constructor(address coordinator_, bytes memory g2Gen) {
        if (coordinator_ == address(0) || g2Gen.length != 256) revert BadArgs();
        coordinator = coordinator_;
        sequencer = coordinator_;
        G2_GEN = g2Gen;
    }

    /* ============================== admin ops ============================= */
    function setSequencer(address s) external onlyCoordinator { sequencer = s; emit SequencerSet(s); }
    function setExecutor(address e)  external onlyCoordinator { executor  = e; emit ExecutorSet(e); }

    /// @notice Register the (t,N) keyper committee with their BLS12-381 G2
    ///         public keys P_i = s_i*g2 (the on-chain VSS commitments). One-shot.
    function registerKeypers(address[] calldata addrs, bytes[] calldata pubkeys, uint16 t)
        external onlyCoordinator
    {
        if (committeeSet) revert CommitteeAlreadySet();
        uint256 n = addrs.length;
        if (n == 0 || pubkeys.length != n || t == 0 || t > n || n > type(uint16).max) revert BadArgs();
        for (uint256 i = 0; i < n; i++) {
            if (pubkeys[i].length != 256) revert BadPointLength();
            keyperAddr[uint16(i)] = addrs[i];
            keyperPub[uint16(i)] = pubkeys[i];
        }
        threshold = t;
        keyperCount = uint16(n);
        committeeSet = true;
        emit CommitteeRegistered(t, uint16(n));
    }

    /* ============================== epoch ops ============================= */
    /// @notice Open an epoch by publishing the epoch encryption pubkey `pk`
    ///         (= the committee group key PK), the epoch identity point `h1`
    ///         (= hashToCurve(epoch tag) in G1) and its negation `h1neg`.
    function startEpoch(uint64 epochId, bytes calldata pk, bytes calldata h1, bytes calldata h1neg)
        external onlyCoordinator
    {
        if (!committeeSet) revert CommitteeNotSet();
        if (_epochs[epochId].phase != Phase.None) revert EpochExists();
        if (pk.length != 256 || h1.length != 128 || h1neg.length != 128) revert BadPointLength();
        Epoch storage e = _epochs[epochId];
        e.phase = Phase.Open;
        e.startBlock = uint64(block.number);
        e.pk = pk;
        e.h1 = h1;
        e.h1neg = h1neg;
        emit EpochStarted(epochId, pk, h1);
    }

    /// @notice Submit an encrypted tx envelope while the epoch is Open.
    ///         `ciphertext` = U(256-byte G2) || C(payload XOR mask); the full
    ///         blob is emitted for off-chain availability, only its binding
    ///         `commitment` = keccak256(abi.encode(plaintext, opening)) is stored.
    function submitEncrypted(uint64 epochId, bytes calldata ciphertext, bytes32 commitment)
        external returns (uint256 index)
    {
        Epoch storage e = _epochs[epochId];
        if (e.phase != Phase.Open) revert EpochNotOpen();
        if (commitment == bytes32(0) || ciphertext.length < 256) revert BadArgs();
        if (committedCommitment[epochId][commitment]) revert DuplicateCommitment();
        committedCommitment[epochId][commitment] = true;
        index = e.envelopeCount++;
        emit Encrypted(epochId, index, msg.sender, commitment, ciphertext);
    }

    /// @notice Sequencer commits the epoch ordering as a Merkle root over
    ///         execution positions. MUST happen before any decryption share.
    function commitOrdering(uint64 epochId, bytes32 orderingRoot) external onlySequencer {
        Epoch storage e = _epochs[epochId];
        if (e.phase != Phase.Open) {
            if (e.phase == Phase.None) revert EpochNotOpen();
            revert OrderingAlreadyCommitted();
        }
        if (e.envelopeCount == 0) revert NoEnvelopes();
        if (orderingRoot == bytes32(0)) revert BadArgs();
        e.orderingRoot = orderingRoot;
        e.phase = Phase.Ordered;
        emit OrderingCommitted(epochId, orderingRoot, e.envelopeCount);
    }

    /// @notice A keyper (or a relayer on its behalf) posts sig_i = s_i*H1. The
    ///         share is REJECTED unless e(sig_i,g2)==e(H1,P_i) verifies on-chain.
    ///         Reverts before the ordering is committed (prevents early decrypt).
    function submitDecryptionShare(uint64 epochId, uint16 keyperIndex, bytes calldata share) external {
        Epoch storage e = _epochs[epochId];
        if (e.phase == Phase.Open || e.phase == Phase.None) revert OrderingNotCommitted();
        if (keyperIndex >= keyperCount) revert KeyperIndexRange();
        if (shareSubmitted[epochId][keyperIndex]) revert DuplicateShare();
        if (share.length != 128) revert BadPointLength();
        // e(sig_i, g2) * e(-H1, P_i) == 1
        if (!_pairingCheck2(share, G2_GEN, e.h1neg, keyperPub[keyperIndex])) revert BadShare();
        _shareOf[epochId][keyperIndex] = share;
        shareSubmitted[epochId][keyperIndex] = true;
        e.shareCount++;
        emit ShareSubmitted(epochId, keyperIndex, keccak256(share));
    }

    /// @notice Once >= t valid shares exist, submit the off-chain Lagrange
    ///         reconstruction DK. Accepted only if e(DK,g2)==e(H1,PK) verifies.
    function finalizeEpoch(uint64 epochId, bytes calldata dk) external {
        Epoch storage e = _epochs[epochId];
        if (e.phase == Phase.Finalized) revert AlreadyFinalized();
        if (e.phase != Phase.Ordered) revert OrderingNotCommitted();
        if (e.shareCount < threshold) revert NotEnoughShares();
        if (dk.length != 128) revert BadPointLength();
        // e(DK, g2) * e(-H1, PK) == 1
        if (!_pairingCheck2(dk, G2_GEN, e.h1neg, e.pk)) revert BadReconstruction();
        e.dk = dk;
        e.phase = Phase.Finalized;
        emit EpochFinalized(epochId, keccak256(dk), e.shareCount);
    }

    /// @notice Reveal a plaintext at its committed execution position and mark it
    ///         executed. Execution positions MUST be revealed strictly in order.
    ///         Verifies: (a) sequential position, (b) the commitment was actually
    ///         submitted, (c) Merkle membership of (epochId,index,commitment) in
    ///         the committed ordering, (d) the plaintext opens the commitment.
    function revealAndExecute(
        uint64 epochId,
        uint256 index,
        bytes calldata plaintext,
        bytes32 opening,
        bytes32[] calldata proof
    ) external {
        Epoch storage e = _epochs[epochId];
        if (e.phase != Phase.Finalized) revert NotFinalized();
        if (index != e.nextExecIndex) revert OutOfOrder();

        bytes32 commitment = keccak256(abi.encode(plaintext, opening));
        if (!committedCommitment[epochId][commitment]) revert UnknownCommitment();
        if (executedCommitment[epochId][commitment]) revert AlreadyExecuted();

        // Merkle leaf = keccak(bytes.concat(keccak(abi.encode(epochId,index,commitment)))) (OZ sorted-pair)
        bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(epochId, index, commitment))));
        if (!MerkleProof.verify(proof, e.orderingRoot, leaf)) revert BadMerkleProof();

        executedCommitment[epochId][commitment] = true;
        e.nextExecIndex = index + 1;
        emit Executed(epochId, index, keccak256(plaintext), commitment);

        // Optional: hand the decoded plaintext to a consumer executor.
        address ex = executor;
        if (ex != address(0)) {
            (bool ok, ) = ex.call(abi.encodeWithSignature("execute(uint64,uint256,bytes)", epochId, index, plaintext));
            ok; // best-effort; failure of the inner consumer does not roll back the reveal record
        }
    }

    /* ============================ slashing flag =========================== */
    /// @notice Flag a keyper who published (off-chain) a share that is
    ///         INCONSISTENT with its on-chain VSS commitment P_i. Reverts if the
    ///         alleged share is actually valid (no false accusations).
    function reportInconsistentShare(uint64 epochId, uint16 keyperIndex, bytes calldata allegedShare) external {
        Epoch storage e = _epochs[epochId];
        if (e.phase == Phase.None) revert EpochNotOpen();
        if (keyperIndex >= keyperCount) revert KeyperIndexRange();
        if (allegedShare.length != 128) revert BadPointLength();
        if (_pairingCheck2(allegedShare, G2_GEN, e.h1neg, keyperPub[keyperIndex])) revert ShareIsConsistent();
        emit KeyperFlagged(epochId, keyperIndex, keyperAddr[keyperIndex]);
    }

    /* ============================ pairing helper ========================== */
    /// @dev Returns true iff e(a1,b1) * e(a2,b2) == 1 via the EIP-2537 pairing
    ///      precompile at 0x0f. a* are 128-byte G1, b* are 256-byte G2. A revert
    ///      inside the precompile (e.g. a non-subgroup point) yields false.
    function _pairingCheck2(bytes memory a1, bytes memory b1, bytes memory a2, bytes memory b2)
        internal view returns (bool)
    {
        if (a1.length != 128 || a2.length != 128 || b1.length != 256 || b2.length != 256) revert BadPointLength();
        bytes memory input = bytes.concat(a1, b1, a2, b2); // 2 pairs * 384 = 768 bytes
        (bool ok, bytes memory out) = BLS_PAIRING.staticcall(input);
        if (!ok || out.length != 32) return false;
        return abi.decode(out, (uint256)) == 1;
    }

    /* =============================== views =============================== */
    function getEpoch(uint64 epochId)
        external view
        returns (Phase phase, uint64 startBlock, bytes32 orderingRoot, uint256 envelopeCount, uint256 shareCount, uint256 nextExecIndex)
    {
        Epoch storage e = _epochs[epochId];
        return (e.phase, e.startBlock, e.orderingRoot, e.envelopeCount, e.shareCount, e.nextExecIndex);
    }
    function epochPk(uint64 epochId)    external view returns (bytes memory) { return _epochs[epochId].pk; }
    function epochH1(uint64 epochId)    external view returns (bytes memory) { return _epochs[epochId].h1; }
    function epochDk(uint64 epochId)    external view returns (bytes memory) { return _epochs[epochId].dk; }
    function shareOf(uint64 epochId, uint16 i) external view returns (bytes memory) { return _shareOf[epochId][i]; }
    function isFinalized(uint64 epochId) external view returns (bool) { return _epochs[epochId].phase == Phase.Finalized; }
    function verifyShareView(uint64 epochId, uint16 keyperIndex, bytes calldata share) external view returns (bool) {
        if (keyperIndex >= keyperCount || share.length != 128) return false;
        return _pairingCheck2(share, G2_GEN, _epochs[epochId].h1neg, keyperPub[keyperIndex]);
    }
}
