/*
   Scott's AKE Client/Server testbed

   See http://eprint.iacr.org/2002/164

   Compile as 
   cl /O2 /GX /DZZNS=16 ake4mnta.cpp zzn4.cpp zzn2.cpp ecn2.cpp big.cpp zzn.cpp 
   ecn.cpp miracl.lib
   Fastest using COMBA build for 160-bit mod-mul

   The file k4mnt.ecs is required 
   Security is G155/F640 (155-bit group, 640-bit field)

   Speeded up using ideas from 
   "Efficient Computation of Tate Pairing in Projective Coordinate over General Characteristic Fields" 
   by Sanjit Chatterjee1, Palash Sarkar1 and Rana Barua1 

   Modified to prevent sub-group confinement attack

*/

#include <iostream>
#include <fstream>
#include <string.h>
#include "ecn.h"
#include <ctime>
#include "ecn2.h"
#include "zzn4.h"

using namespace std;

Miracl precision(10,0);  

// Using SHA-1 as basic hash algorithm

#define HASH_LEN 20
#define COF 34

#ifdef MR_COUNT_OPS
extern "C"
{
    int fpc,fpa,fpx;
}
#endif

//
// Define one or the other of these
//
// Which is faster depends on the I/M ratio - See imratio.c
// Roughly if I/M ratio > 16 use PROJECTIVE, otherwise use AFFINE
//

//#define AFFINE
 #define PROJECTIVE

//
// Tate Pairing Code
//
// Extract ECn point in internal ZZn format
//

void extract(ECn& A,ZZn& x,ZZn& y)
{ 
    x=(A.get_point())->X;
    y=(A.get_point())->Y;
}

void extract(ECn& A,ZZn& x,ZZn& y,ZZn& z)
{ 
    big t;
    x=(A.get_point())->X;
    y=(A.get_point())->Y;
    t=(A.get_point())->Z;
    if (A.get_status()!=MR_EPOINT_GENERAL) z=1;
    else                                   z=t;
}

ZZn4 line(ECn2& A,ECn2& C,ECn2& B,BOOL Doubling,ZZn2& lam,ZZn2& extra1,ZZn2& extra2,ZZn& Qx,ZZn& Qy)
{ 
    ZZn4 w;
    ZZn2 z3;

#ifdef AFFINE

    ZZn2 x,y;
    A.get(x,y);
    w.set(ZZn2(0,Qy),lam*(tx(x)+Qx)-tx(y));

#endif

#ifdef PROJECTIVE
   
    C.getZ(z3);

    if (!Doubling)
    {
        ZZn2 x2,y2;
        B.get(x2,y2);
        w.set(tx(z3*Qy),lam*(tx(x2)+Qx)-z3*tx(y2));
    }
    else
    {
        ZZn2 x,y,z;
        A.get(x,y,z);
        w.set(tx(z3*extra2*Qy),lam*(extra2*Qx+tx(x))-tx(extra1));
    }
/*
         A.get(x,y,z);
         x=tx(x); y=tx(y);
         t=z; x*=z; z*=z; z*=t;
         w.set(z3*z*ZZn2(0,Qy),lam*(x+z*Qx)-y*z3);
*/
#endif

    return w;
}

ZZn4 g(ECn2& A,ECn2& B,ZZn& Qx,ZZn& Qy)
{
    BOOL Doubling;
    ZZn2  lam,extra1,extra2;
    ECn2 P=A;

    ZZn2 x,y,z;

// Evaluate line from A

    Doubling=A.add(B,lam,extra1,extra2);
    if (A.iszero())   return (ZZn4)1; 

    return line(P,A,B,Doubling,lam,extra1,extra2,Qx,Qy);
}

//
// Ate Pairing - note denominator elimination has been applied
//
// P is a point of order q. Q(x,y) is a point of order m.q. 
// Note that P is a point on the curve over Fp, Q(x,y) a point on the 
// extension field Fp^2
//

BOOL ate(ECn2& P,ECn Q,Big& t1,ZZn2 &Fr,Big cof,ZZn2& r)
{ 
    int i,j,n,nb;
    ECn2 A;
    ZZn4 w,res;
    ZZn Qx,Qy;

#ifdef MR_COUNT_OPS
fpc=fpa=fpx=0;
#endif  

    P.norm();
    normalise(Q);
    extract(Q,Qx,Qy);

    Qx+=Qx;
    Qy+=Qy;

    res=1;  

/* Miller loop - Left to right method  */
    A=P;
    nb=bits(t1);
    for (i=nb-2;i>=0;i--)
    {
        res*=res;           
        res*=g(A,A,Qx,Qy); 
        if (bit(t1,i))
            res*=g(A,P,Qx,Qy);
    }

#ifdef MR_COUNT_OPS
printf("After Miller fpc= %d fpa= %d fpx= %d\n",fpc,fpa,fpx);
fpa=fpc=fpx=0;
#endif
    
    w=res;
    w.powq(Fr); w.powq(Fr);  // ^(p^2-1)
    res=w/res;

    res.mark_as_unitary();

    res*=res; w=res; res*=res; res*=res; res*=res; res*=res; res*=w;  // res=powu(res,34);


    w=res;
    res.powq(Fr);
    res*=powu(w,cof);

#ifdef MR_COUNT_OPS
printf("After Final exp. fpc= %d fpa= %d fpx= %d\n",fpc,fpa,fpx);
fpa=fpc=fpx=0;
#endif

    r=real(res);

    if (r.isunity()) return FALSE;
    return TRUE;            
}

//
// Hash functions
// 

Big H1(char *string)
{ // Hash a zero-terminated string to a number < modulus
    Big h,p;
    char s[HASH_LEN];
    int i,j; 
    sha sh;

    shs_init(&sh);

    for (i=0;;i++)
    {
        if (string[i]==0) break;
        shs_process(&sh,string[i]);
    }
    shs_hash(&sh,s);
    p=get_modulus();
    h=1; j=0; i=1;
    forever
    {
        h*=256; 
        if (j==HASH_LEN)  {h+=i++; j=0;}
        else         h+=s[j++];
        if (h>=p) break;
    }
    h%=p;
    return h;
}

Big H2(ZZn2 x)
{ // Hash an Fp2 to a big number
    sha sh;
    Big a,u,v;
    char s[HASH_LEN];
    int m;

    shs_init(&sh);
    x.get(u,v);

    a=u;
    while (a>0)
    {
        m=a%256;
        shs_process(&sh,m);
        a/=256;
    }
    a=v;
    while (a>0)
    {
        m=a%256;
        shs_process(&sh,m);
        a/=256;
    }
    shs_hash(&sh,s);
    a=from_binary(HASH_LEN,s);
    return a;
}

// Hash and map a Server Identity to a curve point E(Fp2)

ECn2 hash2(char *ID,Big cof)
{
    ECn2 T;
    ZZn2 x;
    Big x0,y0=0;

    x0=H1(ID);
    do
    {
        x.set(x0,y0);
        x0+=1;
    }
    while (!is_on_curve(x));
    T.set(x);

    T*=cof;
// cout << "T= " << T << endl;

    return T;
}     

// Hash and map a Client Identity to a curve point E(Fp)

ECn hash_and_map(char *ID,Big cof)
{
    ECn Q;
    Big x0=H1(ID);

    while (!is_on_curve(x0)) x0+=1;
    Q.set(x0);  // Make sure its on E(F_p)

    Q*=cof;
    return Q;
}

int main()
{
    ifstream common("k4mnt.ecs");      // elliptic curve parameters
    miracl* mip=&precision;
    ECn Alice,Bob,sA,sB;
    ECn2 Server,sS;
    ZZn2 res,sp,ap,bp,Fr;
    Big a,b,s,ss,p,q,r,B,delta,fr,t,cof2,t1;
                                       
    int bits,A;
    time_t seed;

    cout << "Started" << endl;
    common >> bits;
    mip->IOBASE=16;
    common >> p;
    common >> A;
    common >> B;
    common >> q >> delta;
    common >> fr;

    cout << "Initialised... " << endl;
 
    t=p+1-COF*q;
    cof2=((p*p+1)/q+(t*t-2*p)/q)/COF;  // should be stored as small constant...
    t1=p-COF*q; 

    time(&seed);
    irand((long)seed);

#ifdef AFFINE
    ecurve(A,B,p,MR_AFFINE);
#endif
#ifdef PROJECTIVE
    ecurve(A,B,p,MR_PROJECTIVE);
#endif

 //   Fr=get_frobenius_constant();

    Fr=(ZZn2)fr; 
    mip->IOBASE=16;
    mip->TWIST=TRUE;   // map Server to point on twisted curve E(Fp2)

// hash Identities to curve point

    ss=rand(q);    // TA's super-secret 

    cout << "Mapping Server ID to point" << endl;
    Server=hash2("Server",cof2);

    cout << "Mapping Alice & Bob ID's to points" << endl;
    Alice=hash_and_map("Alice",COF);

    Bob=  hash_and_map("Robert",COF);
    cout << "Alice, Bob and the Server visit Trusted Authority" << endl; 

    sS=ss*Server; 
    sA=ss*Alice; 
    sB=ss*Bob; 

    cout << "Alice and Server Key Exchange" << endl;

    a=rand(q);   // Alice's random number
    s=rand(q);   // Server's random number

    if (!ate(Server,sA,t1,Fr,delta,res)) cout << "Trouble" << endl;

    if (powl(res,q)!=(ZZn2)1)
    {
        cout << "Wrong group order - aborting" << endl;
        exit(0);
    }

    ap=powl(res,a);

    if (!ate(sS,Alice,t1,Fr,delta,res)) cout << "Trouble" << endl;
    if (powl(res,q)!=(ZZn2)1)
    {
        cout << "Wrong group order - aborting" << endl;
        exit(0);
    }
    sp=powl(res,s);

    cout << "Alice  Key= " << H2(powl(sp,a)) << endl;
    cout << "Server Key= " << H2(powl(ap,s)) << endl;

    cout << "Bob and Server Key Exchange" << endl;

    b=rand(q);   // Bob's random number
    s=rand(q);   // Server's random number

    if (!ate(Server,sB,t1,Fr,delta,res)) cout << "Trouble" << endl;
    if (powl(res,q)!=(ZZn2)1)
    {
        cout << "Wrong group order - aborting" << endl;
        exit(0);
    }
    bp=powl(res,b);

    if (!ate(sS,Bob,t1,Fr,delta,res)) cout << "Trouble" << endl;
    if (powl(res,q)!=(ZZn2)1)
    {
        cout << "Wrong group order - aborting" << endl;
        exit(0);
    }
    sp=powl(res,s);

    cout << "Bob's  Key= " << H2(powl(sp,b)) << endl;
    cout << "Server Key= " << H2(powl(bp,s)) << endl;

    return 0;
}
