int ecc_projective_add_point(ecc_point* P, ecc_point* Q, ecc_point* R,
                             mp_int* a, mp_int* modulus, mp_digit mp)
{
#ifndef WOLFSSL_SP_MATH
#ifdef WOLFSSL_SMALL_STACK
   mp_int* t1 = NULL;
   mp_int* t2 = NULL;
#ifdef ALT_ECC_SIZE
   mp_int* rx = NULL;
   mp_int* ry = NULL;
   mp_int* rz = NULL;
#endif
#else
   mp_int  t1[1], t2[1];
#ifdef ALT_ECC_SIZE
   mp_int  rx[1], ry[1], rz[1];
#endif
#endif
   mp_int  *x, *y, *z;
   int     err;

   if (P == NULL || Q == NULL || R == NULL || modulus == NULL) {
       return ECC_BAD_ARG_E;
   }

   /* if Q == R then swap P and Q, so we don't require a local x,y,z */
   if (Q == R) {
      ecc_point* tPt  = P;
      P = Q;
      Q = tPt;
   }

#ifdef WOLFSSL_SMALL_STACK
#ifdef WOLFSSL_SMALL_STACK_CACHE
   if (R->key != NULL) {
       t1 = R->key->t1;
       t2 = R->key->t2;
#ifdef ALT_ECC_SIZE
       rx = R->key->x;
       ry = R->key->y;
       rz = R->key->z;
#endif
   }
   else
#endif /* WOLFSSL_SMALL_STACK_CACHE */
   {
       t1 = (mp_int*)XMALLOC(sizeof(mp_int), NULL, DYNAMIC_TYPE_ECC);
       t2 = (mp_int*)XMALLOC(sizeof(mp_int), NULL, DYNAMIC_TYPE_ECC);
       if (t1 == NULL || t2 == NULL) {
           XFREE(t1, NULL, DYNAMIC_TYPE_ECC);
           XFREE(t2, NULL, DYNAMIC_TYPE_ECC);
           return MEMORY_E;
       }
#ifdef ALT_ECC_SIZE
       rx = (mp_int*)XMALLOC(sizeof(mp_int), NULL, DYNAMIC_TYPE_ECC);
       ry = (mp_int*)XMALLOC(sizeof(mp_int), NULL, DYNAMIC_TYPE_ECC);
       rz = (mp_int*)XMALLOC(sizeof(mp_int), NULL, DYNAMIC_TYPE_ECC);
       if (rx == NULL || ry == NULL || rz == NULL) {
           XFREE(rz, NULL, DYNAMIC_TYPE_ECC);
           XFREE(ry, NULL, DYNAMIC_TYPE_ECC);
           XFREE(rx, NULL, DYNAMIC_TYPE_ECC);
           XFREE(t2, NULL, DYNAMIC_TYPE_ECC);
           XFREE(t1, NULL, DYNAMIC_TYPE_ECC);
           return MEMORY_E;
       }
#endif
   }
#endif /* WOLFSSL_SMALL_STACK */

   if ((err = mp_init_multi(t1, t2, NULL, NULL, NULL, NULL)) != MP_OKAY) {
#ifdef WOLFSSL_SMALL_STACK
   #ifdef WOLFSSL_SMALL_STACK_CACHE
       if (R->key == NULL)
   #endif
       {
       #ifdef ALT_ECC_SIZE
          XFREE(rz, NULL, DYNAMIC_TYPE_ECC);
          XFREE(ry, NULL, DYNAMIC_TYPE_ECC);
          XFREE(rx, NULL, DYNAMIC_TYPE_ECC);
       #endif
          XFREE(t2, NULL, DYNAMIC_TYPE_ECC);
          XFREE(t1, NULL, DYNAMIC_TYPE_ECC);
       }
#endif
      return err;
   }

   /* should we dbl instead? */
   if (err == MP_OKAY)
       err = mp_sub(modulus, Q->y, t1);
   if (err == MP_OKAY) {
       if ( (mp_cmp(P->x, Q->x) == MP_EQ) &&
            (get_digit_count(Q->z) && mp_cmp(P->z, Q->z) == MP_EQ) &&
            (mp_cmp(P->y, Q->y) == MP_EQ || mp_cmp(P->y, t1) == MP_EQ)) {
           mp_clear(t1);
           mp_clear(t2);
    #ifdef WOLFSSL_SMALL_STACK
       #ifdef WOLFSSL_SMALL_STACK_CACHE
           if (R->key == NULL)
       #endif
           {
            #ifdef ALT_ECC_SIZE
               XFREE(rz, NULL, DYNAMIC_TYPE_ECC);
               XFREE(ry, NULL, DYNAMIC_TYPE_ECC);
               XFREE(rx, NULL, DYNAMIC_TYPE_ECC);
            #endif
               XFREE(t2, NULL, DYNAMIC_TYPE_ECC);
               XFREE(t1, NULL, DYNAMIC_TYPE_ECC);
           }
        #endif
          return ecc_projective_dbl_point(P, R, a, modulus, mp);
       }
   }

   if (err != MP_OKAY) {
      goto done;
   }

/* If use ALT_ECC_SIZE we need to use local stack variable since
   ecc_point x,y,z is reduced size */
#ifdef ALT_ECC_SIZE
   /* Use local stack variable */
   x = rx;
   y = ry;
   z = rz;

   if ((err = mp_init_multi(x, y, z, NULL, NULL, NULL)) != MP_OKAY) {
      goto done;
   }
#else
   /* Use destination directly */
   x = R->x;
   y = R->y;
   z = R->z;
#endif

   if (err == MP_OKAY)
       err = mp_copy(P->x, x);
   if (err == MP_OKAY)
       err = mp_copy(P->y, y);
   if (err == MP_OKAY)
       err = mp_copy(P->z, z);

   /* if Z is one then these are no-operations */
   if (err == MP_OKAY) {
       if (!mp_iszero(Q->z)) {
           /* T1 = Z' * Z' */
           err = mp_sqr(Q->z, t1);
           if (err == MP_OKAY)
               err = mp_montgomery_reduce(t1, modulus, mp);

           /* X = X * T1 */
           if (err == MP_OKAY)
               err = mp_mul(t1, x, x);
           if (err == MP_OKAY)
               err = mp_montgomery_reduce(x, modulus, mp);

           /* T1 = Z' * T1 */
           if (err == MP_OKAY)
               err = mp_mul(Q->z, t1, t1);
           if (err == MP_OKAY)
               err = mp_montgomery_reduce(t1, modulus, mp);

           /* Y = Y * T1 */
           if (err == MP_OKAY)
               err = mp_mul(t1, y, y);
           if (err == MP_OKAY)
               err = mp_montgomery_reduce(y, modulus, mp);
       }
   }

   /* T1 = Z*Z */
   if (err == MP_OKAY)
       err = mp_sqr(z, t1);
   if (err == MP_OKAY)
       err = mp_montgomery_reduce(t1, modulus, mp);

   /* T2 = X' * T1 */
   if (err == MP_OKAY)
       err = mp_mul(Q->x, t1, t2);
   if (err == MP_OKAY)
       err = mp_montgomery_reduce(t2, modulus, mp);

   /* T1 = Z * T1 */
   if (err == MP_OKAY)
       err = mp_mul(z, t1, t1);
   if (err == MP_OKAY)
       err = mp_montgomery_reduce(t1, modulus, mp);

   /* T1 = Y' * T1 */
   if (err == MP_OKAY)
       err = mp_mul(Q->y, t1, t1);
   if (err == MP_OKAY)
       err = mp_montgomery_reduce(t1, modulus, mp);

   /* Y = Y - T1 */
   if (err == MP_OKAY)
       err = mp_sub(y, t1, y);
   if (err == MP_OKAY) {
       if (mp_isneg(y))
           err = mp_add(y, modulus, y);
   }
   /* T1 = 2T1 */
   if (err == MP_OKAY)
       err = mp_add(t1, t1, t1);
   if (err == MP_OKAY) {
       if (mp_cmp(t1, modulus) != MP_LT)
           err = mp_sub(t1, modulus, t1);
   }
   /* T1 = Y + T1 */
   if (err == MP_OKAY)
       err = mp_add(t1, y, t1);
   if (err == MP_OKAY) {
       if (mp_cmp(t1, modulus) != MP_LT)
           err = mp_sub(t1, modulus, t1);
   }
   /* X = X - T2 */
   if (err == MP_OKAY)
       err = mp_sub(x, t2, x);
   if (err == MP_OKAY) {
       if (mp_isneg(x))
           err = mp_add(x, modulus, x);
   }
   /* T2 = 2T2 */
   if (err == MP_OKAY)
       err = mp_add(t2, t2, t2);
   if (err == MP_OKAY) {
       if (mp_cmp(t2, modulus) != MP_LT)
           err = mp_sub(t2, modulus, t2);
   }
   /* T2 = X + T2 */
   if (err == MP_OKAY)
       err = mp_add(t2, x, t2);
   if (err == MP_OKAY) {
       if (mp_cmp(t2, modulus) != MP_LT)
           err = mp_sub(t2, modulus, t2);
   }

   if (err == MP_OKAY) {
       if (!mp_iszero(Q->z)) {
           /* Z = Z * Z' */
           err = mp_mul(z, Q->z, z);
           if (err == MP_OKAY)
               err = mp_montgomery_reduce(z, modulus, mp);
       }
   }

   /* Z = Z * X */
   if (err == MP_OKAY)
       err = mp_mul(z, x, z);
   if (err == MP_OKAY)
       err = mp_montgomery_reduce(z, modulus, mp);

   /* T1 = T1 * X  */
   if (err == MP_OKAY)
       err = mp_mul(t1, x, t1);
   if (err == MP_OKAY)
       err = mp_montgomery_reduce(t1, modulus, mp);

   /* X = X * X */
   if (err == MP_OKAY)
       err = mp_sqr(x, x);
   if (err == MP_OKAY)
       err = mp_montgomery_reduce(x, modulus, mp);

   /* T2 = T2 * x */
   if (err == MP_OKAY)
       err = mp_mul(t2, x, t2);
   if (err == MP_OKAY)
       err = mp_montgomery_reduce(t2, modulus, mp);

   /* T1 = T1 * X  */
   if (err == MP_OKAY)
       err = mp_mul(t1, x, t1);
   if (err == MP_OKAY)
       err = mp_montgomery_reduce(t1, modulus, mp);

   /* X = Y*Y */
   if (err == MP_OKAY)
       err = mp_sqr(y, x);
   if (err == MP_OKAY)
       err = mp_montgomery_reduce(x, modulus, mp);

   /* X = X - T2 */
   if (err == MP_OKAY)
       err = mp_sub(x, t2, x);
   if (err == MP_OKAY) {
       if (mp_isneg(x))
           err = mp_add(x, modulus, x);
   }
   /* T2 = T2 - X */
   if (err == MP_OKAY)
       err = mp_sub(t2, x, t2);
   if (err == MP_OKAY) {
       if (mp_isneg(t2))
           err = mp_add(t2, modulus, t2);
   }
   /* T2 = T2 - X */
   if (err == MP_OKAY)
       err = mp_sub(t2, x, t2);
   if (err == MP_OKAY) {
       if (mp_isneg(t2))
           err = mp_add(t2, modulus, t2);
   }
   /* T2 = T2 * Y */
   if (err == MP_OKAY)
       err = mp_mul(t2, y, t2);
   if (err == MP_OKAY)
       err = mp_montgomery_reduce(t2, modulus, mp);

   /* Y = T2 - T1 */
   if (err == MP_OKAY)
       err = mp_sub(t2, t1, y);
   if (err == MP_OKAY) {
       if (mp_isneg(y))
           err = mp_add(y, modulus, y);
   }
   /* Y = Y/2 */
   if (err == MP_OKAY) {
       if (mp_isodd(y) == MP_YES)
           err = mp_add(y, modulus, y);
   }
   if (err == MP_OKAY)
       err = mp_div_2(y, y);

#ifdef ALT_ECC_SIZE
   if (err == MP_OKAY)
       err = mp_copy(x, R->x);
   if (err == MP_OKAY)
       err = mp_copy(y, R->y);
   if (err == MP_OKAY)
       err = mp_copy(z, R->z);
#endif

done:

   /* clean up */
   mp_clear(t1);
   mp_clear(t2);
#ifdef WOLFSSL_SMALL_STACK
#ifdef WOLFSSL_SMALL_STACK_CACHE
   if (R->key == NULL)
#endif
   {
   #ifdef ALT_ECC_SIZE
      XFREE(rz, NULL, DYNAMIC_TYPE_ECC);
      XFREE(ry, NULL, DYNAMIC_TYPE_ECC);
      XFREE(rx, NULL, DYNAMIC_TYPE_ECC);
   #endif
      XFREE(t2, NULL, DYNAMIC_TYPE_ECC);
      XFREE(t1, NULL, DYNAMIC_TYPE_ECC);
   }
#endif

   return err;
#else
    if (P == NULL || Q == NULL || R == NULL || modulus == NULL) {
        return ECC_BAD_ARG_E;
    }

    (void)a;
    (void)mp;

#ifndef WOLFSSL_SP_NO_256
    if (mp_count_bits(modulus) == 256) {
        return sp_ecc_proj_add_point_256(P->x, P->y, P->z, Q->x, Q->y, Q->z,
                                         R->x, R->y, R->z);
    }
#endif
#ifdef WOLFSSL_SP_384
    if (mp_count_bits(modulus) == 384) {
        return sp_ecc_proj_add_point_384(P->x, P->y, P->z, Q->x, Q->y, Q->z,
                                         R->x, R->y, R->z);
    }
#endif
    return ECC_BAD_ARG_E;
#endif
}