package org.bouncycastle.pqc.crypto.sqisign;


/**
 * Degree-2 and degree-4 isogeny construction ({@code xisog_*}) and evaluation
 * ({@code xeval_*}). Java port of {@code src/ec/ref/lvlx/xisog.c} and
 * {@code xeval.c}.
 *
 * <p>The ref implementation's {@code xisog_4_singular} and
 * {@code xeval_4_singular} are declared in {@code isog.h} but not implemented
 * in the ref tree; they are omitted here too.</p>
 */
final class EcIsog
{
    private EcIsog()
    {
    }

    /**
     * {@code xisog_2}: build a degree-2 isogeny with kernel generated by
     * {@code P} (assumed not (0, 0)). Writes the codomain coefficient
     * {@code B = (A24 form)} and the kernel-point structure {@code kps}.
     */
    public static void xisog_2(GfField field, EcKps2 kps, EcPoint B, EcPoint P)
    {
        field.fp2Sqr(B.x, P.x);
        field.fp2Sqr(B.z, P.z);
        field.fp2Sub(B.x, B.z, B.x);
        field.fp2Add(kps.K.x, P.x, P.z);
        field.fp2Sub(kps.K.z, P.x, P.z);
    }

    /**
     * {@code xisog_2_singular}: degree-2 isogeny when the kernel is the
     * special (0, 0) point. Used only on the signing side per the C ref.
     */
    public static void xisog_2_singular(GfField field, EcKps2 kps, EcPoint B24, EcPoint A24In)
    {
        EcPoint A24 = A24In.copy();
        Fp2 t0 = Fp2.zero();
        Fp2 four = Fp2.zero();
        Fp2.setSmall(four, 4);

        field.fp2Add(t0, A24.x, A24.x);
        field.fp2Sub(t0, t0, A24.z);
        field.fp2Add(t0, t0, t0);
        field.fp2Inv(A24.z);
        field.fp2Mul(t0, t0, A24.z);
        Fp2.copy(kps.K.x, t0);
        field.fp2Add(B24.x, t0, t0);
        field.fp2Sqr(t0, t0);
        field.fp2Sub(t0, t0, four);
        field.fp2Sqrt(t0);
        field.fp2Neg(kps.K.z, t0);
        field.fp2Add(B24.z, t0, t0);
        field.fp2Add(B24.x, B24.x, B24.z);
        field.fp2Add(B24.z, B24.z, B24.z);
    }

    /**
     * {@code xisog_4}: build a degree-4 isogeny with kernel generated by P
     * such that [2]P ≠ (0, 0).
     */
    public static void xisog_4(GfField field, EcKps4 kps, EcPoint B, EcPoint P)
    {
        EcPoint[] K = kps.K;

        field.fp2Sqr(K[0].x, P.x);
        field.fp2Sqr(K[0].z, P.z);
        field.fp2Add(K[1].x, K[0].z, K[0].x);
        field.fp2Sub(K[1].z, K[0].z, K[0].x);
        field.fp2Mul(B.x, K[1].x, K[1].z);
        field.fp2Sqr(B.z, K[0].z);

        // Constants for xeval_4
        field.fp2Add(K[2].x, P.x, P.z);
        field.fp2Sub(K[1].x, P.x, P.z);
        field.fp2Add(K[0].x, K[0].z, K[0].z);
        field.fp2Add(K[0].x, K[0].x, K[0].x);
    }

    /**
     * {@code xeval_2}: evaluate a degree-2 isogeny on each of the {@code lenQ}
     * input points, writing the images to {@code R} (alias-safe with Q).
     */
    public static void xeval_2(GfField field, EcPoint[] R, EcPoint[] Q, int lenQ, EcKps2 kps)
    {
        Fp2 t0 = Fp2.zero(), t1 = Fp2.zero(), t2 = Fp2.zero();
        for (int j = 0; j < lenQ; j++)
        {
            field.fp2Add(t0, Q[j].x, Q[j].z);
            field.fp2Sub(t1, Q[j].x, Q[j].z);
            field.fp2Mul(t2, kps.K.x, t1);
            field.fp2Mul(t1, kps.K.z, t0);
            field.fp2Add(t0, t2, t1);
            field.fp2Sub(t1, t2, t1);
            field.fp2Mul(R[j].x, Q[j].x, t0);
            field.fp2Mul(R[j].z, Q[j].z, t1);
        }
    }

    /** {@code xeval_2_singular}: evaluator for the (0, 0)-kernel variant. */
    public static void xeval_2_singular(GfField field, EcPoint[] R, EcPoint[] Q, int lenQ, EcKps2 kps)
    {
        Fp2 t0 = Fp2.zero(), t1 = Fp2.zero();
        for (int i = 0; i < lenQ; i++)
        {
            field.fp2Mul(t0, Q[i].x, Q[i].z);
            field.fp2Mul(t1, kps.K.x, Q[i].z);
            field.fp2Add(t1, t1, Q[i].x);
            field.fp2Mul(t1, t1, Q[i].x);
            field.fp2Sqr(R[i].x, Q[i].z);
            field.fp2Add(R[i].x, R[i].x, t1);
            field.fp2Mul(R[i].z, t0, kps.K.z);
        }
    }

    /** {@code xeval_4}: degree-4 isogeny evaluation. */
    public static void xeval_4(GfField field, EcPoint[] R, EcPoint[] Q, int lenQ, EcKps4 kps)
    {
        EcPoint[] K = kps.K;
        Fp2 t0 = Fp2.zero(), t1 = Fp2.zero();
        for (int i = 0; i < lenQ; i++)
        {
            field.fp2Add(t0, Q[i].x, Q[i].z);
            field.fp2Sub(t1, Q[i].x, Q[i].z);
            field.fp2Mul(R[i].x, t0, K[1].x);
            field.fp2Mul(R[i].z, t1, K[2].x);
            field.fp2Mul(t0, t0, t1);
            field.fp2Mul(t0, t0, K[0].x);
            field.fp2Add(t1, R[i].x, R[i].z);
            field.fp2Sub(R[i].z, R[i].x, R[i].z);
            field.fp2Sqr(t1, t1);
            field.fp2Sqr(R[i].z, R[i].z);
            field.fp2Add(R[i].x, t0, t1);
            field.fp2Sub(t0, t0, R[i].z);
            field.fp2Mul(R[i].x, R[i].x, t1);
            field.fp2Mul(R[i].z, R[i].z, t0);
        }
    }
}
