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

#include "fp.h"
#include "fp2e.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 "eta_tate.h"

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

static void linefunction_add_eta(fp12e_t rop1, curvepoint_fp_t rop2, const curvepoint_fp_t op1, const curvepoint_fp_t op2, const twistpoint_fp2_t op3)
{
    // op2 is assumed to be in affine coordinates!
    fpe_t tmp0, tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, tmp8, tmp9, tmp10; // Temporary variables needed for intermediary results
    fp2e_t tfp2e1, tfp2e2, tfp2e3, tfp2e4;
    fp6e_t tfp6e1, tfp6e2;

    fpe_square(tmp10, op1->m_z);            // tmp10 = z1^2
    fpe_mul(tmp7, tmp10, op1->m_z);         // tmp7 = z1^3
    fpe_mul(tmp0, op2->m_x, tmp10);         // tmp0 = x2*z1^2
    fpe_mul(tmp1, op2->m_y, tmp7);          // tmp1 = y2*z1^3
    fpe_sub(tmp0, tmp0, op1->m_x);          // tmp0 = x2*z1^2 - x1
    fpe_sub(tmp1, tmp1, op1->m_y);          // tmp1 = y2*z1^3 - y1
    fpe_mul(tmp8, tmp1, op1->m_x);          // tmp8 = x1 * (y2*z1^3 - y1) // Needed for linefunction evaluation
    fpe_square(tmp2, tmp0);                 // tmp2 = (x2*z1^2 - x1)^2
    fpe_mul(tmp6, tmp0, tmp2);              // tmp6 = (x2*z1^2 - x1)^3
    fpe_mul(tmp4, op1->m_x, tmp2);          // tmp4 = x1*(x2*z1^2 - x1)^2
    fpe_square(tmp3, tmp1);                 // tmp3 = (y2*z1^3 - y1)^2
    fpe_mul(rop2->m_z, op1->m_z, tmp0);     // z3   = z1(x2*z1^2 - x1)
    fpe_mul(tmp9, tmp0, op1->m_y);          // tmp9 = y1(x2*z1^2 - x1) // Needed for linefunction evaluation
    fpe_double(tmp5, tmp4);                 // tmp5 = 2x1*(x2*z1^2 - x1)^2
    fpe_sub(rop2->m_x, tmp3, tmp5);         // x3   = (y2*z1^3 - y1)^2 - 2x1*(x2*z1^2 - x1)^2
    fpe_sub(rop2->m_x, rop2->m_x, tmp6);    // x3   = (y2*z1^3 - y1)^2 - 2x1*(x2*z1^2 - x1)^2 - (x2*z1^2 - x1)^3 = (y2*z1^3 - y1)^2 * (x1 + x2*z1^2)
    fpe_mul(tmp3, op1->m_y, tmp6);          // tmp3 = y1 * (x2*z1^2 - x1)^3
    fpe_sub(rop2->m_y, tmp4, rop2->m_x);    // y3   = (x1*(x2*z1^2 - x1)^2 - x3)
    fpe_mul(rop2->m_y, rop2->m_y, tmp1);    // y3   = (y2*z1^3 - y1)(x1*(x2*z1^2 - x1)^2 - x3)
    fpe_sub(rop2->m_y, rop2->m_y, tmp3);    // y3   = (y2*z1^3 - y1)(x1*(x2*z1^2 - x1)^2 - x3) - y1 * (x2*z1^2 - x1)^3
    
    fpe_mul(tmp5, tmp0, tmp7);
    fpe_neg(tmp5, tmp5);
    fp2e_mul_fpe(tfp2e3, op3->m_y, tmp5);   // tfp2e3 = - y3z1^3 * (x2z1^2 - x1)

    fpe_mul(tmp3, tmp1, tmp10);
    fp2e_mul_fpe(tfp2e2, op3->m_x, tmp3);   // tfp2e2 = x3z1^2 * (y2z1^3 - y1)
    
    fpe_sub(tmp9, tmp9, tmp8);
    fp2e_set_fpe(tfp2e4, tmp9);             // tfp2e4 = y1(x2z1^2 - x1) - x1(y2z1^3 - y1)
    
	fp2e_setzero(tfp2e1);

	fp6e_set_fp2e(tfp6e1, tfp2e1, tfp2e3, tfp2e1);
	fp6e_set_fp2e(tfp6e2, tfp2e1, tfp2e2, tfp2e4);

    fp12e_set_fp6e(rop1, tfp6e1, tfp6e2);
}

static void linefunction_double_eta(fp12e_t rop1, curvepoint_fp_t rop2, const curvepoint_fp_t op1, const twistpoint_fp2_t op3)
{
    
	fpe_t tmp0, tmp1, tmp2, tmp3, tmp4;	// Temporary variables
    fp2e_t tfp2e1, tfp2e2, tfp2e3, tfp2e4;
    fp6e_t tfp6e1, tfp6e2;

    fpe_square(tmp0, op1->m_x);
    fpe_add(tmp1, tmp0, tmp0);
    fpe_add(tmp1, tmp1, tmp0); // tmp1 = 3x1^2
    fpe_square(tmp3, op1->m_y); // tmp3 = y1^2
    fpe_mul(tmp2, op1->m_x, tmp3);
    fpe_double(tmp2, tmp2);
    fpe_double(tmp2, tmp2); // tmp2 = 4*x1*y1^2
    fpe_mul(tmp4, tmp1, op1->m_x); // tmp4 = 3x1^3 
    fpe_sub(tmp4, tmp4, tmp3);
    fpe_sub(tmp4, tmp3, tmp4); // tmp4 = 3x1^3 - 2y1^2
    fpe_square(tmp0, tmp3);
    fpe_double(tmp0, tmp0);
    fpe_double(tmp0, tmp0);
    fpe_double(tmp0, tmp0); // tmp0 = 8y1^4
    fpe_square(rop2->m_x, tmp1);
    fpe_sub(rop2->m_x, rop2->m_x, tmp2);
    fpe_sub(rop2->m_x, rop2->m_x, tmp2); // x3 = 9x1^4 - 8x1y1^2
    fpe_square(tmp3, op1->m_z); // tmp3 = z1^2
    fpe_mul(rop2->m_z, op1->m_z, op1->m_y);
    fpe_double(rop2->m_z, rop2->m_z); // z3 = 2z1^2y1
    fpe_sub(rop2->m_y, tmp2, rop2->m_x); // y3 = 4x1y1^2 - x3
    fpe_mul(rop2->m_y, tmp1, rop2->m_y); // y3 = 8y1^4*(4x1y1^2 - x3)
    fpe_sub(rop2->m_y, rop2->m_y, tmp0); // y2 = 8y1^4*(4x1y1^2 - x3 - 1)
    fpe_mul(tmp0, tmp3, rop2->m_z); // tmp0 = 2z1^4y1
    fpe_neg(tmp0, tmp0);
    fpe_mul(tmp3, tmp3, tmp1); // tmp3 = 3x1^2z1^2

    fp2e_mul_fpe(tfp2e2, op3->m_x, tmp3); // tfp2e2 = -3x1^2z1^2x2
    fp2e_mul_fpe(tfp2e3, op3->m_y, tmp0);
    fp2e_set_fpe(tfp2e4, tmp4);
        
	fp2e_setzero(tfp2e1);

	fp6e_set_fp2e(tfp6e1, tfp2e1, tfp2e3, tfp2e1);
	fp6e_set_fp2e(tfp6e2, tfp2e1, tfp2e2, tfp2e4);

    fp12e_set_fp6e(rop1, tfp6e1, tfp6e2);
    
}


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

	fp12e_t dummy;

	curvepoint_fp_t r;
    curvepoint_fp_set(r, op1);

    size_t i;
       
    for(i = mpz_sizeinbase(looplength_eta, 2) - 1; i > 0; i--)
       {
       linefunction_double_eta(dummy, r, r, op2);

		fp12e_square(rop, rop);
		fp12e_mul(rop, rop, dummy);

		if (mpz_tstbit(looplength_eta, i - 1)) 
		{
			linefunction_add_eta(dummy, r, r, op1, op2);
			fp12e_mul(rop, rop, dummy);
		}
	}
    
    curvepoint_fp_makeaffine(r);
    curvepoint_fp_print(stdout, r);
    printf("\n");
	
	final_expo(rop);
}

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

	fp12e_t dummy;

	curvepoint_fp_t r;
	curvepoint_fp_set(r, op1);

	size_t i;

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

		if (mpz_tstbit(n, i - 1)) 
		{
			linefunction_add_eta(dummy, r, r, op1, op2);
			fp12e_mul(rop, rop, dummy);
		}
	}

	final_expo(rop);

}

