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

/**
 * @title AereLatticeVerifier — lattice PQC verification core (Falcon/Dilithium family)
 * @notice The mathematical HEART of lattice-based post-quantum signature
 *         verification, on-chain: negacyclic polynomial arithmetic in the ring
 *         R_q = Z_q[x]/(x^n + 1) with q = 12289 (the Falcon modulus), plus the
 *         short-vector (Euclidean norm) bound check.
 *
 *         Given a public polynomial `h`, a challenge polynomial `c` (in Falcon,
 *         c = HashToPoint(nonce || message)), and a candidate signature
 *         (`s1`, `s2`), it checks the Falcon verification relation
 *             s1 == c - s2 * h   (mod q, in the ring)
 *         and that (s1, s2) is a SHORT vector: ||(s1,s2)||^2 <= bound. A short
 *         solution only exists to someone holding the NTRU trapdoor, which is
 *         hard for classical AND quantum computers — that is the post-quantum
 *         security.
 *
 *         HONEST SCOPE: this is the ring-arithmetic + norm core, parameterised
 *         by n (demo at 256; Falcon uses 512/1024). A SPEC-COMPLETE Falcon-512
 *         verifier additionally needs SHAKE-256 HashToPoint, Falcon's compressed
 *         signature decoding, and validation against NIST KAT vectors — a
 *         separate, larger effort. This contract proves AERE can do the lattice
 *         math on-chain; it is NOT a drop-in NIST Falcon-512 verifier.
 */
contract AereLatticeVerifier {
    uint256 public constant Q = 12289;      // Falcon modulus
    uint256 public constant N = 64;         // ring degree (demo; Falcon = 512/1024 via NTT)

    /// @notice Negacyclic polynomial multiplication a*b in Z_q[x]/(x^n + 1).
    function ringMul(uint256[] calldata a, uint256[] calldata b) public pure returns (uint256[] memory t) {
        require(a.length == N && b.length == N, "len");
        t = new uint256[](N);
        for (uint256 i = 0; i < N; i++) {
            uint256 ai = a[i];
            if (ai == 0) continue;
            for (uint256 j = 0; j < N; j++) {
                uint256 p = mulmod(ai, b[j], Q);
                uint256 k = i + j;
                if (k < N) {
                    t[k] = addmod(t[k], p, Q);
                } else {
                    // x^n = -1  =>  subtract into position k-n
                    t[k - N] = addmod(t[k - N], Q - p, Q);
                }
            }
        }
    }

    /// @notice Squared Euclidean norm of a polynomial with coefficients centered
    ///         into (-q/2, q/2].
    function normSq(uint256[] calldata v) public pure returns (uint256 s) {
        for (uint256 i = 0; i < v.length; i++) {
            uint256 x = v[i] % Q;
            int256 c = x <= Q / 2 ? int256(x) : int256(x) - int256(Q);
            s += uint256(c * c);
        }
    }

    /**
     * @notice Verify the Falcon-family lattice relation + short-vector bound.
     * @param h      public polynomial (n coeffs)
     * @param c      challenge polynomial (n coeffs) — Falcon: HashToPoint(msg)
     * @param s1     signature part 1 (n coeffs)
     * @param s2     signature part 2 (n coeffs)
     * @param bound  ||(s1,s2)||^2 must be <= bound
     */
    function verify(
        uint256[] calldata h,
        uint256[] calldata c,
        uint256[] calldata s1,
        uint256[] calldata s2,
        uint256 bound
    ) external pure returns (bool) {
        require(h.length == N && c.length == N && s1.length == N && s2.length == N, "len");
        // s2 * h
        uint256[] memory t = ringMul(s2, h);
        // s1 must equal c - s2*h  (mod q)
        for (uint256 i = 0; i < N; i++) {
            uint256 expected = addmod(c[i] % Q, Q - t[i], Q); // (c - t) mod q
            if (s1[i] % Q != expected) return false;
        }
        // short-vector bound
        if (normSq(s1) + normSq(s2) > bound) return false;
        return true;
    }
}
