/*############################################################################
  # Copyright 2016 Intel Corporation
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  ############################################################################*/

/*!
 * \file
 * \brief EpidComputePreSig implementation.
 */

#include "epid/member/context.h"

/// Handle SDK Error with Break
#define BREAK_ON_EPID_ERROR(ret) \
  if (kEpidNoErr != (ret)) {     \
    break;                       \
  }

/// Count of elements in array
#define COUNT_OF(A) (sizeof(A) / sizeof((A)[0]))

EpidStatus EpidComputePreSig(MemberCtx const* ctx,
                             PreComputedSignature* precompsig) {
  EpidStatus res = kEpidNotImpl;

  EcPoint* B = NULL;
  EcPoint* K = NULL;
  EcPoint* T = NULL;
  EcPoint* R1 = NULL;

  FfElement* R2 = NULL;

  FfElement* a = NULL;
  FfElement* b = NULL;
  FfElement* rx = NULL;
  FfElement* rf = NULL;
  FfElement* ra = NULL;
  FfElement* rb = NULL;
  FfElement* t1 = NULL;
  FfElement* t2 = NULL;
  FfElement* f = NULL;

  if (!ctx || !precompsig) return kEpidBadArgErr;
  if (!ctx->epid2_params || !ctx->pub_key || !ctx->priv_key)
    return kEpidBadArgErr;

  do {
    // handy shorthands:
    EcGroup* G1 = ctx->epid2_params->G1;
    FiniteField* GT = ctx->epid2_params->GT;
    FiniteField* Fp = ctx->epid2_params->Fp;
    EcPoint* h2 = ctx->pub_key->h2;
    EcPoint* A = ctx->priv_key->A;
    FfElement* x = ctx->priv_key->x;
    BigNumStr f_str = {0};
    BigNumStr a_str = {0};
    BigNumStr t1_str = {0};
    BigNumStr rf_str = {0};
    BigNumStr t2_str = {0};
    BigNumStr ra_str = {0};
    static const BigNumStr one = {
        {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}}};

    if (!G1 || !GT || !Fp || !h2 || !A || !x || !ctx->priv_key->f ||
        !ctx->e12 || !ctx->e22 || !ctx->e2w || !ctx->ea2) {
      res = kEpidBadArgErr;
      BREAK_ON_EPID_ERROR(res);
    }
    f = ctx->priv_key->f;
    // The following variables B, K, T, R1 (elements of G1), R2
    // (elements of GT), a, b, rx, rf, ra, rb, t1, t2 (256-bit
    // integers) are used.
    res = NewEcPoint(G1, &B);
    BREAK_ON_EPID_ERROR(res);
    res = NewEcPoint(G1, &K);
    BREAK_ON_EPID_ERROR(res);
    res = NewEcPoint(G1, &T);
    BREAK_ON_EPID_ERROR(res);
    res = NewEcPoint(G1, &R1);
    BREAK_ON_EPID_ERROR(res);
    res = NewFfElement(GT, &R2);
    BREAK_ON_EPID_ERROR(res);
    res = NewFfElement(Fp, &a);
    BREAK_ON_EPID_ERROR(res);
    res = NewFfElement(Fp, &b);
    BREAK_ON_EPID_ERROR(res);
    res = NewFfElement(Fp, &rx);
    BREAK_ON_EPID_ERROR(res);
    res = NewFfElement(Fp, &rf);
    BREAK_ON_EPID_ERROR(res);
    res = NewFfElement(Fp, &ra);
    BREAK_ON_EPID_ERROR(res);
    res = NewFfElement(Fp, &rb);
    BREAK_ON_EPID_ERROR(res);
    res = NewFfElement(Fp, &t1);
    BREAK_ON_EPID_ERROR(res);
    res = NewFfElement(Fp, &t2);
    BREAK_ON_EPID_ERROR(res);
    // 1. The member expects the pre-computation is done (e12, e22, e2w,
    //    ea2). Refer to Section 3.5 for the computation of these
    //    values.

    // 2. The member verifies gid in public key matches gid in private
    //    key.
    // 3. The member computes B = G1.getRandom().
    res = EcGetRandom(G1, ctx->rnd_func, ctx->rnd_param, B);
    BREAK_ON_EPID_ERROR(res);
    // 4. The member computes K = G1.sscmExp(B, f).
    res = WriteFfElement(Fp, f, &f_str, sizeof(f_str));
    BREAK_ON_EPID_ERROR(res);
    res = EcExp(G1, B, &f_str, K);
    BREAK_ON_EPID_ERROR(res);
    // 5. The member chooses randomly an integers a from [1, p-1].
    res = FfGetRandom(Fp, &one, ctx->rnd_func, ctx->rnd_param, a);
    BREAK_ON_EPID_ERROR(res);
    // 6. The member computes T = G1.sscmExp(h2, a).
    res = WriteFfElement(Fp, a, &a_str, sizeof(a_str));
    BREAK_ON_EPID_ERROR(res);
    res = EcExp(G1, h2, &a_str, T);
    BREAK_ON_EPID_ERROR(res);
    // 7. The member computes T = G1.mul(T, A).
    res = EcMul(G1, T, A, T);
    BREAK_ON_EPID_ERROR(res);
    // 8. The member computes b = (a * x) mod p.
    res = FfMul(Fp, a, x, b);
    BREAK_ON_EPID_ERROR(res);
    // 9. The member chooses rx, rf, ra, rb randomly from [1, p-1].
    res = FfGetRandom(Fp, &one, ctx->rnd_func, ctx->rnd_param, rx);
    BREAK_ON_EPID_ERROR(res);
    res = FfGetRandom(Fp, &one, ctx->rnd_func, ctx->rnd_param, rf);
    BREAK_ON_EPID_ERROR(res);
    res = FfGetRandom(Fp, &one, ctx->rnd_func, ctx->rnd_param, ra);
    BREAK_ON_EPID_ERROR(res);
    res = FfGetRandom(Fp, &one, ctx->rnd_func, ctx->rnd_param, rb);
    BREAK_ON_EPID_ERROR(res);
    // 10. The member computes t1 = (- rx) mod p.
    res = FfNeg(Fp, rx, t1);
    BREAK_ON_EPID_ERROR(res);
    // 11. The member computes t2 = (rb - a * rx) mod p.
    res = FfMul(Fp, a, rx, t2);
    BREAK_ON_EPID_ERROR(res);
    res = FfNeg(Fp, t2, t2);
    BREAK_ON_EPID_ERROR(res);
    res = FfAdd(Fp, rb, t2, t2);
    BREAK_ON_EPID_ERROR(res);
    // 12. The member computes R1 = G1.sscmExp(B, rf).
    res = WriteFfElement(Fp, rf, &rf_str, sizeof(rf_str));
    BREAK_ON_EPID_ERROR(res);
    res = EcExp(G1, B, &rf_str, R1);
    BREAK_ON_EPID_ERROR(res);
    // 13. The member computes R2 = GT.sscmMultiExp(ea2, t1, e12, rf,
    //     e22, t2, e2w, ra).
    res = WriteFfElement(Fp, t1, &t1_str, sizeof(t1_str));
    BREAK_ON_EPID_ERROR(res);
    res = WriteFfElement(Fp, t2, &t2_str, sizeof(t2_str));
    BREAK_ON_EPID_ERROR(res);
    res = WriteFfElement(Fp, ra, &ra_str, sizeof(ra_str));
    BREAK_ON_EPID_ERROR(res);
    {
      FfElement const* points[4];
      BigNumStr const* exponents[4];
      points[0] = ctx->ea2;
      points[1] = ctx->e12;
      points[2] = ctx->e22;
      points[3] = ctx->e2w;
      exponents[0] = &t1_str;
      exponents[1] = &rf_str;
      exponents[2] = &t2_str;
      exponents[3] = &ra_str;
      res = FfMultiExp(GT, points, exponents, COUNT_OF(points), R2);
      BREAK_ON_EPID_ERROR(res);
    }
    // 14. The member sets and outputs pre-sigma = (B, K, T, a, b, rx,
    //     rf, ra, rb, R1, R2).
    res = WriteEcPoint(G1, B, &precompsig->B, sizeof(precompsig->B));
    BREAK_ON_EPID_ERROR(res);
    res = WriteEcPoint(G1, K, &precompsig->K, sizeof(precompsig->K));
    BREAK_ON_EPID_ERROR(res);
    res = WriteEcPoint(G1, T, &precompsig->T, sizeof(precompsig->T));
    BREAK_ON_EPID_ERROR(res);
    res = WriteFfElement(Fp, a, &precompsig->a, sizeof(precompsig->a));
    BREAK_ON_EPID_ERROR(res);
    res = WriteFfElement(Fp, b, &precompsig->b, sizeof(precompsig->b));
    BREAK_ON_EPID_ERROR(res);
    res = WriteFfElement(Fp, rx, &precompsig->rx, sizeof(precompsig->rx));
    BREAK_ON_EPID_ERROR(res);
    res = WriteFfElement(Fp, rf, &precompsig->rf, sizeof(precompsig->rf));
    BREAK_ON_EPID_ERROR(res);
    res = WriteFfElement(Fp, ra, &precompsig->ra, sizeof(precompsig->ra));
    BREAK_ON_EPID_ERROR(res);
    res = WriteFfElement(Fp, rb, &precompsig->rb, sizeof(precompsig->rb));
    BREAK_ON_EPID_ERROR(res);
    res = WriteEcPoint(G1, R1, &precompsig->R1, sizeof(precompsig->R1));
    BREAK_ON_EPID_ERROR(res);
    res = WriteFfElement(GT, R2, &precompsig->R2, sizeof(precompsig->R2));
    BREAK_ON_EPID_ERROR(res);
    // 15. The member stores pre-sigma in the secure storage of the
    //     member.
    res = kEpidNoErr;
  } while (0);

  f = NULL;
  DeleteEcPoint(&B);
  DeleteEcPoint(&K);
  DeleteEcPoint(&T);
  DeleteEcPoint(&R1);
  DeleteFfElement(&R2);
  DeleteFfElement(&a);
  DeleteFfElement(&b);
  DeleteFfElement(&rx);
  DeleteFfElement(&rf);
  DeleteFfElement(&ra);
  DeleteFfElement(&rb);
  DeleteFfElement(&t1);
  DeleteFfElement(&t2);

  return (res);
}
