/*############################################################################
  # Copyright 2016-2017 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 Elliptic curve group implementation.
 */

#include "epid/common/math/ecgroup.h"
#include <string.h>
#include "epid/common/1.1/types.h"
#include "epid/common/math/hash.h"
#include "epid/common/math/src/bignum-internal.h"
#include "epid/common/math/src/ecgroup-internal.h"
#include "epid/common/math/src/finitefield-internal.h"
#include "epid/common/src/endian_convert.h"
#include "epid/common/src/memory.h"
#include "ext/ipp/include/ippcp.h"
#include "ext/ipp/include/ippcpdefs.h"

/// Handle SDK Error with Break
#define BREAK_ON_EPID_ERROR(ret) \
  if (kEpidNoErr != (ret)) {     \
    break;                       \
  }  /// Handle Ipp Errors with Break
#define BREAK_ON_IPP_ERROR(sts, ret)           \
  {                                            \
    IppStatus temp_sts = (sts);                \
    if (ippStsNoErr != temp_sts) {             \
      if (ippStsContextMatchErr == temp_sts) { \
        (ret) = kEpidMathErr;                  \
      } else {                                 \
        (ret) = kEpidBadArgErr;                \
      }                                        \
      break;                                   \
    }                                          \
  }

EpidStatus NewEcGroup(FiniteField const* ff, FfElement const* a,
                      FfElement const* b, FfElement const* x,
                      FfElement const* y, BigNum const* order,
                      BigNum const* cofactor, EcGroup** g) {
  EpidStatus result = kEpidNoErr;
  IppsGFpECState* state = NULL;
  OctStr scratch_buffer = NULL;
  EcGroup* grp = NULL;
  do {
    IppStatus ipp_status;
    int stateSize = 0;
    int scratch_size = 0;
    IppBNU order_bnu;
    IppBNU cofactor_bnu;
    int order_bnu_size;
    int cofactor_bnu_size;
    IppsBigNumSGN sgn;
    // validate input pointers
    if (!ff || !a || !b || !x || !y || !order || !cofactor || !g) {
      result = kEpidBadArgErr;
      break;
    }
    if (ff->element_len != a->element_len ||
        ff->element_len != b->element_len ||
        ff->element_len != x->element_len ||
        ff->element_len != y->element_len) {
      result = kEpidBadArgErr;
      break;
    }

    // construct the ECPrimeField
    ipp_status = ippsGFpECGetSize(ff->ipp_ff, &stateSize);
    if (ippStsNoErr != ipp_status) {
      if (ippStsSizeErr == ipp_status) {
        result = kEpidBadArgErr;
      } else {
        result = kEpidMathErr;
      }
      break;
    }

    grp = (EcGroup*)SAFE_ALLOC(sizeof(EcGroup));
    if (!grp) {
      result = kEpidMemAllocErr;
      break;
    }

    state = (IppsGFpECState*)SAFE_ALLOC(stateSize);
    if (!state) {
      result = kEpidMemAllocErr;
      break;
    }

    ipp_status = ippsRef_BN(&sgn, &order_bnu_size, &order_bnu, order->ipp_bn);
    order_bnu_size /= sizeof(CHAR_BIT) * 4;
    if (ippStsNoErr != ipp_status) {
      result = kEpidMathErr;
      break;
    }

    ipp_status =
        ippsRef_BN(&sgn, &cofactor_bnu_size, &cofactor_bnu, cofactor->ipp_bn);
    cofactor_bnu_size /= sizeof(CHAR_BIT) * 4;
    if (ippStsNoErr != ipp_status) {
      result = kEpidMathErr;
      break;
    }

    ipp_status =
        ippsGFpECInit(ff->ipp_ff, a->ipp_ff_elem, b->ipp_ff_elem, state);
    if (ippStsNoErr != ipp_status) {
      result = kEpidMathErr;
      break;
    }
    ipp_status = ippsGFpECSetSubgroup(x->ipp_ff_elem, y->ipp_ff_elem,
                                      order->ipp_bn, cofactor->ipp_bn, state);
    if (ippStsNoErr != ipp_status) {
      result = kEpidMathErr;
      break;
    }

    // allocate scratch buffer
    ipp_status = ippsGFpECScratchBufferSize(1, state, &scratch_size);
    // check return codes
    if (ippStsNoErr != ipp_status) {
      // ippStsContextMatchErr not possible since we create the state
      // in this function
      result = kEpidMathErr;
      break;
    }

    // allocate scratch buffer
    scratch_buffer = (OctStr)SAFE_ALLOC(scratch_size);
    if (!scratch_buffer) {
      result = kEpidMemAllocErr;
      break;
    }
    // Warning: once assigned ground field must never be modified. this was not
    // made const
    // to allow the FiniteField structure to be used in context when we want to
    // modify the parameters.
    grp->ff = (FiniteField*)ff;
    grp->ipp_ec = state;
    grp->scratch_buffer = scratch_buffer;
    *g = grp;
  } while (0);

  if (kEpidNoErr != result) {
    // we had a problem during init, free any allocated memory
    SAFE_FREE(state);
    SAFE_FREE(scratch_buffer);
    SAFE_FREE(grp);
  }
  return result;
}

void DeleteEcGroup(EcGroup** g) {
  if (!g || !(*g)) {
    return;
  }
  if ((*g)->ipp_ec) {
    SAFE_FREE((*g)->ipp_ec);
    (*g)->ipp_ec = NULL;
  }
  if ((*g)->scratch_buffer) {
    SAFE_FREE((*g)->scratch_buffer);
    (*g)->scratch_buffer = NULL;
  }
  SAFE_FREE(*g);
  *g = NULL;
}

EpidStatus NewEcPoint(EcGroup const* g, EcPoint** p) {
  EpidStatus result = kEpidErr;
  IppsGFpECPoint* ec_pt_context = NULL;
  EcPoint* ecpoint = NULL;
  do {
    IppStatus sts = ippStsNoErr;
    int sizeInBytes = 0;
    // validate inputs
    if (!g || !p) {
      result = kEpidBadArgErr;
      break;
    } else if (!g->ipp_ec) {
      result = kEpidBadArgErr;
      break;
    }
    // get size
    sts = ippsGFpECPointGetSize(g->ipp_ec, &sizeInBytes);
    if (ippStsContextMatchErr == sts) {
      result = kEpidBadArgErr;
      break;
    } else if (ippStsNoErr != sts) {
      result = kEpidMathErr;
      break;
    }
    // allocate memory
    ec_pt_context = (IppsGFpECPoint*)SAFE_ALLOC(sizeInBytes);
    if (!ec_pt_context) {
      result = kEpidMemAllocErr;
      break;
    }
    // Initialize
    sts = ippsGFpECPointInit(NULL, NULL, ec_pt_context, g->ipp_ec);
    if (ippStsContextMatchErr == sts) {
      result = kEpidBadArgErr;
      break;
    } else if (ippStsNoErr != sts) {
      result = kEpidMathErr;
      break;
    }
    ecpoint = SAFE_ALLOC(sizeof(EcPoint));
    if (!ecpoint) {
      result = kEpidMemAllocErr;
      break;
    }
    if (!g->ff) {
      result = kEpidBadArgErr;
      break;
    }
    ecpoint->element_len = g->ff->element_len;
    ecpoint->ipp_ec_pt = ec_pt_context;
    *p = ecpoint;
    result = kEpidNoErr;
  } while (0);
  if (kEpidNoErr != result) {
    SAFE_FREE(ec_pt_context);
    SAFE_FREE(ecpoint);
  }
  return result;
}

void DeleteEcPoint(EcPoint** p) {
  if (p) {
    if (*p) {
      SAFE_FREE((*p)->ipp_ec_pt);
    }
    SAFE_FREE(*p);
  }
}

/// Check and initialize element if it is in elliptic curve group.
/*!
  This is internal function.
  Takes a value p as input. If p is indeed an element of g, it
  outputs true, otherwise, it outputs false.

  This is only used to check if input buffer are actually valid
  elements in group. If p is in g, this fills p and initializes it to
  internal FfElement format.

  \param[in] g
  The eliptic curve group in which to perform the check
  \param[in] p_str
  Serialized eliptic curve group element to check
  \param[in] strlen
  The size of p_str in bytes.
  \param[out] p
  Deserialized value of p_str
  \param[out] in_group
  Result of the check

  \returns ::EpidStatus

  \see NewEcPoint
*/
EpidStatus eccontains(EcGroup* g, ConstOctStr p_str, size_t strlen, EcPoint* p,
                      bool* in_group) {
  EpidStatus result = kEpidErr;
  IppStatus sts = ippStsNoErr;
  FiniteField* fp = NULL;
  FfElement* fp_x = NULL;
  FfElement* fp_y = NULL;
  ConstIppOctStr byte_str = (ConstIppOctStr)p_str;
  IppECResult ec_result = ippECPointIsNotValid;
  int ipp_half_strlen = (int)strlen / 2;

  if (!g || !p_str || !p || !in_group) {
    return kEpidBadArgErr;
  }
  if (!g->ff || !g->ipp_ec || !p->ipp_ec_pt) {
    return kEpidBadArgErr;
  }

  if (INT_MAX < strlen || strlen <= 0 || strlen & 0x1) {
    return kEpidBadArgErr;
  }

  do {
    size_t i = 0;
    // if the string is all zeros then we take it as point at infinity
    for (i = 0; i < strlen; i++) {
      if (0 != byte_str[i]) {
        break;
      }
    }
    if (i >= strlen) {
      // p_str is point at infinity! Set it and we are done
      sts = ippsGFpECSetPointAtInfinity(p->ipp_ec_pt, g->ipp_ec);
      // check return codes
      if (ippStsNoErr != sts) {
        if (ippStsContextMatchErr == sts)
          result = kEpidBadArgErr;
        else
          result = kEpidMathErr;
        break;
      }
      *in_group = true;
      result = kEpidNoErr;
      break;
    }
    // get finite field
    fp = g->ff;
    // create element X
    result = NewFfElement(fp, &fp_x);
    if (kEpidNoErr != result) {
      break;
    }

    // create element Y
    result = NewFfElement(fp, &fp_y);
    if (kEpidNoErr != result) {
      break;
    }

    // set element X data
    result = SetFfElementOctString(byte_str, ipp_half_strlen, fp_x, fp);
    if (kEpidNoErr != result) {
      break;
    }

    // set element Y data
    result = SetFfElementOctString(byte_str + ipp_half_strlen, ipp_half_strlen,
                                   fp_y, fp);
    if (kEpidNoErr != result) {
      break;
    }

    // set point from elements
    sts = ippsGFpECSetPoint(fp_x->ipp_ff_elem, fp_y->ipp_ff_elem, p->ipp_ec_pt,
                            g->ipp_ec);
    // check return codes
    if (ippStsNoErr != sts) {
      if (ippStsContextMatchErr == sts)
        result = kEpidBadArgErr;
      else
        result = kEpidMathErr;
      break;
    }

    // verify the point is actually on the curve
    sts = ippsGFpECTstPoint(p->ipp_ec_pt, &ec_result, g->ipp_ec);
    // check return codes
    if (ippStsNoErr != sts) {
      if (ippStsContextMatchErr == sts)
        result = kEpidBadArgErr;
      else
        result = kEpidMathErr;
      break;
    }
    *in_group = (ippECValid == ec_result);
    result = kEpidNoErr;
  } while (0);

  DeleteFfElement(&fp_x);
  DeleteFfElement(&fp_y);
  return result;
}

EpidStatus ReadEcPoint(EcGroup* g, ConstOctStr p_str, size_t strlen,
                       EcPoint* p) {
  EpidStatus result;
  bool in_group = false;

  if (!g || !p_str || !p) {
    return kEpidBadArgErr;
  }
  if (0 == strlen) {
    return kEpidBadArgErr;
  }

  result = eccontains(g, p_str, strlen, p, &in_group);
  if (kEpidNoErr != result) {
    return result;
  }
  if (in_group == false) {
    IppStatus sts = ippsGFpECPointInit(NULL, NULL, p->ipp_ec_pt, g->ipp_ec);
    if (ippStsContextMatchErr == sts) {
      return kEpidBadArgErr;
    } else if (ippStsNoErr != sts) {
      return kEpidMathErr;
    }
    return kEpidBadArgErr;
  }
  return kEpidNoErr;
}

EpidStatus WriteEcPoint(EcGroup* g, EcPoint const* p, OctStr p_str,
                        size_t strlen) {
  EpidStatus result = kEpidErr;
  FiniteField* fp = NULL;
  FfElement* fp_x = NULL;
  FfElement* fp_y = NULL;
  IppOctStr byte_str = (IppOctStr)p_str;
  IppStatus sts = ippStsNoErr;
  int ipp_half_strlen = (int)strlen / 2;

  if (!g || !p || !p_str) {
    return kEpidBadArgErr;
  }
  if (!g->ff || !g->ipp_ec || !p->ipp_ec_pt) {
    return kEpidBadArgErr;
  }
  if (INT_MAX < strlen) {
    return kEpidBadArgErr;
  }

  if (INT_MAX < strlen || strlen <= 0 || strlen & 0x1) {
    return kEpidBadArgErr;
  }

  do {
    // get finite field
    fp = g->ff;

    // create element X
    result = NewFfElement(fp, &fp_x);
    if (kEpidNoErr != result) {
      break;
    }

    // create element Y
    result = NewFfElement(fp, &fp_y);
    if (kEpidNoErr != result) {
      break;
    }

    // get elements from point
    sts = ippsGFpECGetPoint(p->ipp_ec_pt, fp_x->ipp_ff_elem, fp_y->ipp_ff_elem,
                            g->ipp_ec);
    // check return codes
    if (ippStsNoErr != sts) {
      if (ippStsPointAtInfinity == sts) {
        memset(p_str, 0, strlen);
        result = kEpidNoErr;
      } else if (ippStsContextMatchErr == sts || ippStsOutOfRangeErr == sts) {
        result = kEpidBadArgErr;
      } else {
        result = kEpidMathErr;
      }
      break;
    }

    // get element X data
    sts = ippsGFpGetElementOctString(fp_x->ipp_ff_elem, byte_str,
                                     ipp_half_strlen, fp->ipp_ff);
    // check return codes
    if (ippStsNoErr != sts) {
      if (ippStsContextMatchErr == sts)
        result = kEpidBadArgErr;
      else
        result = kEpidMathErr;
      break;
    }

    // get element Y data
    sts = ippsGFpGetElementOctString(fp_y->ipp_ff_elem,
                                     byte_str + ipp_half_strlen,
                                     ipp_half_strlen, fp->ipp_ff);
    // check return codes
    if (ippStsNoErr != sts) {
      if (ippStsContextMatchErr == sts)
        result = kEpidBadArgErr;
      else
        result = kEpidMathErr;
      break;
    }
    result = kEpidNoErr;
  } while (0);

  DeleteFfElement(&fp_x);
  DeleteFfElement(&fp_y);

  return result;
}

EpidStatus EcMul(EcGroup* g, EcPoint const* a, EcPoint const* b, EcPoint* r) {
  IppStatus sts = ippStsNoErr;
  if (!g || !a || !b || !r) {
    return kEpidBadArgErr;
  } else if (!g->ff || !g->ipp_ec || !a->ipp_ec_pt || !b->ipp_ec_pt ||
             !r->ipp_ec_pt) {
    return kEpidBadArgErr;
  }
  if (g->ff->element_len != a->element_len ||
      g->ff->element_len != b->element_len ||
      g->ff->element_len != r->element_len) {
    return kEpidBadArgErr;
  }
  // Multiplies elliptic curve points
  sts = ippsGFpECAddPoint(a->ipp_ec_pt, b->ipp_ec_pt, r->ipp_ec_pt, g->ipp_ec);
  // Check return codes
  if (ippStsNoErr != sts) {
    if (ippStsContextMatchErr == sts)
      return kEpidBadArgErr;
    else
      return kEpidMathErr;
  }
  return kEpidNoErr;
}

EpidStatus EcExp(EcGroup* g, EcPoint const* a, BigNumStr const* b, EcPoint* r) {
  EpidStatus result = kEpidErr;
  BigNum* b_bn = NULL;
  do {
    IppStatus sts = ippStsNoErr;

    // Check required parameters
    if (!g || !a || !b || !r) {
      result = kEpidBadArgErr;
      break;
    } else if (!g->ff || !g->ipp_ec || !a->ipp_ec_pt || !r->ipp_ec_pt) {
      result = kEpidBadArgErr;
      break;
    }
    if (g->ff->element_len != a->element_len ||
        g->ff->element_len != r->element_len) {
      result = kEpidBadArgErr;
      break;
    }

    // Create and initialize big number element for ipp call
    result = NewBigNum(sizeof(((BigNumStr*)0)->data.data), &b_bn);
    if (kEpidNoErr != result) break;
    result = ReadBigNum(b, sizeof(*b), b_bn);
    if (kEpidNoErr != result) break;
    sts = ippsGFpECMulPoint(a->ipp_ec_pt, b_bn->ipp_bn, r->ipp_ec_pt, g->ipp_ec,
                            g->scratch_buffer);
    if (ippStsNoErr != sts) {
      if (ippStsContextMatchErr == sts || ippStsRangeErr == sts ||
          ippStsOutOfRangeErr == sts)
        result = kEpidBadArgErr;
      else
        result = kEpidMathErr;
      break;
    }
    result = kEpidNoErr;
  } while (0);
  DeleteBigNum(&b_bn);
  return result;
}

EpidStatus EcSscmExp(EcGroup* g, EcPoint const* a, BigNumStr const* b,
                     EcPoint* r) {
  // call EcExp directly because its implementation is side channel
  // mitigated already
  return EcExp(g, a, b, r);
}

EpidStatus EcMultiExp(EcGroup* g, EcPoint const** a, BigNumStr const** b,
                      size_t m, EcPoint* r) {
  EpidStatus result = kEpidErr;
  BigNum* b_bn = NULL;
  EcPoint* ecp_t = NULL;
  size_t i = 0;
  size_t ii = 0;

  if (!g || !a || !b || !r) {
    return kEpidBadArgErr;
  }
  if (!g->ff || !g->ipp_ec || m <= 0) {
    return kEpidBadArgErr;
  }

  // Verify that ec points are not NULL
  for (i = 0; i < m; i++) {
    if (!a[i]) {
      return kEpidBadArgErr;
    }
    if (!a[i]->ipp_ec_pt) {
      return kEpidBadArgErr;
    }
    if (g->ff->element_len != a[i]->element_len) {
      return kEpidBadArgErr;
    }
    for (ii = 0; ii < i; ii++) {
      if (a[i]->element_len != a[ii]->element_len) {
        return kEpidBadArgErr;
      }
    }
  }

  if (g->ff->element_len != r->element_len) {
    return kEpidBadArgErr;
  }

  do {
    IppStatus sts = ippStsNoErr;

    // Create big number element for ipp call
    result = NewBigNum(sizeof(((BigNumStr*)0)->data.data), &b_bn);
    if (kEpidNoErr != result) break;
    // Create temporal EcPoint element
    result = NewEcPoint(g, &ecp_t);
    if (kEpidNoErr != result) break;

    for (i = 0; i < m; i++) {
      // Initialize big number element for ipp call
      result = ReadBigNum(b[i], sizeof(BigNumStr), b_bn);
      if (kEpidNoErr != result) break;
      sts = ippsGFpECMulPoint(a[i]->ipp_ec_pt, b_bn->ipp_bn, ecp_t->ipp_ec_pt,
                              g->ipp_ec, g->scratch_buffer);
      if (ippStsNoErr != sts) {
        if (ippStsContextMatchErr == sts || ippStsRangeErr == sts ||
            ippStsOutOfRangeErr == sts)
          result = kEpidBadArgErr;
        else
          result = kEpidMathErr;
        break;
      }
      if (i == 0) {
        sts = ippsGFpECCpyPoint(ecp_t->ipp_ec_pt, r->ipp_ec_pt, g->ipp_ec);
        if (ippStsNoErr != sts) {
          result = kEpidMathErr;
          break;
        }
      } else {
        sts = ippsGFpECAddPoint(ecp_t->ipp_ec_pt, r->ipp_ec_pt, r->ipp_ec_pt,
                                g->ipp_ec);
        if (ippStsNoErr != sts) {
          result = kEpidMathErr;
          break;
        }
      }
    }
    if (kEpidNoErr != result) break;

    result = kEpidNoErr;
  } while (0);
  DeleteBigNum(&b_bn);
  DeleteEcPoint(&ecp_t);

  return result;
}

EpidStatus EcMultiExpBn(EcGroup* g, EcPoint const** a, BigNum const** b,
                        size_t m, EcPoint* r) {
  EpidStatus result = kEpidErr;
  EcPoint* ecp_t = NULL;
  size_t i = 0;
  size_t ii = 0;

  if (!g || !a || !b || !r) {
    return kEpidBadArgErr;
  }
  if (!g->ff || !g->ipp_ec || m <= 0) {
    return kEpidBadArgErr;
  }

  // Verify that ec points are not NULL
  for (i = 0; i < m; i++) {
    if (!a[i]) {
      return kEpidBadArgErr;
    }
    if (!a[i]->ipp_ec_pt) {
      return kEpidBadArgErr;
    }
    if (!b[i]) {
      return kEpidBadArgErr;
    }
    if (!b[i]->ipp_bn) {
      return kEpidBadArgErr;
    }

    if (g->ff->element_len != a[i]->element_len) {
      return kEpidBadArgErr;
    }
    for (ii = 0; ii < i; ii++) {
      if (a[i]->element_len != a[ii]->element_len) {
        return kEpidBadArgErr;
      }
    }
  }

  if (g->ff->element_len != r->element_len) {
    return kEpidBadArgErr;
  }

  do {
    IppStatus sts = ippStsNoErr;
    // Create temporal EcPoint element
    result = NewEcPoint(g, &ecp_t);
    if (kEpidNoErr != result) break;

    for (i = 0; i < m; i++) {
      sts = ippsGFpECMulPoint(a[i]->ipp_ec_pt, b[i]->ipp_bn, ecp_t->ipp_ec_pt,
                              g->ipp_ec, g->scratch_buffer);
      if (ippStsNoErr != sts) {
        if (ippStsContextMatchErr == sts || ippStsRangeErr == sts ||
            ippStsOutOfRangeErr == sts)
          result = kEpidBadArgErr;
        else
          result = kEpidMathErr;
        break;
      }
      if (i == 0) {
        sts = ippsGFpECCpyPoint(ecp_t->ipp_ec_pt, r->ipp_ec_pt, g->ipp_ec);
        if (ippStsNoErr != sts) {
          result = kEpidMathErr;
          break;
        }
      } else {
        sts = ippsGFpECAddPoint(ecp_t->ipp_ec_pt, r->ipp_ec_pt, r->ipp_ec_pt,
                                g->ipp_ec);
        if (ippStsNoErr != sts) {
          result = kEpidMathErr;
          break;
        }
      }
    }
    if (kEpidNoErr != result) break;

    result = kEpidNoErr;
  } while (0);
  DeleteEcPoint(&ecp_t);

  return result;
}

EpidStatus EcSscmMultiExp(EcGroup* g, EcPoint const** a, BigNumStr const** b,
                          size_t m, EcPoint* r) {
  // call EcMultiExp directly because its implementation is side channel
  // mitigated already
  return EcMultiExp(g, a, b, m, r);
}

EpidStatus EcGetRandom(EcGroup* g, BitSupplier rnd_func, void* rnd_func_param,
                       EcPoint* r) {
  IppStatus sts = ippStsNoErr;
  if (!g || !rnd_func || !r) {
    return kEpidBadArgErr;
  }
  if (!g->ff || !g->ipp_ec || !g->scratch_buffer) {
    return kEpidBadArgErr;
  }

  if (g->ff->element_len != r->element_len) {
    return kEpidBadArgErr;
  }

  sts =
      ippsGFpECSetPointRandom(r->ipp_ec_pt, g->ipp_ec, (IppBitSupplier)rnd_func,
                              rnd_func_param, g->scratch_buffer);
  if (ippStsNoErr != sts) {
    if (ippStsContextMatchErr == sts) {
      return kEpidBadArgErr;
    } else {
      return kEpidMathErr;
    }
  }
  return kEpidNoErr;
}

EpidStatus EcInGroup(EcGroup* g, ConstOctStr p_str, size_t strlen,
                     bool* in_group) {
  EpidStatus result = kEpidErr;
  EcPoint* p = NULL;

  if (!g || !p_str || !in_group) {
    return kEpidBadArgErr;
  }
  if (!g->ff) {
    return kEpidBadArgErr;
  }
  if (0 == strlen) {
    return kEpidBadArgErr;
  }

  if (strlen != sizeof(G1ElemStr) && strlen != sizeof(G2ElemStr)) {
    *in_group = false;
    return kEpidBadArgErr;
  } else {
    if (strlen == sizeof(G1ElemStr)) {
      // check finitefield.elementlen with strlen
      // multiply by 2 for x,y and 4 multiply to convert dword to bytes
      size_t info_elementLen_in_byte = (g->ff->element_len) * 2 * 4;
      if (info_elementLen_in_byte != strlen) {
        *in_group = false;
        return kEpidBadArgErr;
      }
      // check Fq basic and ground degree
      if (g->ff->basic_degree != 1 || g->ff->ground_degree != 1) {
        *in_group = false;
        return kEpidBadArgErr;
      }
    }
    if (strlen == sizeof(G2ElemStr)) {
      // check info.elementlen with strlen
      // multiply by 2 for x,y and 4 multiply to convert dword to bytes
      size_t info_elementLen_in_byte = (g->ff->element_len) * 2 * 4;
      FiniteField* ground_ff = NULL;
      if (info_elementLen_in_byte != strlen) {
        *in_group = false;
        return kEpidBadArgErr;
      }
      // check Fq2 basic and ground degree
      if (g->ff->basic_degree != 2 || g->ff->ground_degree != 2) {
        *in_group = false;
        return kEpidBadArgErr;
      }
      // check Fq basic and ground degree
      ground_ff = g->ff->ground_ff;
      if (ground_ff == NULL) {
        *in_group = false;
        return kEpidBadArgErr;
      }

      if (ground_ff->basic_degree != 1 || ground_ff->ground_degree != 1) {
        *in_group = false;
        return kEpidBadArgErr;
      }
    }
  }

  do {
    result = NewEcPoint(g, &p);
    if (kEpidNoErr != result) break;

    result = eccontains(g, p_str, strlen, p, in_group);
    if (kEpidNoErr != result) break;

    result = kEpidNoErr;
  } while (0);

  DeleteEcPoint(&p);

  return result;
}

/// The number of attempts to hash a message to an element
#define EPID_ECHASH_WATCHDOG (50)

#pragma pack(1)
/// 336 bit octet string
typedef struct OctStr336 {
  unsigned char data[336 / CHAR_BIT];  ///< 336 bit data
} OctStr336;
#pragma pack()

/*!
Returns the first bit and the next 336 bits of str in octet string.

\param[in] str hash string
\param[in] str_len hash string lengh in bytes
\param[out] first_bit first bit of str
\param[out] t pointer to the first 336 bits of input str after the first bit
\param[in] t_len length of t octet string

\returns ::EpidStatus
*/
static EpidStatus SplitHashBits(ConstOctStr str, size_t str_len,
                                uint32_t* first_bit, OctStr336* t) {
  // this is 336bits /8 bits per byte = 42 bytes
  OctStr336 next336 = {0};
  size_t i = 0;
  ConstIppOctStr data = (ConstIppOctStr)str;
  if (!str || !first_bit || !t) return kEpidBadArgErr;
  if (str_len < sizeof(next336) + 1) {
    // we need at least 337 bits!
    return kEpidBadArgErr;
  }
  for (i = 0; i < sizeof(next336); i++) {
    // This is not overflowing since str length was assured to
    // be at least one byte greater than needed for 336 bits. We are
    // carrying in the first bit of that byte.
    uint8_t carry = ((data[i + 1] & 0x80) >> 7);
    next336.data[i] = (((data[i] << 1) & 0xFF) | carry) & 0xFF;
  }
  *first_bit = ((data[0] & 0x80) >> 7);
  *t = next336;
  return kEpidNoErr;
}

EpidStatus Epid11EcHash(EcGroup* g, ConstOctStr msg, size_t msg_len,
                        EcPoint* r) {
  EpidStatus result = kEpidErr;

#pragma pack(1)
  struct {
    uint32_t msg_len;
    uint8_t msg[1];
  }* hash_buf = NULL;
#pragma pack()
  size_t hash_buf_size = 0;

  FfElement* a = NULL;
  FfElement* b = NULL;

  FfElement* rx = NULL;
  FfElement* t1 = NULL;
  FfElement* t2 = NULL;

  BigNum* q = NULL;
  BigNum* t_bn = NULL;
  BigNum* h_bn = NULL;

  FiniteField* ff = NULL;

  // check parameters
  if ((!msg && msg_len > 0) || !r || !g) {
    return kEpidBadArgErr;
  }
  if (!g->ff || !g->ipp_ec || !r->ipp_ec_pt) {
    return kEpidBadArgErr;
  }

  if (g->ff->element_len != r->element_len) {
    return kEpidBadArgErr;
  }

  // mitigate hash_buf_size and msg_len overflow
  if (INT_MAX - sizeof(uint32_t) < msg_len) {
    return kEpidBadArgErr;
  }

  do {
    IppStatus sts;
    uint32_t i = 0;
    uint32_t ip1 = 0;
    uint32_t high_bit = 0;

    IppsGFpState* ipp_ff = NULL;

    int sqrt_loop_count = 2 * EPID_ECHASH_WATCHDOG;
    Sha256Digest message_digest[2] = {0};
    OctStr336 t = {0};

    hash_buf_size = sizeof(*hash_buf) - sizeof(hash_buf->msg) + msg_len;
    hash_buf = SAFE_ALLOC(hash_buf_size);
    if (!hash_buf) {
      result = kEpidMemAllocErr;
      break;
    }

    result = NewBigNum(sizeof(BigNumStr), &h_bn);
    BREAK_ON_EPID_ERROR(result);
    sts = ippsGFpECGet(&ipp_ff, 0, 0, g->ipp_ec);
    BREAK_ON_IPP_ERROR(sts, result);
    sts = ippsGFpECGetSubgroup(&ipp_ff, 0, 0, 0, h_bn->ipp_bn, g->ipp_ec);
    BREAK_ON_IPP_ERROR(sts, result);
    ff = g->ff;

    result = NewFfElement(ff, &a);
    BREAK_ON_EPID_ERROR(result);
    result = NewFfElement(ff, &b);
    BREAK_ON_EPID_ERROR(result);
    result = NewFfElement(ff, &rx);
    BREAK_ON_EPID_ERROR(result);
    result = NewFfElement(ff, &t1);
    BREAK_ON_EPID_ERROR(result);
    result = NewFfElement(ff, &t2);
    BREAK_ON_EPID_ERROR(result);
    result = NewBigNum(sizeof(t), &t_bn);
    BREAK_ON_EPID_ERROR(result);

    sts = ippsGFpECGet(0, a->ipp_ff_elem, b->ipp_ff_elem, g->ipp_ec);
    BREAK_ON_IPP_ERROR(sts, result);

    // compute H = hash (i || m) || Hash (i+1 || m) where (i =ipp32u)
    // copy variable length message to the buffer to hash
    if (0 != memcpy_S(hash_buf->msg,
                      hash_buf_size - sizeof(*hash_buf) + sizeof(hash_buf->msg),
                      msg, msg_len)) {
      result = kEpidErr;
      break;
    }

    do {
      result = kEpidErr;

      // set hash (i || m) portion
      hash_buf->msg_len = ntohl(i);
      result = Sha256MessageDigest(hash_buf, hash_buf_size, &message_digest[0]);
      BREAK_ON_EPID_ERROR(result);
      // set hash (i+1 || m) portion
      ip1 = i + 1;
      hash_buf->msg_len = ntohl(ip1);
      result = Sha256MessageDigest(hash_buf, hash_buf_size, &message_digest[1]);
      BREAK_ON_EPID_ERROR(result);
      // let b = first bit of H
      // t = next 336bits of H (336 = length(q) + slen)
      result =
          SplitHashBits(message_digest, sizeof(message_digest), &high_bit, &t);
      BREAK_ON_EPID_ERROR(result);
      result = ReadBigNum(&t, sizeof(t), t_bn);
      BREAK_ON_EPID_ERROR(result);
      // compute rx = t mod q (aka prime field based on q)
      result = InitFfElementFromBn(ff, t_bn, rx);
      BREAK_ON_EPID_ERROR(result);

      // t1 = (rx^3 + a*rx + b) mod q
      result = FfMul(ff, rx, rx, t1);
      BREAK_ON_EPID_ERROR(result);
      result = FfMul(ff, t1, rx, t1);
      BREAK_ON_EPID_ERROR(result);
      result = FfMul(ff, a, rx, t2);
      BREAK_ON_EPID_ERROR(result);
      result = FfAdd(ff, t1, t2, t1);
      BREAK_ON_EPID_ERROR(result);
      result = FfAdd(ff, t1, b, t1);
      BREAK_ON_EPID_ERROR(result);

      // t2 = &ff.sqrt(t1)
      result = FfSqrt(ff, t1, t2);
      if (kEpidMathQuadraticNonResidueError == result) {
        // if sqrt fail set i = i+ 2 and repeat from top
        i += 2;
        continue;
      } else if (kEpidNoErr != result) {
        result = kEpidErr;
      }
      break;
    } while (--sqrt_loop_count);

    BREAK_ON_EPID_ERROR(result);
    // reset to fail to catch other errors
    result = kEpidErr;

    // y[0] = min (t2, q-t2), y[1] = max(t2, q-t2)
    if (0 == high_bit) {
      // q-t2 = &ff.neg(t2)
      result = FfNeg(ff, t2, t2);
      BREAK_ON_EPID_ERROR(result);
    }

    // Ry = y[b]
    sts = ippsGFpECSetPoint(rx->ipp_ff_elem, t2->ipp_ff_elem, r->ipp_ec_pt,
                            g->ipp_ec);
    BREAK_ON_IPP_ERROR(sts, result);
    // R = E(&ff).exp(R,h)
    sts = ippsGFpECMulPoint(r->ipp_ec_pt, h_bn->ipp_bn, r->ipp_ec_pt, g->ipp_ec,
                            g->scratch_buffer);
    BREAK_ON_IPP_ERROR(sts, result);

    result = kEpidNoErr;
  } while (0);

  SAFE_FREE(hash_buf);
  DeleteFfElement(&a);
  DeleteFfElement(&b);
  DeleteFfElement(&rx);
  DeleteFfElement(&t1);
  DeleteFfElement(&t2);
  DeleteBigNum(&h_bn);
  DeleteBigNum(&t_bn);
  DeleteBigNum(&q);

  return result;
}

EpidStatus EcHash(EcGroup* g, ConstOctStr msg, size_t msg_len, HashAlg hash_alg,
                  EcPoint* r, uint32_t* iterations) {
  IppStatus sts = ippStsNoErr;
  IppHashAlgId hash_id;
  int ipp_msg_len = 0;
  Ipp32u ipp_i = 0;
  if (!g || (!msg && msg_len > 0) || !r) {
    return kEpidBadArgErr;
  } else if (!g->ff || !g->ipp_ec || !r->ipp_ec_pt) {
    return kEpidBadArgErr;
  }
  // because we use ipp function with message length parameter
  // defined as "int" we need to verify that input length
  // do not exceed INT_MAX to avoid overflow
  if (msg_len > INT_MAX) {
    return kEpidBadArgErr;
  }
  ipp_msg_len = (int)msg_len;
  if (kSha256 == hash_alg) {
    hash_id = ippHashAlg_SHA256;
  } else if (kSha384 == hash_alg) {
    hash_id = ippHashAlg_SHA384;
  } else if (kSha512 == hash_alg) {
    hash_id = ippHashAlg_SHA512;
  } else if (kSha512_256 == hash_alg) {
    hash_id = ippHashAlg_SHA512_256;
  } else {
    return kEpidHashAlgorithmNotSupported;
  }

  if (g->ff->element_len != r->element_len) {
    return kEpidBadArgErr;
  }

  do {
    sts = ippsGFpECSetPointHash(ipp_i, msg, ipp_msg_len, r->ipp_ec_pt,
                                g->ipp_ec, hash_id, g->scratch_buffer);
  } while (ippStsQuadraticNonResidueErr == sts &&
           ipp_i++ < EPID_ECHASH_WATCHDOG);

  if (iterations) {
    *iterations = (uint32_t)ipp_i;
  }

  if (ippStsContextMatchErr == sts || ippStsBadArgErr == sts ||
      ippStsLengthErr == sts) {
    return kEpidBadArgErr;
  }
  if (ippStsNoErr != sts) {
    return kEpidMathErr;
  }

  return kEpidNoErr;
}

EpidStatus EcMakePoint(EcGroup* g, FfElement const* x, EcPoint* r) {
  IppStatus sts = ippStsNoErr;
  if (!g || !x || !r) {
    return kEpidBadArgErr;
  }
  if (!g->ff || !g->ipp_ec || !x->ipp_ff_elem || !r->ipp_ec_pt) {
    return kEpidBadArgErr;
  }

  if (g->ff->element_len != x->element_len ||
      g->ff->element_len != r->element_len) {
    return kEpidBadArgErr;
  }
  sts = ippsGFpECMakePoint(x->ipp_ff_elem, r->ipp_ec_pt, g->ipp_ec);
  if (ippStsNoErr != sts) {
    if (ippStsContextMatchErr == sts || ippStsQuadraticNonResidueErr == sts ||
        ippStsBadArgErr == sts)
      return kEpidBadArgErr;
    else
      return kEpidMathErr;
  }
  return kEpidNoErr;
}

EpidStatus EcInverse(EcGroup* g, EcPoint const* p, EcPoint* r) {
  IppStatus sts = ippStsNoErr;
  if (!g || !p || !r) {
    return kEpidBadArgErr;
  } else if (!g->ff || !g->ipp_ec || !p->ipp_ec_pt || !r->ipp_ec_pt) {
    return kEpidBadArgErr;
  }

  if (g->ff->element_len != p->element_len ||
      g->ff->element_len != r->element_len) {
    return kEpidBadArgErr;
  }
  // Inverses elliptic curve point
  sts = ippsGFpECNegPoint(p->ipp_ec_pt, r->ipp_ec_pt, g->ipp_ec);
  // Check return codes
  if (ippStsNoErr != sts) {
    if (ippStsContextMatchErr == sts)
      return kEpidBadArgErr;
    else
      return kEpidMathErr;
  }
  return kEpidNoErr;
}

EpidStatus EcIsEqual(EcGroup* g, EcPoint const* a, EcPoint const* b,
                     bool* is_equal) {
  IppStatus sts;
  IppECResult result;

  if (!g || !a || !b || !is_equal) {
    return kEpidBadArgErr;
  }
  if (!g->ff || !g->ipp_ec || !a->ipp_ec_pt || !b->ipp_ec_pt) {
    return kEpidBadArgErr;
  }
  if (g->ff->element_len != a->element_len ||
      g->ff->element_len != b->element_len) {
    return kEpidBadArgErr;
  }

  sts = ippsGFpECCmpPoint(a->ipp_ec_pt, b->ipp_ec_pt, &result, g->ipp_ec);
  if (ippStsNoErr != sts) {
    if (ippStsContextMatchErr == sts) {
      return kEpidBadArgErr;
    } else {
      return kEpidMathErr;
    }
  }
  *is_equal = ippECPointIsEqual == result;

  return kEpidNoErr;
}

EpidStatus EcIsIdentity(EcGroup* g, EcPoint const* p, bool* is_identity) {
  IppStatus sts;
  IppECResult result;

  if (!g || !p || !is_identity) {
    return kEpidBadArgErr;
  }
  if (!g->ff || !g->ipp_ec || !p->ipp_ec_pt) {
    return kEpidBadArgErr;
  }
  if (g->ff->element_len != p->element_len) {
    return kEpidBadArgErr;
  }

  sts = ippsGFpECTstPoint(p->ipp_ec_pt, &result, g->ipp_ec);
  if (ippStsNoErr != sts) {
    if (ippStsContextMatchErr == sts) {
      return kEpidBadArgErr;
    } else {
      return kEpidMathErr;
    }
  }
  sts = ippsGFpECTstPointInSubgroup(p->ipp_ec_pt, &result, g->ipp_ec,
                                    g->scratch_buffer);
  if (ippStsNoErr != sts) {
    if (ippStsContextMatchErr == sts) {
      return kEpidBadArgErr;
    } else {
      return kEpidMathErr;
    }
  }
  *is_identity = ippECPointIsAtInfinite == result;

  return kEpidNoErr;
}
