/*************************************************************
**         Europäisches Institut für Systemsicherheit        *
**   Proktikum "Kryptoanalyse"                               *
**                                                           *
** Versuch 4: Kocher-Timing-Attack                           *
**                                                           *
**************************************************************
**
** attack.c: Timing-Attacke Rahmenprogramm
**/
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#include <gmp.h>
#include "texp.h"
#include "correlation.h"

#include "daemon.h"

#define EXPBITS 128 /* Anzahl der Bits im Exponent */
#define NUMSAMPLES 625
#define DEBUG_STATUS true
// #define DEBUG_VERBOSE true
// #define DEBUG_VERIFY true

mpz_t n;       /* Modulus */
mpz_t y_trial; /* Hier soll der geheime Exponent y berechnet werden */
mpz_t y_ok;    /* Tatsaechlicher geheimer Exponent (vom Daemon) */
int ok;        /* war der Exponent richtig? */

// struct sample
// {
//     mpz_t x;
//     mpz_t z;
//     unsigned int zixi[EXPBITS / 2];
//     unsigned int time;
// };

//struct sample samples[NUMSAMPLES];
unsigned long getHammingWeight(void)
{
    mpz_t one, z;
    mpz_init_set_ui(one, 1);
    mpz_init(z);
    unsigned long timeExponentiate1 = exp_daemon(z, one);
    unsigned long weight = calculateHammingWeight(n, timeExponentiate1);
    mpz_clear(one);
    mpz_clear(z);

#ifdef DEBUG_VERBOSE
    printf("Hamming weight: %lu\n", weight);
#endif

    return weight;
}

void generateSamples(mpz_t *X, unsigned long *T)
{
    gmp_randstate_t randomness;
    gmp_randinit_default(randomness);

    time_t now = time(0);
    gmp_randseed_ui(randomness, now);

    mpz_t Z;
    mpz_init(Z);

    for (int i = 0; i < NUMSAMPLES; i++) //generate samples and calculate tm
    {
        mpz_init(X[i]);
        mpz_urandomm(X[i], randomness, n); //send a random query

        T[i] = exp_daemon(Z, X[i]); //get the time it took to encrypt

#ifdef DEBUG_STATUS
        if (i % 50 == 49)
        {
            printf("collected sample %i of %i\n", i + 1, NUMSAMPLES);
        }
#endif
    }

    mpz_clear(Z);
    gmp_randclear(randomness);
}

void verifyData(mpz_t y, const mpz_t *X, const unsigned long *T, const unsigned long *TM)
{
    mpz_t xi, zi, z_check;
    mpz_init(xi);
    mpz_init(zi);
    mpz_init(z_check);

    unsigned long T_check, TM_check, TSq_check;

    unsigned long T_requery;

    for (int i = 0; i < NUMSAMPLES; i++)
    {
        gmp_printf("message #%i: %Zd \ncipher: %Zd \n", i, X[i], zi);

        T_check = 0;
        TM_check = 0;
        TSq_check = 0;

        mpz_set(xi, X[i]);
        mpz_set_ui(zi, 1);

        T_requery = LITModExp(z_check, xi, y, n);

        for (int bit = 0; bit < EXPBITS; bit++)
        {
            if (mpz_tstbit(y, bit))
            {
                T_check += LITTimeModMult(zi, xi, n); //IMPORTANT: these operands must be in the same order as in texp.c, first Z, then X
                TM_check += LITTimeModMult(zi, xi, n); //IMPORTANT: these operands must be in the same order as in texp.c, first Z, then X
                gmp_printf("bit %i: time to multiply %Zd and %Zd is %lu\n", bit, zi, xi, LITTimeModMult(zi, xi, n));
                mpz_mul(zi, zi, xi);
                mpz_mod(zi, zi, n);
            }
            T_check += LITTimeModSquare(xi, n);
            TSq_check += LITTimeModSquare(xi, n);

            mpz_powm_ui(xi, xi, 2, n);
        }

        
        printf("original total time: %lu \noriginal multiplication time: %lu\n", T[i], TM[i]);
        printf("recalculated total time: %lu \nrecalculated multiplication time: %lu\n", T_check, TM_check);
        printf("recalculated squaring time: %lu \nchecksum of 3 terms: %lu\n", TSq_check, TM_check + TSq_check - T_check);
        gmp_printf("requeried library function: \ncipher: %Zd \ntotal time: %lu\n", z_check, T_requery);
        printf("\n\n");
    }

    mpz_clear(xi);
    mpz_clear(zi);
}

int main(void)
{
    connect_daemon(n); /* Mit dem Daemonen verbinden und den Modulus
                        * abholen */

    /* XXX Aufgabe: mit exp_daemon () Samples generieren und y_trial berechnen */

    //  Key observation: upper 64 bit are always 10000...
    //  lower 64 bit have 24 randomly distributed 1s

#ifdef DEBUG_VERBOSE
    gmp_printf("Modulus is %Zd\n", n);
#endif

    mpz_init(y_ok);
    mpz_init(y_trial);

#ifdef DEBUG_STATUS
    printf("Calculating hamming weight...\n");
#endif

    unsigned long weight = getHammingWeight();

#ifdef DEBUG_STATUS
    printf("Finished calculating hamming weight.\n\n");
#endif

#ifdef DEBUG_STATUS
    printf("Generating %i samples...\n", NUMSAMPLES);
#endif

    mpz_t *X;
    X = malloc(NUMSAMPLES * sizeof(mpz_t));

    unsigned long *T;
    T = malloc(NUMSAMPLES * sizeof(unsigned long));

    generateSamples(X, T);

#ifdef DEBUG_STATUS
    printf("Finished generating %i samples.\n\n", NUMSAMPLES);
#endif

#ifdef DEBUG_STATUS
    printf("Calculating multiplication times...\n");
#endif

    unsigned long *TM;
    TM = malloc(NUMSAMPLES * sizeof(unsigned long));

    calculateMultiplicationTimes(TM, X, T, n, NUMSAMPLES);

#ifdef DEBUG_STATUS
    printf("Finished calculating multiplication times.\n\n");
#endif

    mpz_t y;
    mpz_init(y);

    timingAttackFast(y, X, n, TM, weight, NUMSAMPLES);
    timingAttackCorrelation(y, X, n, TM, weight, NUMSAMPLES);


    // unsigned int w = NMult; //amount of remaining bits to be set to y_trial
    // w--;                    //first bit has been set @pos128

    // mpz_t zero; //we need some constants
    // mpz_init_set_ui(zero, 0);
    // mpz_t one;
    // mpz_init_set_ui(one, 1);

    // mpz_t x1;
    // mpz_init_set_ui(x1, 1);
    // unsigned long time1Mult = LITTimeModMult(x1, x1, n);
    // printf("time 1 * 1: %lu\ttime 1mult: %u\n", LITTimeModMult(one, one, n), time1Mult);
    // mpz_t t1;
    // mpz_init(t1);
    // mpz_t t0;
    // mpz_init(t0);

    // mpz_t t1sum;
    // mpz_init(t1sum);
    // mpz_t t0sum;
    // mpz_init(t0sum);
    // mpz_t adder;
    // mpz_init_set_ui(adder, 1);
    // for (int exp = 0; exp < EXPBITS / 2; exp++)
    // {
    //     //int exp = 0;
    //     for (int i = 0; i < NUMSAMPLES; i++) //calculate multiplication timing for zi * xi
    //     {
    //         if (mpz_tstbit(samples[i].z, exp))
    //         {
    //             if (mpz_tstbit(samples[i].x, exp))
    //                 samples[i].zixi[exp] = LITTimeModMult(one, one, n);
    //             else
    //                 samples[i].zixi[exp] = LITTimeModMult(one, zero, n);
    //         }
    //         else
    //         {
    //             if (mpz_tstbit(samples[i].x, exp))
    //                 samples[i].zixi[exp] = LITTimeModMult(zero, one, n);
    //             else
    //                 samples[i].zixi[exp] = LITTimeModMult(zero, zero, n);
    //         }
    //     }

    //     mpz_set_ui(t0sum, 0);
    //     mpz_set_ui(t1sum, 0);
    //     double e;
    //     //
    //     //#####################################################
    //     //CALCULATE CORRELATION HERE
    //     //#####################################################
    //     //
    //     for (int i = 0; i < NUMSAMPLES; i++)
    //     {
    //         //formular says: T0 = TM − ( w * E * TMult)
    //         //E is expected value of what? Of the bit to be 1?
    //         //TMult is what? Time for one multiplication?
    //         //T1= TM − (TMult(zi,xi) + (w−1) * ETMult)
    //         //the next section is experimental and can be removed / edited
    //         e = w / (double)((EXPBITS / 2) - exp); //calculate expected value E
    //         printf("probility for 1: %f\t for 0: %f\n", e, (1 - e));
    //         if (mpz_tstbit(samples[i].x, exp))
    //         {
    //             mpz_add_ui(t1sum, t1sum, samples[i].time);
    //         }
    //         else
    //             mpz_add_ui(t0sum, t0sum, samples[i].time);

    //         mpz_add(t0sum, t0sum, t0); //sum it up
    //         mpz_add(t1sum, t1sum, t1);
    //     }
    //     printf("t0sum: %s\tt1sum: %s\n", mpz_get_str(NULL, 10, t0sum), mpz_get_str(NULL, 10, t1sum));
    //     mpz_sub(t0sum, Sum_TM, t0sum);
    //     mpz_sub(t1sum, Sum_TM, t1sum);
    //     gmp_printf("total multiplication time = %Zd\n", Sum_TM);
    //     gmp_printf("error for 0: %Zd\terror for 1: %Zd\n", t0sum, t1sum);

    //     if ((mpz_cmp(t1sum, t0sum)) > 0) //bit wahrscheinlich 1
    //     {
    //         mpz_add(y_trial, y_trial, adder);
    //         w--;
    //         mpz_sub_ui(Sum_TM, Sum_TM, time1Mult);
    //     }
    //     mpz_add(adder, adder, adder);
    // }

    mpz_set(y_trial, y);

    printf("Berechneter Exponent: %s\n", mpz_get_str(NULL, 16, y_trial));

    ok = key_daemon(y_ok, y_trial);
    printf("Das war %s\n", ok ? "richtig" : "falsch");
    if (!ok)
    {
        printf("Richtig war: %s\n", mpz_get_str(NULL, 16, y_ok));
    }
    disc_daemon(); /* Verbindung zum Daemon beenden */

#ifdef DEBUG_VERIFY
    verifyData(y_ok, X, T, TM);
#endif

    exit(0);
}
