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

/**
 * @title BLS12381 — thin, spec-correct wrappers over AERE's LIVE EIP-2537 precompiles.
 *
 * @notice AERE Network (chain 2800, Prague) exposes the EIP-2537 BLS12-381 precompiles
 *         natively. This library builds the exact EIP-2537 wire encodings and delegates
 *         all field/curve math to those precompiles. It also implements RFC-9380
 *         hash-to-curve for G2 (expand_message_xmd over SHA-256, hash_to_field via the
 *         modexp precompile, SSWU + isogeny + cofactor clear via MAP_FP2_TO_G2), so a
 *         message can be mapped to a G2 point ON CHAIN with no trusted hint.
 *
 *         Point encodings follow EIP-2537 exactly:
 *           - Fp element  = 64 bytes  (16 zero bytes || 48-byte big-endian value)
 *           - G1 point    = 128 bytes (x || y),               infinity = 128 zero bytes
 *           - Fp2 element = 128 bytes (c0 || c1)
 *           - G2 point    = 256 bytes (x.c0 || x.c1 || y.c0 || y.c1), infinity = 256 zeros
 *
 * @dev    SECURITY / HONESTY. BLS12-381 is a CLASSICAL, pairing-friendly curve and is
 *         NOT post-quantum. This library is the INBOUND (Ethereum -> AERE) crypto: it
 *         inherits Ethereum's sync-committee security model, which is classical and
 *         quantum-vulnerable. It says nothing about AERE's own consensus (QBFT / ECDSA)
 *         or AERE's PQC precompiles. See AereEthLightClient for the full model.
 */
library BLS12381 {
    // ---- EIP-2537 precompile addresses (Prague canonical) --------------------
    address internal constant G1ADD = address(uint160(0x0b));
    address internal constant G2ADD = address(uint160(0x0d));
    address internal constant PAIRING = address(uint160(0x0f));
    address internal constant MAP_FP2_TO_G2 = address(uint160(0x11));
    // ---- Legacy precompiles used by hash-to-curve ---------------------------
    address internal constant SHA256 = address(uint160(0x02));
    address internal constant MODEXP = address(uint160(0x05));

    // Field modulus p of BLS12-381, big-endian 48 bytes.
    bytes internal constant P =
        hex"1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab";

    uint256 internal constant G1_LEN = 128;
    uint256 internal constant G2_LEN = 256;

    // -------------------------------------------------------------------------
    //                              curve operations
    // -------------------------------------------------------------------------

    /// @notice G1 point addition via precompile 0x0b. `a`,`b` are 128-byte G1 encodings.
    function g1Add(bytes memory a, bytes memory b) internal view returns (bytes memory out) {
        require(a.length == G1_LEN && b.length == G1_LEN, "BLS:g1add len");
        out = _call(G1ADD, bytes.concat(a, b), G1_LEN);
    }

    /// @notice G2 point addition via precompile 0x0d. `a`,`b` are 256-byte G2 encodings.
    function g2Add(bytes memory a, bytes memory b) internal view returns (bytes memory out) {
        require(a.length == G2_LEN && b.length == G2_LEN, "BLS:g2add len");
        out = _call(G2ADD, bytes.concat(a, b), G2_LEN);
    }

    /// @notice Map an Fp2 element (two 64-byte Fp coords c0,c1) to G2 via precompile 0x11.
    ///         The precompile applies SSWU + the 3-isogeny + cofactor clearing, so the
    ///         result is a valid member of the G2 prime-order subgroup.
    function mapFp2ToG2(bytes memory fp2) internal view returns (bytes memory out) {
        require(fp2.length == 128, "BLS:map len");
        out = _call(MAP_FP2_TO_G2, fp2, G2_LEN);
    }

    /// @notice EIP-2537 pairing check. `input` is k concatenated (G1 || G2) pairs
    ///         (k * 384 bytes). Returns true iff the product of pairings equals one.
    function pairing(bytes memory input) internal view returns (bool) {
        require(input.length % 384 == 0 && input.length != 0, "BLS:pair len");
        bytes memory ret = _call(PAIRING, input, 32);
        return ret[31] == 0x01;
    }

    // -------------------------------------------------------------------------
    //                       signature verification (min-pubkey-size)
    // -------------------------------------------------------------------------

    /// @notice Verify a BLS signature under the Ethereum "min-pubkey-size" scheme:
    ///         public key in G1 (128B), signature in G2 (256B). Checks
    ///             e(-G1_generator, signature) * e(pubkey, H(message)) == 1
    ///         where H = hashToG2(message, dst). All heavy math runs on the LIVE
    ///         precompiles; the message is hashed to the curve on chain (no hint).
    /// @param pubkeyG1 aggregate (or single) public key, 128-byte G1 encoding
    /// @param message  the 32-byte signing root (or any message) that was signed
    /// @param signatureG2 the signature, 256-byte G2 encoding
    /// @param dst      the RFC-9380 domain separation tag used when signing
    function verify(
        bytes memory pubkeyG1,
        bytes memory message,
        bytes memory signatureG2,
        bytes memory dst
    ) internal view returns (bool) {
        require(pubkeyG1.length == G1_LEN, "BLS:pk len");
        require(signatureG2.length == G2_LEN, "BLS:sig len");
        bytes memory hm = hashToG2(message, dst);
        // pairing input = (-G1) || sig  ++  pk || H(m)
        bytes memory input = bytes.concat(NEG_G1_GENERATOR(), signatureG2, pubkeyG1, hm);
        return pairing(input);
    }

    // -------------------------------------------------------------------------
    //                        RFC-9380 hash-to-curve (G2)
    // -------------------------------------------------------------------------

    /// @notice Hash an arbitrary message to a point in G2, per RFC-9380
    ///         (expand_message_xmd:SHA-256, SSWU_RO). Reproduces the reference
    ///         behaviour of noble-curves bls12_381.G2.hashToCurve (validated).
    function hashToG2(bytes memory message, bytes memory dst) internal view returns (bytes memory) {
        // 256 bytes = count(2) * m(2) * L(64)
        bytes memory uniform = _expandMessageXmd(message, dst, 256);
        bytes memory fp2a = bytes.concat(_reduceModP(uniform, 0), _reduceModP(uniform, 64));
        bytes memory fp2b = bytes.concat(_reduceModP(uniform, 128), _reduceModP(uniform, 192));
        bytes memory q0 = mapFp2ToG2(fp2a);
        bytes memory q1 = mapFp2ToG2(fp2b);
        return g2Add(q0, q1);
    }

    /// @dev expand_message_xmd (RFC-9380 §5.4.1) with SHA-256, b_in_bytes=32.
    ///      len_in_bytes must be a multiple of 32 and <= 255*32.
    function _expandMessageXmd(bytes memory message, bytes memory dst, uint256 lenInBytes)
        private
        view
        returns (bytes memory)
    {
        require(dst.length <= 255, "BLS:dst too long");
        uint256 ell = lenInBytes / 32; // b_in_bytes = 32
        require(ell * 32 == lenInBytes && ell <= 255, "BLS:xmd len");
        bytes memory dstPrime = bytes.concat(dst, bytes1(uint8(dst.length)));
        bytes memory lBytes = bytes.concat(bytes1(uint8(lenInBytes >> 8)), bytes1(uint8(lenInBytes)));
        // b0 = H( Z_pad(64) || msg || l_i_b(2) || 0x00 || dst_prime )
        bytes memory zPad = new bytes(64);
        bytes32 b0 = _sha256(bytes.concat(zPad, message, lBytes, bytes1(0x00), dstPrime));
        // b1 = H( b0 || 0x01 || dst_prime )
        bytes32 bPrev = _sha256(bytes.concat(b0, bytes1(0x01), dstPrime));
        bytes memory out = new bytes(lenInBytes);
        _writeWord(out, 0, bPrev);
        for (uint256 i = 2; i <= ell; i++) {
            bytes32 xored = b0 ^ bPrev;
            bPrev = _sha256(bytes.concat(xored, bytes1(uint8(i)), dstPrime));
            _writeWord(out, (i - 1) * 32, bPrev);
        }
        return out;
    }

    /// @dev Reduce the 64-byte big-endian value at `uniform[off:off+64]` modulo p,
    ///      returning it as a 64-byte EIP-2537 Fp element (16 zero bytes || 48 bytes).
    ///      Uses the modexp precompile: x mod p = x^1 mod p.
    function _reduceModP(bytes memory uniform, uint256 off) private view returns (bytes memory) {
        bytes memory base = new bytes(64);
        for (uint256 i = 0; i < 64; i++) base[i] = uniform[off + i];
        // modexp input: <baseLen=64><expLen=1><modLen=48> base exp(0x01) mod(p)
        bytes memory input = bytes.concat(
            bytes32(uint256(64)),
            bytes32(uint256(1)),
            bytes32(uint256(48)),
            base,
            bytes1(0x01),
            P
        );
        bytes memory r48 = _call(MODEXP, input, 48); // 48-byte reduced value
        bytes memory fp = new bytes(64); // 16 zero pad + 48
        for (uint256 i = 0; i < 48; i++) fp[16 + i] = r48[i];
        return fp;
    }

    // -------------------------------------------------------------------------
    //                                helpers
    // -------------------------------------------------------------------------

    function _sha256(bytes memory data) private view returns (bytes32 h) {
        bytes memory r = _call(SHA256, data, 32);
        assembly {
            h := mload(add(r, 32))
        }
    }

    function _writeWord(bytes memory out, uint256 pos, bytes32 word) private pure {
        assembly {
            mstore(add(add(out, 32), pos), word)
        }
    }

    /// @dev staticcall a precompile, require success and an exact-length return.
    function _call(address to, bytes memory input, uint256 outLen) private view returns (bytes memory out) {
        bool ok;
        out = new bytes(outLen);
        assembly {
            ok := staticcall(gas(), to, add(input, 32), mload(input), add(out, 32), outLen)
        }
        require(ok, "BLS:precompile");
        // guard against a short return (e.g. precompile missing on a non-Prague chain)
        require(out.length == outLen, "BLS:retlen");
    }

    /// @dev The negation of the G1 generator, EIP-2537 encoded (128 bytes).
    ///      x = generator.x ; y = p - generator.y. Precomputed constant.
    function NEG_G1_GENERATOR() internal pure returns (bytes memory) {
        return
            hex"0000000000000000000000000000000017f1d3a73197d7942695638c4fa9ac0f"
            hex"c3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb"
            hex"00000000000000000000000000000000114d1d6855d545a8aa7d76c8cf2e21f2"
            hex"67816aef1db507c96655b9d5caac42364e6f38ba0ecb751bad54dcd6b939c2ca";
    }
}
