/*
 * bn256-20080525/ate_optate.c 
 * Peter Schwabe
 * Public domain
*/

#include <stdio.h>
#include <gmp.h>
#include <cpucycles.h>

#include "fp2e.h"
#include "fp6.h"
#include "fp6e.h"
#include "fp12e.h"
#include "curvepoint_fp.h"
#include "twistpoint_fp2.h"
#include "init.h"
#include "points.h"
#include "final_expo.h"
#include "ate_optate.h"
#include "parameters.h"

static void linefunction_add_ate(
        fp12e_t rop1, 
        twistpoint_fp2_t rop2, 
        const twistpoint_fp2_t op1, 
        const twistpoint_fp2_t op2, 
        const curvepoint_fp_t op3)
{
    fp2e_t tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6; // Temporary variables needed for intermediary results
    fp6e_t tfp6e1, tfp6e2;


    fp2e_square(tmp0, op1->m_z);
    fp2e_mul(tmp1, tmp0, op1->m_z);
    fp2e_mul(tmp0, op2->m_x, tmp0);
    fp2e_mul(tmp1, op2->m_y, tmp1);
    fp2e_sub(tmp0, tmp0, op1->m_x);
    fp2e_sub(tmp1, tmp1, op1->m_y);
    fp2e_square(tmp2, tmp0);
    fp2e_square(tmp3, tmp1);
    fp2e_mul(rop2->m_z, op1->m_z, tmp0);
    fp2e_mul(tmp4, op1->m_x, tmp2);
    fp2e_double(tmp4, tmp4);
    fp2e_mul(tmp6, tmp0, tmp2);
    fp2e_mul(tmp5, tmp1, op2->m_x);
    fp2e_mul(tmp0, rop2->m_z, op2->m_y);
    fp2e_sub(tmp5, tmp5, tmp0);
    fp2e_mul(tmp0, op1->m_y, tmp6);
    fp2e_mul(rop2->m_y, op1->m_x, tmp2);
    fp2e_sub(rop2->m_x, tmp3, tmp6);
    fp2e_sub(rop2->m_x, rop2->m_x, tmp4);
    fp2e_sub(rop2->m_y, rop2->m_y, rop2->m_x);
    fp2e_mul(rop2->m_y, rop2->m_y, tmp1);
    fp2e_sub(rop2->m_y, rop2->m_y, tmp0);
    fp2e_neg(tmp1, tmp1);
    fp2e_mul_fpe(tmp1, tmp1, op3->m_x);
    fp2e_mul_fpe(tmp0, rop2->m_z, op3->m_y);

    fp2e_setzero(tmp2);

    fp6e_set_fp2e(tfp6e1, tmp2, tmp5, tmp1);
    fp6e_set_fp2e(tfp6e2, tmp2, tmp2, tmp0);

    fp12e_set_fp6e(rop1, tfp6e1, tfp6e2);
}

static void linefunction_double_ate(fp12e_t rop1, twistpoint_fp2_t rop2, const twistpoint_fp2_t op1, const curvepoint_fp_t op3)
{
    fp2e_t tmp0, tmp1, tmp2, tmp3, tmp4; // Temporary variables needed for intermediary results
    fp6e_t tfp6e1, tfp6e2;

    fp2e_square(tmp0, op1->m_x);
    fp2e_add(tmp1, tmp0, tmp0);
    fp2e_add(tmp1, tmp1, tmp0); // tmp1 = 3x1^2
    fp2e_square(tmp3, op1->m_y); // tmp3 = y1^2
    fp2e_mul(tmp2, op1->m_x, tmp3);
    fp2e_double(tmp2, tmp2);
    fp2e_double(tmp2, tmp2); // tmp2 = 4*x1*y1^2
    fp2e_mul(tmp4, tmp1, op1->m_x); // tmp4 = 3x1^3 
    fp2e_sub(tmp4, tmp4, tmp3);
    fp2e_sub(tmp4, tmp4, tmp3); // tmp4 = 3x1^2 - 2y1^2
    fp2e_square(tmp0, tmp3);
    fp2e_double(tmp0, tmp0);
    fp2e_double(tmp0, tmp0);
    fp2e_double(tmp0, tmp0); // tmp0 = 8y1^4
    fp2e_square(rop2->m_x, tmp1);
    fp2e_sub(rop2->m_x, rop2->m_x, tmp2);
    fp2e_sub(rop2->m_x, rop2->m_x, tmp2); // x3 = 9x1^4 - 8x1y1^2
    fp2e_square(tmp3, op1->m_z); // tmp3 = z1^2
    fp2e_mul(rop2->m_z, op1->m_z, op1->m_y);
    fp2e_double(rop2->m_z, rop2->m_z); // z3 = 2z1^2y1
    fp2e_sub(rop2->m_y, tmp2, rop2->m_x); // y3 = 4x1y1^2 - x3
    fp2e_mul(rop2->m_y, tmp1, rop2->m_y); // y3 = 8y1^4*(4x1y1^2 - x3)
    fp2e_sub(rop2->m_y, rop2->m_y, tmp0); // y2 = 8y1^4*(4x1y1^2 - x3 - 1)
    fp2e_mul(tmp0, tmp3, rop2->m_z); // t0 = 2z1^4y1
    fp2e_neg(tmp1, tmp1);
    fp2e_mul(tmp3, tmp3, tmp1);
    fp2e_mul_fpe(tmp3, tmp3, op3->m_x);
    fp2e_mul_fpe(tmp0, tmp0, op3->m_y);

    fp2e_setzero(tmp1);

    fp6e_set_fp2e(tfp6e1, tmp1, tmp4, tmp3);
    fp6e_set_fp2e(tfp6e2, tmp1, tmp1, tmp0);

    fp12e_set_fp6e(rop1, tfp6e1, tfp6e2);
}

void optate(fp12e_t rop, const twistpoint_fp2_t op1, const curvepoint_fp_t op2)
{
    // op1 and op2 are assumed to be in affine coordinates!
    fpe_t zeta2;
    fp2e_t z2p, z3p;
    fpe_set_str(zeta2, BN_ZETA2);
    fp2e_set_str(z2p, BN_Z2P);
    fp2e_set_str(z3p, BN_Z3P);
    twistpoint_fp2_t q1, q2, q3;
    fp12e_setone(rop);

    fp12e_t dummy;
    fp2e_t tfp2e1, tfp2e2;

    mpz_t _6uplus2;
    mpz_init_set(_6uplus2, x);
    mpz_mul_ui(_6uplus2, _6uplus2, 6);
    mpz_add_ui(_6uplus2, _6uplus2, 2);

    twistpoint_fp2_t r, t;
    twistpoint_fp2_set(r, op1);

    unsigned long int i;

    for(i = mpz_sizeinbase(_6uplus2, 2) - 1; i > 0; i--)
    {
        linefunction_double_ate(dummy, r, r, op2);
        fp12e_square(rop, rop);
        fp12e_mul(rop, rop, dummy);

        if (mpz_tstbit(_6uplus2, i - 1)) 
        {
            linefunction_add_ate(dummy, r, r, op1, op2);
            fp12e_mul(rop, rop, dummy);
        }
    }
    // Needed because linfunction_add needs op2 in affine coordinates
    twistpoint_fp2_makeaffine(r);

    fp2e_mul_fpe(tfp2e1, op1->m_x, zeta2);
    twistpoint_fp2_affineset_fp2e(q2, tfp2e1, op1->m_y); 

    fp2e_set(tfp2e1, op1->m_x);
    fpe_neg(tfp2e1->m_a, tfp2e1->m_a);
    fp2e_mul(tfp2e1, tfp2e1, z2p);
    fp2e_set(tfp2e2, op1->m_y);
    fpe_neg(tfp2e2->m_a, tfp2e2->m_a);
    fp2e_mul(tfp2e2, tfp2e2, z3p);
    twistpoint_fp2_affineset_fp2e(q1, tfp2e1, tfp2e2);

    fp2e_mul_fpe(tfp2e1, tfp2e1, zeta2);
    fp2e_neg(tfp2e2, tfp2e2);
    twistpoint_fp2_affineset_fp2e(q3, tfp2e1, tfp2e2);

    linefunction_add_ate(dummy, t, q3, q2, op2);
    fp12e_mul(rop, rop, dummy);
    linefunction_add_ate(dummy, t, t, q1, op2);
    fp12e_mul(rop, rop, dummy);
    linefunction_add_ate(dummy, t, t, r, op2);
    fp12e_mul(rop, rop, dummy);

    final_expo(rop);
}

void ate(fp12e_t rop, const twistpoint_fp2_t op1, const curvepoint_fp_t op2)
{
    fp12e_setone(rop);

    fp12e_t dummy;

    mpz_t tminus1;
    mpz_init_set(tminus1, trace);
    mpz_sub_ui(tminus1, tminus1, 1);

    twistpoint_fp2_t r;
    twistpoint_fp2_set(r, op1);

    unsigned long int i;

    for(i = mpz_sizeinbase(tminus1, 2) - 1; i > 0; i--)
    {
        linefunction_double_ate(dummy, r, r, op2);
        fp12e_square(rop, rop);
        fp12e_mul(rop, rop, dummy);

        if (mpz_tstbit(tminus1, i - 1)) 
        {
            linefunction_add_ate(dummy, r, r, op1, op2);
            fp12e_mul(rop, rop, dummy);
        }
    }
    final_expo(rop);
}
