int wc_ecc_mulmod_ex(mp_int* k, ecc_point *G, ecc_point *R,
                  mp_int* a, mp_int* modulus, int map,
                  void* heap)
#endif
{
#ifndef WOLFSSL_SP_MATH
#ifndef ECC_TIMING_RESISTANT
   /* size of sliding window, don't change this! */
   #define WINSIZE  4
   #define M_POINTS 8
   int           first = 1, bitbuf = 0, bitcpy = 0, j;
#else
   #define M_POINTS 4
#endif

   ecc_point     *tG, *M[M_POINTS];
   int           i, err;
#ifdef WOLFSSL_SMALL_STACK
   mp_int*       mu = NULL;
#ifdef WOLFSSL_SMALL_STACK_CACHE
   ecc_key       key;
#endif
#else
   mp_int        mu[1];
#endif
   mp_digit      mp;
   mp_digit      buf;
   int           bitcnt = 0, mode = 0, digidx = 0;

   if (k == NULL || G == NULL || R == NULL || modulus == NULL) {
       return ECC_BAD_ARG_E;
   }

   /* init variables */
   tG = NULL;
   XMEMSET(M, 0, sizeof(M));
#ifdef WOLFSSL_SMALL_STACK
   mu = (mp_int*)XMALLOC(sizeof(mp_int), heap, DYNAMIC_TYPE_ECC);
   if (mu == NULL)
       return MEMORY_E;
#endif
#ifdef WOLFSSL_SMALL_STACK_CACHE
   key.t1 = (mp_int*)XMALLOC(sizeof(mp_int), heap, DYNAMIC_TYPE_ECC);
   key.t2 = (mp_int*)XMALLOC(sizeof(mp_int), heap, DYNAMIC_TYPE_ECC);
#ifdef ALT_ECC_SIZE
   key.x = (mp_int*)XMALLOC(sizeof(mp_int), heap, DYNAMIC_TYPE_ECC);
   key.y = (mp_int*)XMALLOC(sizeof(mp_int), heap, DYNAMIC_TYPE_ECC);
   key.z = (mp_int*)XMALLOC(sizeof(mp_int), heap, DYNAMIC_TYPE_ECC);
#endif
   if (key.t1 == NULL || key.t2 == NULL
#ifdef ALT_ECC_SIZE
      || key.x == NULL || key.y == NULL || key.z == NULL
#endif
   ) {
#ifdef ALT_ECC_SIZE
       XFREE(key.z, heap, DYNAMIC_TYPE_ECC);
       XFREE(key.y, heap, DYNAMIC_TYPE_ECC);
       XFREE(key.x, heap, DYNAMIC_TYPE_ECC);
#endif
       XFREE(key.t2, heap, DYNAMIC_TYPE_ECC);
       XFREE(key.t1, heap, DYNAMIC_TYPE_ECC);
       XFREE(mu, heap, DYNAMIC_TYPE_ECC);
       return MEMORY_E;
   }
#endif /* WOLFSSL_SMALL_STACK_CACHE */

   /* init montgomery reduction */
   if ((err = mp_montgomery_setup(modulus, &mp)) != MP_OKAY) {
#ifdef WOLFSSL_SMALL_STACK_CACHE
#ifdef ALT_ECC_SIZE
       XFREE(key.z, heap, DYNAMIC_TYPE_ECC);
       XFREE(key.y, heap, DYNAMIC_TYPE_ECC);
       XFREE(key.x, heap, DYNAMIC_TYPE_ECC);
#endif
       XFREE(key.t2, heap, DYNAMIC_TYPE_ECC);
       XFREE(key.t1, heap, DYNAMIC_TYPE_ECC);
#endif /* WOLFSSL_SMALL_STACK_CACHE */
#ifdef WOLFSSL_SMALL_STACK
       XFREE(mu, heap, DYNAMIC_TYPE_ECC);
#endif
       return err;
   }

   if ((err = mp_init(mu)) != MP_OKAY) {
#ifdef WOLFSSL_SMALL_STACK_CACHE
#ifdef ALT_ECC_SIZE
       XFREE(key.z, heap, DYNAMIC_TYPE_ECC);
       XFREE(key.y, heap, DYNAMIC_TYPE_ECC);
       XFREE(key.x, heap, DYNAMIC_TYPE_ECC);
#endif
       XFREE(key.t2, heap, DYNAMIC_TYPE_ECC);
       XFREE(key.t1, heap, DYNAMIC_TYPE_ECC);
#endif /* WOLFSSL_SMALL_STACK_CACHE */
#ifdef WOLFSSL_SMALL_STACK
       XFREE(mu, heap, DYNAMIC_TYPE_ECC);
#endif
       return err;
   }
   if ((err = mp_montgomery_calc_normalization(mu, modulus)) != MP_OKAY) {
       mp_clear(mu);
#ifdef WOLFSSL_SMALL_STACK_CACHE
#ifdef ALT_ECC_SIZE
       XFREE(key.z, heap, DYNAMIC_TYPE_ECC);
       XFREE(key.y, heap, DYNAMIC_TYPE_ECC);
       XFREE(key.x, heap, DYNAMIC_TYPE_ECC);
#endif
       XFREE(key.t2, heap, DYNAMIC_TYPE_ECC);
       XFREE(key.t1, heap, DYNAMIC_TYPE_ECC);
#endif /* WOLFSSL_SMALL_STACK_CACHE */
#ifdef WOLFSSL_SMALL_STACK
       XFREE(mu, heap, DYNAMIC_TYPE_ECC);
#endif
       return err;
   }

  /* alloc ram for window temps */
  for (i = 0; i < M_POINTS; i++) {
      M[i] = wc_ecc_new_point_h(heap);
      if (M[i] == NULL) {
         mp_clear(mu);
         err = MEMORY_E; goto exit;
      }
#ifdef WOLFSSL_SMALL_STACK_CACHE
      M[i]->key = &key;
#endif
  }

   /* make a copy of G in case R==G */
   tG = wc_ecc_new_point_h(heap);
   if (tG == NULL)
       err = MEMORY_E;

   /* tG = G  and convert to montgomery */
   if (err == MP_OKAY) {
       if (mp_cmp_d(mu, 1) == MP_EQ) {
           err = mp_copy(G->x, tG->x);
           if (err == MP_OKAY)
               err = mp_copy(G->y, tG->y);
           if (err == MP_OKAY)
               err = mp_copy(G->z, tG->z);
       } else {
           err = mp_mulmod(G->x, mu, modulus, tG->x);
           if (err == MP_OKAY)
               err = mp_mulmod(G->y, mu, modulus, tG->y);
           if (err == MP_OKAY)
               err = mp_mulmod(G->z, mu, modulus, tG->z);
       }
   }

   /* done with mu */
   mp_clear(mu);

#ifdef WOLFSSL_SMALL_STACK_CACHE
   R->key = &key;
#endif
#ifndef ECC_TIMING_RESISTANT

   /* calc the M tab, which holds kG for k==8..15 */
   /* M[0] == 8G */
   if (err == MP_OKAY)
       err = ecc_projective_dbl_point(tG, M[0], a, modulus, mp);
   if (err == MP_OKAY)
       err = ecc_projective_dbl_point(M[0], M[0], a, modulus, mp);
   if (err == MP_OKAY)
       err = ecc_projective_dbl_point(M[0], M[0], a, modulus, mp);

   /* now find (8+k)G for k=1..7 */
   if (err == MP_OKAY)
       for (j = 9; j < 16; j++) {
           err = ecc_projective_add_point(M[j-9], tG, M[j-M_POINTS], a, modulus,
                                                                            mp);
           if (err != MP_OKAY) break;
       }

   /* setup sliding window */
   if (err == MP_OKAY) {
       mode   = 0;
       bitcnt = 1;
       buf    = 0;
       digidx = get_digit_count(k) - 1;
       bitcpy = bitbuf = 0;
       first  = 1;

       /* perform ops */
       for (;;) {
           /* grab next digit as required */
           if (--bitcnt == 0) {
               if (digidx == -1) {
                   break;
               }
               buf    = get_digit(k, digidx);
               bitcnt = (int) DIGIT_BIT;
               --digidx;
           }

           /* grab the next msb from the ltiplicand */
           i = (int)(buf >> (DIGIT_BIT - 1)) & 1;
           buf <<= 1;

           /* skip leading zero bits */
           if (mode == 0 && i == 0)
               continue;

           /* if the bit is zero and mode == 1 then we double */
           if (mode == 1 && i == 0) {
               err = ecc_projective_dbl_point(R, R, a, modulus, mp);
               if (err != MP_OKAY) break;
               continue;
           }

           /* else we add it to the window */
           bitbuf |= (i << (WINSIZE - ++bitcpy));
           mode = 2;

           if (bitcpy == WINSIZE) {
               /* if this is the first window we do a simple copy */
               if (first == 1) {
                   /* R = kG [k = first window] */
                   err = mp_copy(M[bitbuf-M_POINTS]->x, R->x);
                   if (err != MP_OKAY) break;

                   err = mp_copy(M[bitbuf-M_POINTS]->y, R->y);
                   if (err != MP_OKAY) break;

                   err = mp_copy(M[bitbuf-M_POINTS]->z, R->z);
                   first = 0;
               } else {
                   /* normal window */
                   /* ok window is filled so double as required and add  */
                   /* double first */
                   for (j = 0; j < WINSIZE; j++) {
                       err = ecc_projective_dbl_point(R, R, a, modulus, mp);
                       if (err != MP_OKAY) break;
                   }
                   if (err != MP_OKAY) break;  /* out of first for(;;) */

                   /* then add, bitbuf will be 8..15 [8..2^WINSIZE] guaranteed */
                   err = ecc_projective_add_point(R, M[bitbuf-M_POINTS], R, a,
                                                                   modulus, mp);
               }
               if (err != MP_OKAY) break;
               /* empty window and reset */
               bitcpy = bitbuf = 0;
               mode = 1;
           }
       }
   }

   /* if bits remain then double/add */
   if (err == MP_OKAY) {
       if (mode == 2 && bitcpy > 0) {
           /* double then add */
           for (j = 0; j < bitcpy; j++) {
               /* only double if we have had at least one add first */
               if (first == 0) {
                   err = ecc_projective_dbl_point(R, R, a, modulus, mp);
                   if (err != MP_OKAY) break;
               }

               bitbuf <<= 1;
               if ((bitbuf & (1 << WINSIZE)) != 0) {
                   if (first == 1) {
                       /* first add, so copy */
                       err = mp_copy(tG->x, R->x);
                       if (err != MP_OKAY) break;

                       err = mp_copy(tG->y, R->y);
                       if (err != MP_OKAY) break;

                       err = mp_copy(tG->z, R->z);
                       if (err != MP_OKAY) break;
                       first = 0;
                   } else {
                       /* then add */
                       err = ecc_projective_add_point(R, tG, R, a, modulus, mp);
                       if (err != MP_OKAY) break;
                   }
               }
           }
       }
   }

   #undef WINSIZE

#else /* ECC_TIMING_RESISTANT */

   /* calc the M tab */
   /* M[0] == G */
   if (err == MP_OKAY)
       err = mp_copy(tG->x, M[0]->x);
   if (err == MP_OKAY)
       err = mp_copy(tG->y, M[0]->y);
   if (err == MP_OKAY)
       err = mp_copy(tG->z, M[0]->z);

   /* M[1] == 2G */
   if (err == MP_OKAY)
       err = ecc_projective_dbl_point(tG, M[1], a, modulus, mp);
#ifdef WC_NO_CACHE_RESISTANT
   if (err == MP_OKAY)
       err = wc_ecc_copy_point(M[0], M[2]);
#endif

   /* setup sliding window */
   mode   = 0;
   bitcnt = 1;
   buf    = 0;
   digidx = get_digit_count(modulus) - 1;
   /* The order MAY be 1 bit longer than the modulus. */
   digidx += (modulus->dp[digidx] >> (DIGIT_BIT-1));

   /* perform ops */
   if (err == MP_OKAY) {
       for (;;) {
           /* grab next digit as required */
           if (--bitcnt == 0) {
               if (digidx == -1) {
                   break;
               }
               buf = get_digit(k, digidx);
               bitcnt = (int)DIGIT_BIT;
               --digidx;
           }

           /* grab the next msb from the multiplicand */
           i = (buf >> (DIGIT_BIT - 1)) & 1;
           buf <<= 1;

#ifdef WC_NO_CACHE_RESISTANT
           if (mode == 0) {
               /* timing resistant - dummy operations */
               if (err == MP_OKAY)
                   err = ecc_projective_add_point(M[1], M[2], M[2], a, modulus,
                                                  mp);
               if (err == MP_OKAY)
                   err = ecc_projective_dbl_point(M[2], M[3], a, modulus, mp);
           }
           else {
               if (err == MP_OKAY)
                   err = ecc_projective_add_point(M[0], M[1], M[i^1], a,
                                                  modulus, mp);
               if (err == MP_OKAY)
                   err = ecc_projective_dbl_point(M[i], M[i], a, modulus, mp);
           }

           mode |= i;
#else
           if (err == MP_OKAY)
               err = ecc_projective_add_point(M[0], M[1], M[2], a, modulus,
                                              mp);
           if (err == MP_OKAY)
               err = mp_copy(M[2]->x,
                             (mp_int*)
                             ( ((size_t)M[0]->x & wc_off_on_addr[mode&(i  )]) +
                               ((size_t)M[1]->x & wc_off_on_addr[mode&(i^1)]) +
                               ((size_t)M[2]->x & wc_off_on_addr[mode^1])) );
           if (err == MP_OKAY)
               err = mp_copy(M[2]->y,
                             (mp_int*)
                             ( ((size_t)M[0]->y & wc_off_on_addr[mode&(i  )]) +
                               ((size_t)M[1]->y & wc_off_on_addr[mode&(i^1)]) +
                               ((size_t)M[2]->y & wc_off_on_addr[mode^1])) );
           if (err == MP_OKAY)
               err = mp_copy(M[2]->z,
                             (mp_int*)
                             ( ((size_t)M[0]->z & wc_off_on_addr[mode&(i  )]) +
                               ((size_t)M[1]->z & wc_off_on_addr[mode&(i^1)]) +
                               ((size_t)M[2]->z & wc_off_on_addr[mode^1])) );

            /* instead of using M[i] for double, which leaks key bit to cache
             * monitor, use M[2] as temp, make sure address calc is constant,
             * keep M[0] and M[1] in cache */
           if (err == MP_OKAY)
               err = mp_copy((mp_int*)
                             ( ((size_t)M[0]->x & wc_off_on_addr[i^1]) +
                               ((size_t)M[1]->x & wc_off_on_addr[i])),
                             M[2]->x);
           if (err == MP_OKAY)
               err = mp_copy((mp_int*)
                             ( ((size_t)M[0]->y & wc_off_on_addr[i^1]) +
                               ((size_t)M[1]->y & wc_off_on_addr[i])),
                             M[2]->y);
           if (err == MP_OKAY)
               err = mp_copy((mp_int*)
                             ( ((size_t)M[0]->z & wc_off_on_addr[i^1]) +
                               ((size_t)M[1]->z & wc_off_on_addr[i])),
                             M[2]->z);
           if (err == MP_OKAY)
               err = ecc_projective_dbl_point(M[2], M[3], a, modulus, mp);
           /* copy M[2] back to M[i] */
           if (err == MP_OKAY)
               err = mp_copy((mp_int*)
                             (((size_t)M[2]->x & wc_off_on_addr[mode^1]) +
                              ((size_t)M[3]->x & wc_off_on_addr[mode])),
                             (mp_int*)
                             ( ((size_t)M[0]->x & wc_off_on_addr[i^1]) +
                               ((size_t)M[1]->x & wc_off_on_addr[i])) );
           if (err == MP_OKAY)
               err = mp_copy((mp_int*)
                             (((size_t)M[2]->y & wc_off_on_addr[mode^1]) +
                              ((size_t)M[3]->y & wc_off_on_addr[mode])),
                             (mp_int*)
                             ( ((size_t)M[0]->y & wc_off_on_addr[i^1]) +
                               ((size_t)M[1]->y & wc_off_on_addr[i])) );
           if (err == MP_OKAY)
               err = mp_copy((mp_int*)
                             (((size_t)M[2]->z & wc_off_on_addr[mode^1]) +
                              ((size_t)M[3]->z & wc_off_on_addr[mode])),
                             (mp_int*)
                             ( ((size_t)M[0]->z & wc_off_on_addr[i^1]) +
                               ((size_t)M[1]->z & wc_off_on_addr[i])) );
           if (err != MP_OKAY)
               break;

           mode |= i;
#endif /* WC_NO_CACHE_RESISTANT */
       } /* end for */
   }

   /* copy result out */
   if (err == MP_OKAY)
       err = mp_copy(M[0]->x, R->x);
   if (err == MP_OKAY)
       err = mp_copy(M[0]->y, R->y);
   if (err == MP_OKAY)
       err = mp_copy(M[0]->z, R->z);

#endif /* ECC_TIMING_RESISTANT */

   /* map R back from projective space */
   if (err == MP_OKAY && map)
       err = ecc_map(R, modulus, mp);

exit:

   /* done */
   wc_ecc_del_point_h(tG, heap);
   for (i = 0; i < M_POINTS; i++) {
       wc_ecc_del_point_h(M[i], heap);
   }
#ifdef WOLFSSL_SMALL_STACK_CACHE
   R->key = NULL;
#ifdef ALT_ECC_SIZE
   XFREE(key.z, heap, DYNAMIC_TYPE_ECC);
   XFREE(key.y, heap, DYNAMIC_TYPE_ECC);
   XFREE(key.x, heap, DYNAMIC_TYPE_ECC);
#endif
   XFREE(key.t2, heap, DYNAMIC_TYPE_ECC);
   XFREE(key.t1, heap, DYNAMIC_TYPE_ECC);
#endif /* WOLFSSL_SMALL_STACK_CACHE */
#ifdef WOLFSSL_SMALL_STACK
   XFREE(mu, heap, DYNAMIC_TYPE_ECC);
#endif

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

   (void)a;

#ifndef WOLFSSL_SP_NO_256
   if (mp_count_bits(modulus) == 256) {
       return sp_ecc_mulmod_256(k, G, R, map, heap);
   }
#endif
#ifdef WOLFSSL_SP_384
   if (mp_count_bits(modulus) == 384) {
       return sp_ecc_mulmod_384(k, G, R, map, heap);
   }
#endif
   return ECC_BAD_ARG_E;
#endif
}