void ed_mul_sim_joint(ed_t r, const ed_t p, const bn_t k, const ed_t q,
		const bn_t m) {
	ed_t t[5];
	int i, l, u_i, offset;
	int8_t jsf[2 * (RLC_FP_BITS + 1)];

	if (bn_is_zero(k) || ed_is_infty(p)) {
		ed_mul(r, q, m);
		return;
	}
	if (bn_is_zero(m) || ed_is_infty(q)) {
		ed_mul(r, p, k);
		return;
	}

	RLC_TRY {
		for (i = 0; i < 5; i++) {
			ed_null(t[i]);
			ed_new(t[i]);
		}

		ed_set_infty(t[0]);
		ed_copy(t[1], q);
		if (bn_sign(m) == RLC_NEG) {
			ed_neg(t[1], t[1]);
		}
		ed_copy(t[2], p);
		if (bn_sign(k) == RLC_NEG) {
			ed_neg(t[2], t[2]);
		}
		ed_add(t[3], t[2], t[1]);
		ed_sub(t[4], t[2], t[1]);
#if defined(ED_MIXED)
		ed_norm_sim(t + 3, (const ed_t *)t + 3, 2);
#endif

		l = 2 * (RLC_FP_BITS + 1);
		bn_rec_jsf(jsf, &l, k, m);

		ed_set_infty(r);

		offset = RLC_MAX(bn_bits(k), bn_bits(m)) + 1;
		for (i = l - 1; i >= 0; i--) {
			ed_dbl(r, r);
			if (jsf[i] != 0 && jsf[i] == -jsf[i + offset]) {
				u_i = jsf[i] * 2 + jsf[i + offset];
				if (u_i < 0) {
					ed_sub(r, r, t[4]);
				} else {
					ed_add(r, r, t[4]);
				}
			} else {
				u_i = jsf[i] * 2 + jsf[i + offset];
				if (u_i < 0) {
					ed_sub(r, r, t[-u_i]);
				} else {
					ed_add(r, r, t[u_i]);
				}
			}
		}
		ed_norm(r, r);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		for (i = 0; i < 5; i++) {
			ed_free(t[i]);
		}
	}
}


static void ed_mul_sim_plain(ed_t r, const ed_t p, const bn_t k, const ed_t q,
		const bn_t m, const ed_t *t) {
	int i, l, l0, l1, n0, n1, w, gen;
	int8_t naf0[RLC_FP_BITS + 1], naf1[RLC_FP_BITS + 1], *_k, *_m;
	ed_t t0[1 << (ED_WIDTH - 2)];
	ed_t t1[1 << (ED_WIDTH - 2)];

	RLC_TRY {
		gen = (t == NULL ? 0 : 1);
		if (!gen) {
			for (i = 0; i < (1 << (ED_WIDTH - 2)); i++) {
				ed_null(t0[i]);
				ed_new(t0[i]);
			}
			ed_tab(t0, p, ED_WIDTH);
			t = (const ed_t *)t0;
		}

		/* Prepare the precomputation table. */
		for (i = 0; i < (1 << (ED_WIDTH - 2)); i++) {
			ed_null(t1[i]);
			ed_new(t1[i]);
		}
		/* Compute the precomputation table. */
		ed_tab(t1, q, ED_WIDTH);

		/* Compute the w-TNAF representation of k. */
		if (gen) {
			w = ED_DEPTH;
		} else {
			w = ED_WIDTH;
		}
		l0 = l1 = RLC_FP_BITS + 1;
		bn_rec_naf(naf0, &l0, k, w);
		bn_rec_naf(naf1, &l1, m, ED_WIDTH);

		l = RLC_MAX(l0, l1);
		if (bn_sign(k) == RLC_NEG) {
			for (i =  0; i < l0; i++) {
				naf0[i] = -naf0[i];
			}
		}
		if (bn_sign(m) == RLC_NEG) {
			for (i =  0; i < l1; i++) {
				naf1[i] = -naf1[i];
			}
		}

		_k = naf0 + l - 1;
		_m = naf1 + l - 1;
		ed_set_infty(r);
		for (i = l - 1; i >= 0; i--, _k--, _m--) {
			ed_dbl(r, r);

			n0 = *_k;
			n1 = *_m;
			if (n0 > 0) {
				ed_add(r, r, t[n0 / 2]);
			}
			if (n0 < 0) {
				ed_sub(r, r, t[-n0 / 2]);
			}
			if (n1 > 0) {
				ed_add(r, r, t1[n1 / 2]);
			}
			if (n1 < 0) {
				ed_sub(r, r, t1[-n1 / 2]);
			}
		}
		/* Convert r to affine coordinates. */
		ed_norm(r, r);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		/* Free the precomputation tables. */
		if (!gen) {
			for (i = 0; i < 1 << (ED_WIDTH - 2); i++) {
				ed_free(t0[i]);
			}
		}
		for (i = 0; i < 1 << (ED_WIDTH - 2); i++) {
			ed_free(t1[i]);
		}
	}
}

The product performs a calculation that can produce an integer overflow or wraparound, when the logic assumes that the resulting value will always be larger than the original value. This can introduce other weaknesses when the calculation is used for resource management or execution control.


void ed_mul_sim_trick(ed_t r, const ed_t p, const bn_t k, const ed_t q,
		const bn_t m) {
	ed_t t0[1 << (ED_WIDTH / 2)], t1[1 << (ED_WIDTH / 2)], t[1 << ED_WIDTH];
	bn_t n;
	int l0, l1, w = ED_WIDTH / 2;
	uint8_t w0[RLC_FP_BITS + 1], w1[RLC_FP_BITS + 1];

	bn_null(n);

	if (bn_is_zero(k) || ed_is_infty(p)) {
		ed_mul(r, q, m);
		return;
	}
	if (bn_is_zero(m) || ed_is_infty(q)) {
		ed_mul(r, p, k);
		return;
	}

	RLC_TRY {
		bn_new(n);

		ed_curve_get_ord(n);

		for (int i = 0; i < (1 << w); i++) {
			ed_null(t0[i]);
			ed_null(t1[i]);
			ed_new(t0[i]);
			ed_new(t1[i]);
		}
		for (int i = 0; i < (1 << ED_WIDTH); i++) {
			ed_null(t[i]);
			ed_new(t[i]);
		}

		ed_set_infty(t0[0]);
		ed_copy(t0[1], p);
		if (bn_sign(k) == RLC_NEG) {
			ed_neg(t0[1], t0[1]);
		}
		for (int i = 2; i < (1 << w); i++) {
			ed_add(t0[i], t0[i - 1], t0[1]);
		}

		ed_set_infty(t1[0]);
		ed_copy(t1[1], q);
		if (bn_sign(m) == RLC_NEG) {
			ed_neg(t1[1], t1[1]);
		}
		for (int i = 1; i < (1 << w); i++) {
			ed_add(t1[i], t1[i - 1], t1[1]);
		}

		for (int i = 0; i < (1 << w); i++) {
			for (int j = 0; j < (1 << w); j++) {
				ed_add(t[(i << w) + j], t0[i], t1[j]);
			}
		}

#if defined(ED_MIXED)
		ed_norm_sim(t + 1, (const ed_t *)t + 1, (1 << (ED_WIDTH)) - 1);
#endif

		l0 = l1 = RLC_CEIL(RLC_FP_BITS, w);
		bn_rec_win(w0, &l0, k, w);
		bn_rec_win(w1, &l1, m, w);

		for (int i = l0; i < l1; i++) {
			w0[i] = 0;
		}
		for (int i = l1; i < l0; i++) {
			w1[i] = 0;
		}

		ed_set_infty(r);
		for (int i = RLC_MAX(l0, l1) - 1; i >= 0; i--) {
			for (int j = 0; j < w; j++) {
				ed_dbl(r, r);
			}
			ed_add(r, r, t[(w0[i] << w) + w1[i]]);
		}
		ed_norm(r, r);
	} RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(n);
		for (int i = 0; i < (1 << w); i++) {
			ed_free(t0[i]);
			ed_free(t1[i]);
		}
		for (int i = 0; i < (1 << ED_WIDTH); i++) {
			ed_free(t[i]);
		}
	}
}


void ed_read_bin(ed_t a, const uint8_t *bin, int len) {
	if (len == 1) {
		if (bin[0] == 0) {
			ed_set_infty(a);
			return;
		} else {
			RLC_THROW(ERR_NO_BUFFER);
			return;
		}
	}

	if (len != (RLC_FP_BYTES + 1) && len != (2 * RLC_FP_BYTES + 1)) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}

	a->coord = BASIC;
	fp_set_dig(a->z, 1);
	fp_read_bin(a->y, bin + 1, RLC_FP_BYTES);
	if (len == RLC_FP_BYTES + 1) {
		switch (bin[0]) {
			case 2:
				fp_zero(a->x);
				break;
			case 3:
				fp_zero(a->x);
				fp_set_bit(a->x, 0, 1);
				break;
			default:
				RLC_THROW(ERR_NO_VALID);
				break;
		}
		ed_upk(a, a);
	}

	if (len == 2 * RLC_FP_BYTES + 1) {
		if (bin[0] == 4) {
			fp_read_bin(a->x, bin + RLC_FP_BYTES + 1, RLC_FP_BYTES);
		} else {
			RLC_THROW(ERR_NO_VALID);
			return;
		}
	}
#if ED_ADD == EXTND
	fp_mul(a->t, a->x, a->y);
	fp_mul(a->x, a->x, a->z);
	fp_mul(a->y, a->y, a->z);
	fp_sqr(a->z, a->z);
#endif

	if (!ed_on_curve(a)) {
		RLC_THROW(ERR_NO_VALID);
	}
}


void ed_write_bin(uint8_t *bin, int len, const ed_t a, int pack) {
	ed_t t;

	ed_null(t);

	memset(bin, 0, len);

	if (ed_is_infty(a)) {
		if (len < 1) {
			RLC_THROW(ERR_NO_BUFFER);
			return;
		} else {
			return;
		}
	}

	RLC_TRY {
		ed_new(t);

		ed_norm(t, a);

		if (pack) {
			if (len < RLC_FP_BYTES + 1) {
				RLC_THROW(ERR_NO_BUFFER);
			} else {
				ed_pck(t, t);
				bin[0] = 2 | fp_get_bit(t->x, 0);
				fp_write_bin(bin + 1, RLC_FP_BYTES, t->y);
			}
		} else {
			if (len < 2 * RLC_FP_BYTES + 1) {
				RLC_THROW(ERR_NO_BUFFER);
			} else {
				bin[0] = 4;
				fp_write_bin(bin + 1, RLC_FP_BYTES, t->y);
				fp_write_bin(bin + RLC_FP_BYTES + 1, RLC_FP_BYTES, t->x);
			}
		}
	} RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		ed_free(t);
	}
}


void ep_map_dst(ep_t p, const uint8_t *msg, int len, const uint8_t *dst,
		int dst_len) {

	/* enough space for two field elements plus extra bytes for uniformity */
	const int len_per_elm = (FP_PRIME + ep_param_level() + 7) / 8;
	uint8_t *pseudo_random_bytes = RLC_ALLOCA(uint8_t, 2 * len_per_elm);

	RLC_TRY {
		/* for hash_to_field, need to hash to a pseudorandom string */
		/* XXX(rsw) the below assumes that we want to use MD_MAP for hashing.
		 *          Consider making the hash function a per-curve option!
		 */
		md_xmd(pseudo_random_bytes, 2 * len_per_elm, msg, len, dst, dst_len);
		ep_map_from_field(p, pseudo_random_bytes, 2 * len_per_elm);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		RLC_FREE(pseudo_random_bytes);
	}
}


void ep_map(ep_t p, const uint8_t *msg, int len) {
	ep_map_dst(p, msg, len, (const uint8_t *)"RELIC", 5);
}


static void ep_mul_naf_imp(ep_t r, const ep_t p, const bn_t k) {
	int i, l;
	/* Some of the supported prime curves have order > field. */
	int8_t u, naf[RLC_FP_BITS + 2];
	ep_t t[1 << (EP_WIDTH - 2)];
	bn_t _k, n;

	bn_null(n);
	bn_null(_k);

	RLC_TRY {
		bn_new(n);
		bn_new(_k);
		/* Prepare the precomputation table. */
		for (i = 0; i < (1 << (EP_WIDTH - 2)); i++) {
			ep_null(t[i]);
			ep_new(t[i]);
		}

		ep_curve_get_ord(n);
		bn_mod(_k, k, n);

		/* Compute the precomputation table. */
		ep_tab(t, p, EP_WIDTH);

		/* Compute the w-NAF representation of k. */
		l = RLC_FP_BITS + 2;
		bn_rec_naf(naf, &l, _k, EP_WIDTH);

		ep_set_infty(r);
		for (i = l - 1; i >= 0; i--) {
			ep_dbl(r, r);

			u = naf[i];
			if (u > 0) {
				ep_add(r, r, t[u / 2]);
			} else if (u < 0) {
				ep_sub(r, r, t[-u / 2]);
			}
		}
		/* Convert r to affine coordinates. */
		ep_norm(r, r);
		if (bn_sign(_k) == RLC_NEG) {
			ep_neg(r, r);
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(n);
		bn_free(_k);
		/* Free the precomputation table. */
		for (i = 0; i < (1 << (EP_WIDTH - 2)); i++) {
			ep_free(t[i]);
		}
	}
}


void ep_mul_slide(ep_t r, const ep_t p, const bn_t k) {
	bn_t _k, n;
	ep_t t[1 << (EP_WIDTH - 1)], q;
	int i, j, l;
	uint8_t win[RLC_FP_BITS + 1];

	if (bn_is_zero(k) || ep_is_infty(p)) {
		ep_set_infty(r);
		return;
	}

	ep_null(q);
	bn_null(n);
	bn_null(_k);

	RLC_TRY {
		bn_new(n);
		bn_new(_k);
		for (i = 0; i < (1 << (EP_WIDTH - 1)); i ++) {
			ep_null(t[i]);
			ep_new(t[i]);
		}
		ep_new(q);

		ep_copy(t[0], p);
		ep_dbl(q, p);

#if defined(EP_MIXED)
		ep_norm(q, q);
#endif

		ep_curve_get_ord(n);
		bn_mod(_k, k, n);

		/* Create table. */
		for (i = 1; i < (1 << (EP_WIDTH - 1)); i++) {
			ep_add(t[i], t[i - 1], q);
		}

#if defined(EP_MIXED)
		ep_norm_sim(t + 1, (const ep_t *)t + 1, (1 << (EP_WIDTH - 1)) - 1);
#endif

		ep_set_infty(q);
		l = RLC_FP_BITS + 1;
		bn_rec_slw(win, &l, _k, EP_WIDTH);
		for (i = 0; i < l; i++) {
			if (win[i] == 0) {
				ep_dbl(q, q);
			} else {
				for (j = 0; j < util_bits_dig(win[i]); j++) {
					ep_dbl(q, q);
				}
				ep_add(q, q, t[win[i] >> 1]);
			}
		}

		ep_norm(r, q);
		if (bn_sign(_k) == RLC_NEG) {
			ep_neg(r, r);
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(n);
		bn_free(_k);
		for (i = 0; i < (1 << (EP_WIDTH - 1)); i++) {
			ep_free(t[i]);
		}
		ep_free(q);
	}
}


void ep_mul_monty(ep_t r, const ep_t p, const bn_t k) {
	int i, j, bits;
	ep_t t[2];
	bn_t n, l, _k;

	bn_null(n);
	bn_null(l);
	bn_null(_k);
	ep_null(t[0]);
	ep_null(t[1]);

	if (bn_is_zero(k) || ep_is_infty(p)) {
		ep_set_infty(r);
		return;
	}

	RLC_TRY {
		bn_new(n);
		bn_new(l);
		bn_new(_k);
		ep_new(t[0]);
		ep_new(t[1]);

		ep_curve_get_ord(n);
		bits = bn_bits(n);

		bn_mod(_k, k, n);
		bn_abs(l, _k);
		bn_add(l, l, n);
		bn_add(n, l, n);
		dv_swap_cond(l->dp, n->dp, RLC_MAX(l->used, n->used),
			bn_get_bit(l, bits) == 0);
		l->used = RLC_SEL(l->used, n->used, bn_get_bit(l, bits) == 0);

		ep_norm(t[0], p);
		ep_dbl(t[1], t[0]);

		/* Blind both points independently. */
		ep_blind(t[0], t[0]);
		ep_blind(t[1], t[1]);

		for (i = bits - 1; i >= 0; i--) {
			j = bn_get_bit(l, i);
			dv_swap_cond(t[0]->x, t[1]->x, RLC_FP_DIGS, j ^ 1);
			dv_swap_cond(t[0]->y, t[1]->y, RLC_FP_DIGS, j ^ 1);
			dv_swap_cond(t[0]->z, t[1]->z, RLC_FP_DIGS, j ^ 1);
			ep_add(t[0], t[0], t[1]);
			ep_dbl(t[1], t[1]);
			dv_swap_cond(t[0]->x, t[1]->x, RLC_FP_DIGS, j ^ 1);
			dv_swap_cond(t[0]->y, t[1]->y, RLC_FP_DIGS, j ^ 1);
			dv_swap_cond(t[0]->z, t[1]->z, RLC_FP_DIGS, j ^ 1);
		}

		ep_norm(r, t[0]);
		ep_neg(t[0], r);
		dv_copy_cond(r->y, t[0]->y, RLC_FP_DIGS, bn_sign(_k) == RLC_NEG);
	} RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(n);
		bn_free(l);
		bn_free(_k);
		ep_free(t[1]);
		ep_free(t[0]);
	}
}


void ep_mul_dig(ep_t r, const ep_t p, dig_t k) {
	ep_t t;
	bn_t _k;
	int8_t u, naf[RLC_DIG + 1];
	int l;

	ep_null(t);
	bn_null(_k);

	if (k == 0 || ep_is_infty(p)) {
		ep_set_infty(r);
		return;
	}

	RLC_TRY {
		ep_new(t);
		bn_new(_k);

		bn_set_dig(_k, k);

		l = RLC_DIG + 1;
		bn_rec_naf(naf, &l, _k, 2);

		ep_set_infty(t);
		for (int i = l - 1; i >= 0; i--) {
			ep_dbl(t, t);

			u = naf[i];
			if (u > 0) {
				ep_add(t, t, p);
			} else if (u < 0) {
				ep_sub(t, t, p);
			}
		}

		ep_norm(r, t);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		ep_free(t);
		bn_free(_k);
	}
}


static void ep_mul_glv_imp(ep_t r, const ep_t p, const bn_t k) {
	int l, l0, l1, i, n0, n1, s0, s1;
	int8_t naf0[RLC_FP_BITS + 1], naf1[RLC_FP_BITS + 1], *t0, *t1;
	bn_t n, _k, k0, k1, v1[3], v2[3];
	ep_t q, t[1 << (EP_WIDTH - 2)];

	bn_null(n);
	bn_null(_k);
	bn_null(k0);
	bn_null(k1);
	ep_null(q);

	RLC_TRY {
		bn_new(n);
		bn_new(_k);
		bn_new(k0);
		bn_new(k1);
		ep_new(q);
		for (i = 0; i < (1 << (EP_WIDTH - 2)); i++) {
			ep_null(t[i]);
			ep_new(t[i]);
		}
		for (i = 0; i < 3; i++) {
			bn_null(v1[i]);
			bn_null(v2[i]);
			bn_new(v1[i]);
			bn_new(v2[i]);
		}

		ep_curve_get_ord(n);
		ep_curve_get_v1(v1);
		ep_curve_get_v2(v2);

		bn_mod(_k, k, n);
		bn_rec_glv(k0, k1, _k, n, (const bn_t *)v1, (const bn_t *)v2);
		s0 = bn_sign(k0);
		s1 = bn_sign(k1);
		bn_abs(k0, k0);
		bn_abs(k1, k1);

		if (s0 == RLC_POS) {
			ep_tab(t, p, EP_WIDTH);
		} else {
			ep_neg(q, p);
			ep_tab(t, q, EP_WIDTH);
		}

		l0 = l1 = RLC_FP_BITS + 1;
		bn_rec_naf(naf0, &l0, k0, EP_WIDTH);
		bn_rec_naf(naf1, &l1, k1, EP_WIDTH);

		l = RLC_MAX(l0, l1);
		t0 = naf0 + l - 1;
		t1 = naf1 + l - 1;

		ep_set_infty(r);
		for (i = l - 1; i >= 0; i--, t0--, t1--) {
			ep_dbl(r, r);

			n0 = *t0;
			n1 = *t1;
			if (n0 > 0) {
				ep_add(r, r, t[n0 / 2]);
			}
			if (n0 < 0) {
				ep_sub(r, r, t[-n0 / 2]);
			}
			if (n1 > 0) {
				ep_psi(q, t[n1 / 2]);
				if (s0 != s1) {
					ep_neg(q, q);
				}
				ep_add(r, r, q);
			}
			if (n1 < 0) {
				ep_psi(q, t[-n1 / 2]);
				if (s0 != s1) {
					ep_neg(q, q);
				}
				ep_sub(r, r, q);
			}
		}
		/* Convert r to affine coordinates. */
		ep_norm(r, r);
		if (bn_sign(_k) == RLC_NEG) {
			ep_neg(r, r);
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(n);
		bn_free(_k);
		bn_free(k0);
		bn_free(k1);
		bn_free(n)
		ep_free(q);
		for (i = 0; i < 1 << (EP_WIDTH - 2); i++) {
			ep_free(t[i]);
		}
		for (i = 0; i < 3; i++) {
			bn_free(v1[i]);
			bn_free(v2[i]);
		}

	}
}


static void ep_mul_reg_imp(ep_t r, const ep_t p, const bn_t k) {
	bn_t _k;
	int i, j, l, n;
	int8_t s, reg[1 + RLC_CEIL(RLC_FP_BITS + 1, EP_WIDTH - 1)];
	ep_t t[1 << (EP_WIDTH - 2)], u, v;

	if (bn_is_zero(k)) {
		ep_set_infty(r);
		return;
	}

	bn_null(_k);

	RLC_TRY {
		bn_new(_k);
		ep_new(u);
		ep_new(v);
		/* Prepare the precomputation table. */
		for (i = 0; i < (1 << (EP_WIDTH - 2)); i++) {
			ep_null(t[i]);
			ep_new(t[i]);
		}
		/* Compute the precomputation table. */
		ep_tab(t, p, EP_WIDTH);

		ep_curve_get_ord(_k);
		n = bn_bits(_k);

		/* Make a copy of the scalar for processing. */
		bn_abs(_k, k);
		_k->dp[0] |= 1;

		/* Compute the regular w-NAF representation of k. */
		l = RLC_CEIL(n, EP_WIDTH - 1) + 1;
		bn_rec_reg(reg, &l, _k, n, EP_WIDTH);

#if defined(EP_MIXED)
		fp_set_dig(u->z, 1);
		u->coord = BASIC;
#else
		u->coord = EP_ADD;
#endif
		ep_set_infty(r);
		for (i = l - 1; i >= 0; i--) {
			for (j = 0; j < EP_WIDTH - 1; j++) {
				ep_dbl(r, r);
			}

			n = reg[i];
			s = (n >> 7);
			n = ((n ^ s) - s) >> 1;

			for (j = 0; j < (1 << (EP_WIDTH - 2)); j++) {
				dv_copy_cond(u->x, t[j]->x, RLC_FP_DIGS, j == n);
				dv_copy_cond(u->y, t[j]->y, RLC_FP_DIGS, j == n);
#if !defined(EP_MIXED)
				dv_copy_cond(u->z, t[j]->z, RLC_FP_DIGS, j == n);
#endif
			}
			ep_neg(v, u);
			dv_copy_cond(u->y, v->y, RLC_FP_DIGS, s != 0);
			ep_add(r, r, u);
		}
		/* t[0] has an unmodified copy of p. */
		ep_sub(u, r, t[0]);
		dv_copy_cond(r->x, u->x, RLC_FP_DIGS, bn_is_even(k));
		dv_copy_cond(r->y, u->y, RLC_FP_DIGS, bn_is_even(k));
		dv_copy_cond(r->z, u->z, RLC_FP_DIGS, bn_is_even(k));
		/* Convert r to affine coordinates. */
		ep_norm(r, r);
		ep_neg(u, r);
		dv_copy_cond(r->y, u->y, RLC_FP_DIGS, bn_sign(k) == RLC_NEG);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		/* Free the precomputation table. */
		for (i = 0; i < (1 << (EP_WIDTH - 2)); i++) {
			ep_free(t[i]);
		}
		bn_free(_k);
		ep_free(u);
		ep_free(v);
	}
}


static void ep_mul_fix_plain(ep_t r, const ep_t *t, const bn_t k) {
	int l, i, n;
	int8_t naf[RLC_FP_BITS + 1];

	/* Compute the w-TNAF representation of k. */
	l = RLC_FP_BITS + 1;
	bn_rec_naf(naf, &l, k, EP_DEPTH);

	n = naf[l - 1];
	if (n > 0) {
		ep_copy(r, t[n / 2]);
	} else {
		ep_neg(r, t[-n / 2]);
	}

	for (i = l - 2; i >= 0; i--) {
		ep_dbl(r, r);

		n = naf[i];
		if (n > 0) {
			ep_add(r, r, t[n / 2]);
		}
		if (n < 0) {
			ep_sub(r, r, t[-n / 2]);
		}
	}
	/* Convert r to affine coordinates. */
	ep_norm(r, r);
	if (bn_sign(k) == RLC_NEG) {
		ep_neg(r, r);
	}
}


static void ep_mul_sim_plain(ep_t r, const ep_t p, const bn_t k, const ep_t q,
		const bn_t m, const ep_t *t) {
	int i, l, l0, l1, w, gen;
	int8_t naf0[RLC_FP_BITS + 1], naf1[RLC_FP_BITS + 1], n0, n1, *u, *v;
	ep_t t0[1 << (EP_WIDTH - 2)];
	ep_t t1[1 << (EP_WIDTH - 2)];

	RLC_TRY {
		gen = (t == NULL ? 0 : 1);
		if (!gen) {
			for (i = 0; i < (1 << (EP_WIDTH - 2)); i++) {
				ep_null(t0[i]);
				ep_new(t0[i]);
			}
			ep_tab(t0, p, EP_WIDTH);
			t = (const ep_t *)t0;
		}

		/* Prepare the precomputation table. */
		for (i = 0; i < (1 << (EP_WIDTH - 2)); i++) {
			ep_null(t1[i]);
			ep_new(t1[i]);
		}

		/* Compute the precomputation table. */
		ep_tab(t1, q, EP_WIDTH);

		/* Compute the w-TNAF representation of k. */
		if (gen) {
			w = EP_DEPTH;
		} else {
			w = EP_WIDTH;
		}
		l0 = l1 = RLC_FP_BITS + 1;
		bn_rec_naf(naf0, &l0, k, w);
		bn_rec_naf(naf1, &l1, m, EP_WIDTH);

		l = RLC_MAX(l0, l1);
		if (bn_sign(k) == RLC_NEG) {
			for (i =  0; i < l0; i++) {
				naf0[i] = -naf0[i];
			}
		}
		if (bn_sign(m) == RLC_NEG) {
			for (i =  0; i < l1; i++) {
				naf1[i] = -naf1[i];
			}
		}

		u = naf0 + l - 1;
		v = naf1 + l - 1;
		ep_set_infty(r);
		for (i = l - 1; i >= 0; i--, u--, v--) {
			ep_dbl(r, r);

			n0 = *u;
			n1 = *v;
			if (n0 > 0) {
				ep_add(r, r, t[n0 / 2]);
			}
			if (n0 < 0) {
				ep_sub(r, r, t[-n0 / 2]);
			}
			if (n1 > 0) {
				ep_add(r, r, t1[n1 / 2]);
			}
			if (n1 < 0) {
				ep_sub(r, r, t1[-n1 / 2]);
			}
		}
		/* Convert r to affine coordinates. */
		ep_norm(r, r);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		/* Free the precomputation tables. */
		if (!gen) {
			for (i = 0; i < 1 << (EP_WIDTH - 2); i++) {
				ep_free(t0[i]);
			}
		}
		for (i = 0; i < 1 << (EP_WIDTH - 2); i++) {
			ep_free(t1[i]);
		}
	}
}


void ep_mul_sim_joint(ep_t r, const ep_t p, const bn_t k, const ep_t q,
		const bn_t m) {
	bn_t n, _k, _m;
	ep_t t[5];
	int i, l, u_i, offset;
	int8_t jsf[2 * (RLC_FP_BITS + 1)];

	if (bn_is_zero(k) || ep_is_infty(p)) {
		ep_mul(r, q, m);
		return;
	}
	if (bn_is_zero(m) || ep_is_infty(q)) {
		ep_mul(r, p, k);
		return;
	}

	bn_null(n);
	bn_null(_k);
	bn_null(_m);

	RLC_TRY {
		bn_new(n);
		bn_new(_k);
		bn_new(_m);
		for (i = 0; i < 5; i++) {
			ep_null(t[i]);
			ep_new(t[i]);
		}

		ep_curve_get_ord(n);
		bn_mod(_k, k, n);
		bn_mod(_m, m, n);

		ep_set_infty(t[0]);
		ep_copy(t[1], q);
		if (bn_sign(_m) == RLC_NEG) {
			ep_neg(t[1], t[1]);
		}
		ep_copy(t[2], p);
		if (bn_sign(_k) == RLC_NEG) {
			ep_neg(t[2], t[2]);
		}
		ep_add(t[3], t[2], t[1]);
		ep_sub(t[4], t[2], t[1]);
#if defined(EP_MIXED)
		ep_norm_sim(t + 3, (const ep_t *)t + 3, 2);
#endif

		l = 2 * (RLC_FP_BITS + 1);
		bn_rec_jsf(jsf, &l, _k, _m);

		ep_set_infty(r);

		offset = RLC_MAX(bn_bits(_k), bn_bits(_m)) + 1;
		for (i = l - 1; i >= 0; i--) {
			ep_dbl(r, r);
			if (jsf[i] != 0 && jsf[i] == -jsf[i + offset]) {
				u_i = jsf[i] * 2 + jsf[i + offset];
				if (u_i < 0) {
					ep_sub(r, r, t[4]);
				} else {
					ep_add(r, r, t[4]);
				}
			} else {
				u_i = jsf[i] * 2 + jsf[i + offset];
				if (u_i < 0) {
					ep_sub(r, r, t[-u_i]);
				} else {
					ep_add(r, r, t[u_i]);
				}
			}
		}
		ep_norm(r, r);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(n);
		bn_free(_k);
		bn_free(_m);
		for (i = 0; i < 5; i++) {
			ep_free(t[i]);
		}
	}
}


void ep_mul_sim_trick(ep_t r, const ep_t p, const bn_t k, const ep_t q,
		const bn_t m) {
	ep_t t0[1 << (EP_WIDTH / 2)], t1[1 << (EP_WIDTH / 2)], t[1 << EP_WIDTH];
	bn_t n, _k, _m;
	int l0, l1, w = EP_WIDTH / 2;
	uint8_t w0[RLC_FP_BITS + 1], w1[RLC_FP_BITS + 1];

	if (bn_is_zero(k) || ep_is_infty(p)) {
		ep_mul(r, q, m);
		return;
	}
	if (bn_is_zero(m) || ep_is_infty(q)) {
		ep_mul(r, p, k);
		return;
	}

	bn_null(n);
	bn_null(_k);
	bn_null(_m);

	RLC_TRY {
		bn_new(n);
		bn_new(_k);
		bn_new(_m);

		for (int i = 0; i < (1 << w); i++) {
			ep_null(t0[i]);
			ep_null(t1[i]);
			ep_new(t0[i]);
			ep_new(t1[i]);
		}
		for (int i = 0; i < (1 << EP_WIDTH); i++) {
			ep_null(t[i]);
			ep_new(t[i]);
		}

		ep_curve_get_ord(n);
		bn_mod(_k, k, n);
		bn_mod(_m, m, n);

		ep_set_infty(t0[0]);
		ep_copy(t0[1], p);
		if (bn_sign(_k) == RLC_NEG) {
			ep_neg(t0[1], t0[1]);
		}
		for (int i = 2; i < (1 << w); i++) {
			ep_add(t0[i], t0[i - 1], t0[1]);
		}

		ep_set_infty(t1[0]);
		ep_copy(t1[1], q);
		if (bn_sign(_m) == RLC_NEG) {
			ep_neg(t1[1], t1[1]);
		}
		for (int i = 2; i < (1 << w); i++) {
			ep_add(t1[i], t1[i - 1], t1[1]);
		}

		for (int i = 0; i < (1 << w); i++) {
			for (int j = 0; j < (1 << w); j++) {
				ep_add(t[(i << w) + j], t0[i], t1[j]);
			}
		}

#if EP_WIDTH > 2 && defined(EP_MIXED)
		ep_norm_sim(t + 1, (const ep_t *)(t + 1), (1 << EP_WIDTH) - 1);
#endif

		l0 = l1 = RLC_CEIL(RLC_FP_BITS + 1, w);
		bn_rec_win(w0, &l0, _k, w);
		bn_rec_win(w1, &l1, _m, w);
		for (int i = l0; i < l1; i++) {
			w0[i] = 0;
		}
		for (int i = l1; i < l0; i++) {
			w1[i] = 0;
		}

		ep_set_infty(r);
		for (int i = RLC_MAX(l0, l1) - 1; i >= 0; i--) {
			for (int j = 0; j < w; j++) {
				ep_dbl(r, r);
			}
			ep_add(r, r, t[(w0[i] << w) + w1[i]]);
		}
		ep_norm(r, r);
	} RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(n);
		bn_free(_k);
		bn_free(_m);
		for (int i = 0; i < (1 << w); i++) {
			ep_free(t0[i]);
			ep_free(t1[i]);
		}
		for (int i = 0; i < (1 << EP_WIDTH); i++) {
			ep_free(t[i]);
		}
	}
}


void ep_mul_sim_lot_plain(ep_t r, const ep_t p[], const bn_t k[], int n) {
	int i, j, l, *_l = RLC_ALLOCA(int, n);
	ep_t *_p = RLC_ALLOCA(ep_t, n);
	int8_t *naf = NULL;

	RLC_TRY {
		l = 0;
		for (i = 0; i < n; i++) {
			l = RLC_MAX(l, bn_bits(k[i]) + 1);
		}
		naf = RLC_ALLOCA(int8_t, n * l);
		if (naf == NULL || _p == NULL || _l == NULL) {
			RLC_THROW(ERR_NO_MEMORY);
		}

		for (i = 0; i < n; i++) {
			ep_null(_p[i]);
			ep_new(_p[i]);
		}

		for (i = 0; i < n; i++) {
			_l[i] = l;
			ep_norm(_p[i], p[i]);
			bn_rec_naf(&naf[i*l], &_l[i], k[i], 2);
			if (bn_sign(k[i]) == RLC_NEG) {
				ep_neg(_p[i], _p[i]);
			}
		}

		ep_set_infty(r);
		for (i = l - 1; i >= 0; i--) {
			ep_dbl(r, r);
			for (j = 0; j < n; j++) {
				if (naf[j*l + i] > 0) {
					ep_add(r, r, _p[j]);
				}
				if (naf[j*l + i] < 0) {
					ep_sub(r, r, _p[j]);
				}
			}
		}

		/* Convert r to affine coordinates. */
		ep_norm(r, r);
	} RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	} RLC_FINALLY {
		for (i = 0; i < n; i++) {
			ep_free(_p[i]);
		}
		RLC_FREE(_l);
		RLC_FREE(_p);
		RLC_FREE(naf);
	}
}


void ep_write_bin(uint8_t *bin, int len, const ep_t a, int pack) {
	ep_t t;

	ep_null(t);

	memset(bin, 0, len);

	if (ep_is_infty(a)) {
		if (len < 1) {
			RLC_THROW(ERR_NO_BUFFER);
			return;
		} else {
			return;
		}
	}

	RLC_TRY {
		ep_new(t);

		ep_norm(t, a);

		if (pack) {
			if (len < RLC_FP_BYTES + 1) {
				RLC_THROW(ERR_NO_BUFFER);
			} else {
				ep_pck(t, t);
				bin[0] = 2 | fp_get_bit(t->y, 0);
				fp_write_bin(bin + 1, RLC_FP_BYTES, t->x);
			}
		} else {
			if (len < 2 * RLC_FP_BYTES + 1) {
				RLC_THROW(ERR_NO_BUFFER);
			} else {
				bin[0] = 4;
				fp_write_bin(bin + 1, RLC_FP_BYTES, t->x);
				fp_write_bin(bin + RLC_FP_BYTES + 1, RLC_FP_BYTES, t->y);
			}
		}
	} RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		ep_free(t);
	}
}


void ep_read_bin(ep_t a, const uint8_t *bin, int len) {
	if (len == 1) {
		if (bin[0] == 0) {
			ep_set_infty(a);
			return;
		} else {
			RLC_THROW(ERR_NO_BUFFER);
			return;
		}
	}

	if (len != (RLC_FP_BYTES + 1) && len != (2 * RLC_FP_BYTES + 1)) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}

	a->coord = BASIC;
	fp_set_dig(a->z, 1);
	fp_read_bin(a->x, bin + 1, RLC_FP_BYTES);
	if (len == RLC_FP_BYTES + 1) {
		switch(bin[0]) {
			case 2:
				fp_zero(a->y);
				break;
			case 3:
				fp_zero(a->y);
				fp_set_bit(a->y, 0, 1);
				break;
			default:
				RLC_THROW(ERR_NO_VALID);
				break;
		}
		ep_upk(a, a);
	}

	if (len == 2 * RLC_FP_BYTES + 1) {
		if (bin[0] == 4) {
			fp_read_bin(a->y, bin + RLC_FP_BYTES + 1, RLC_FP_BYTES);
		} else {
			RLC_THROW(ERR_NO_VALID);
			return;
		}
	}

	if (!ep_on_curve(a)) {
		RLC_THROW(ERR_NO_VALID);
		return;
	}
}


void ep2_map_dst(ep2_t p, const uint8_t *msg, int len, const uint8_t *dst, int dst_len) {

        /* enough space for two field elements plus extra bytes for uniformity */
        const int len_per_elm = (FP_PRIME + ep_param_level() + 7) / 8;
        uint8_t *pseudo_random_bytes = RLC_ALLOCA(uint8_t, 4 * len_per_elm);

        RLC_TRY {

                /* XXX(rsw) See note in ep/relic_ep_map.c about using MD_MAP. */
                /* hash to a pseudorandom string using md_xmd */
                md_xmd(pseudo_random_bytes, 4 * len_per_elm, msg, len, dst, dst_len);
                ep2_map_from_field(p, pseudo_random_bytes, 2 * len_per_elm);
        }
        RLC_CATCH_ANY {
                RLC_THROW(ERR_CAUGHT);
        }
        RLC_FINALLY {
                RLC_FREE(pseudo_random_bytes);
        }
}


void ep2_map(ep2_t p, const uint8_t *msg, int len) {
	ep2_map_dst(p, msg, len, (const uint8_t *)"RELIC", 5);
}


void ep2_mul_dig(ep2_t r, const ep2_t p, const dig_t k) {
	ep2_t t;
	bn_t _k;
	int8_t u, naf[RLC_DIG + 1];
	int l;

	ep2_null(t);
	bn_null(_k);

	if (k == 0 || ep2_is_infty(p)) {
		ep2_set_infty(r);
		return;
	}

	RLC_TRY {
		ep2_new(t);
		bn_new(_k);

		bn_set_dig(_k, k);

		l = RLC_DIG + 1;
		bn_rec_naf(naf, &l, _k, 2);

		ep2_set_infty(t);
		for (int i = l - 1; i >= 0; i--) {
			ep2_dbl(t, t);

			u = naf[i];
			if (u > 0) {
				ep2_add(t, t, p);
			} else if (u < 0) {
				ep2_sub(t, t, p);
			}
		}

		ep2_norm(r, t);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		ep2_free(t);
		bn_free(_k);
	}
}


void ep2_mul_basic(ep2_t r, const ep2_t p, const bn_t k) {
	int i, l;
	ep2_t t;

	ep2_null(t);

	if (bn_is_zero(k) || ep2_is_infty(p)) {
		ep2_set_infty(r);
		return;
	}

	RLC_TRY {
		ep2_new(t);
		l = bn_bits(k);

		if (bn_get_bit(k, l - 1)) {
			ep2_copy(t, p);
		} else {
			ep2_set_infty(t);
		}

		for (i = l - 2; i >= 0; i--) {
			ep2_dbl(t, t);
			if (bn_get_bit(k, i)) {
				ep2_add(t, t, p);
			}
		}

		ep2_copy(r, t);
		ep2_norm(r, r);
		if (bn_sign(k) == RLC_NEG) {
			ep2_neg(r, r);
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		ep2_free(t);
	}
}


void ep2_mul_slide(ep2_t r, const ep2_t p, const bn_t k) {
	ep2_t t[1 << (EP_WIDTH - 1)], q;
	int i, j, l;
	uint8_t win[RLC_FP_BITS + 1];

	ep2_null(q);

	if (bn_is_zero(k) || ep2_is_infty(p)) {
		ep2_set_infty(r);
		return;
	}

	RLC_TRY {
		for (i = 0; i < (1 << (EP_WIDTH - 1)); i ++) {
			ep2_null(t[i]);
			ep2_new(t[i]);
		}

		ep2_new(q);

		ep2_copy(t[0], p);
		ep2_dbl(q, p);

#if defined(EP_MIXED)
		ep2_norm(q, q);
#endif

		/* Create table. */
		for (i = 1; i < (1 << (EP_WIDTH - 1)); i++) {
			ep2_add(t[i], t[i - 1], q);
		}

#if defined(EP_MIXED)
		ep2_norm_sim(t + 1, t + 1, (1 << (EP_WIDTH - 1)) - 1);
#endif

		ep2_set_infty(q);
		l = RLC_FP_BITS + 1;
		bn_rec_slw(win, &l, k, EP_WIDTH);
		for (i = 0; i < l; i++) {
			if (win[i] == 0) {
				ep2_dbl(q, q);
			} else {
				for (j = 0; j < util_bits_dig(win[i]); j++) {
					ep2_dbl(q, q);
				}
				ep2_add(q, q, t[win[i] >> 1]);
			}
		}

		ep2_norm(r, q);
		if (bn_sign(k) == RLC_NEG) {
			ep2_neg(r, r);
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		for (i = 0; i < (1 << (EP_WIDTH - 1)); i++) {
			ep2_free(t[i]);
		}
		ep2_free(q);
	}
}


static void ep2_mul_naf_imp(ep2_t r, const ep2_t p, const bn_t k) {
	int l, i, n;
	int8_t naf[RLC_FP_BITS + 1];
	ep2_t t[1 << (EP_WIDTH - 2)];

	RLC_TRY {
		/* Prepare the precomputation table. */
		for (i = 0; i < (1 << (EP_WIDTH - 2)); i++) {
			ep2_null(t[i]);
			ep2_new(t[i]);
		}
		/* Compute the precomputation table. */
		ep2_tab(t, p, EP_WIDTH);

		/* Compute the w-NAF representation of k. */
		l = sizeof(naf);
		bn_rec_naf(naf, &l, k, EP_WIDTH);

		ep2_set_infty(r);
		for (i = l - 1; i >= 0; i--) {
			ep2_dbl(r, r);

			n = naf[i];
			if (n > 0) {
				ep2_add(r, r, t[n / 2]);
			}
			if (n < 0) {
				ep2_sub(r, r, t[-n / 2]);
			}
		}
		/* Convert r to affine coordinates. */
		ep2_norm(r, r);
		if (bn_sign(k) == RLC_NEG) {
			ep2_neg(r, r);
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		/* Free the precomputation table. */
		for (i = 0; i < (1 << (EP_WIDTH - 2)); i++) {
			ep2_free(t[i]);
		}
	}
}


static void ep2_mul_glv_imp(ep2_t r, const ep2_t p, const bn_t k) {
	int i, j, l, _l[4];
	bn_t n, _k[4], u;
	int8_t naf[4][RLC_FP_BITS + 1];
	ep2_t q[4];

	bn_null(n);
	bn_null(u);

	RLC_TRY {
		bn_new(n);
		bn_new(u);
		for (i = 0; i < 4; i++) {
			bn_null(_k[i]);
			ep2_null(q[i]);
			bn_new(_k[i]);
			ep2_new(q[i]);
		}

		ep2_curve_get_ord(n);
		fp_prime_get_par(u);
		bn_mod(_k[0], k, n);
		bn_rec_frb(_k, 4, _k[0], u, n, ep_curve_is_pairf() == EP_BN);

		ep2_norm(q[0], p);
		ep2_frb(q[1], q[0], 1);
		ep2_frb(q[2], q[1], 1);
		ep2_frb(q[3], q[2], 1);

		l = 0;
		for (i = 0; i < 4; i++) {
			if (bn_sign(_k[i]) == RLC_NEG) {
				ep2_neg(q[i], q[i]);
			}
			_l[i] = RLC_FP_BITS + 1;
			bn_rec_naf(naf[i], &_l[i], _k[i], 2);
			l = RLC_MAX(l, _l[i]);
		}

		ep2_set_infty(r);
		for (j = l - 1; j >= 0; j--) {
			ep2_dbl(r, r);

			for (i = 0; i < 4; i++) {
				if (naf[i][j] > 0) {
					ep2_add(r, r, q[i]);
				}
				if (naf[i][j] < 0) {
					ep2_sub(r, r, q[i]);
				}
			}
		}

		/* Convert r to affine coordinates. */
		ep2_norm(r, r);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(n);
		bn_free(u);
		for (i = 0; i < 4; i++) {
			bn_free(_k[i]);
			ep2_free(q[i]);
		}

	}
}


static void ep2_mul_fix_plain(ep2_t r, const ep2_t *table, const bn_t k) {
	int len, i, n;
	int8_t naf[2 * RLC_FP_BITS + 1], *t;

	if (bn_is_zero(k)) {
		ep2_set_infty(r);
		return;
	}

	/* Compute the w-TNAF representation of k. */
	len = 2 * RLC_FP_BITS + 1;
	bn_rec_naf(naf, &len, k, EP_DEPTH);

	t = naf + len - 1;
	ep2_set_infty(r);
	for (i = len - 1; i >= 0; i--, t--) {
		ep2_dbl(r, r);

		n = *t;
		if (n > 0) {
			ep2_add(r, r, table[n / 2]);
		}
		if (n < 0) {
			ep2_sub(r, r, table[-n / 2]);
		}
	}
	/* Convert r to affine coordinates. */
	ep2_norm(r, r);
	if (bn_sign(k) == RLC_NEG) {
		ep2_neg(r, r);
	}
}


static void ep2_mul_sim_plain(ep2_t r, const ep2_t p, const bn_t k,
		const ep2_t q, const bn_t m, const ep2_t *t) {
	int i, l, l0, l1, n0, n1, w, gen;
	int8_t naf0[2 * RLC_FP_BITS + 1], naf1[2 * RLC_FP_BITS + 1], *_k, *_m;
	ep2_t t0[1 << (EP_WIDTH - 2)];
	ep2_t t1[1 << (EP_WIDTH - 2)];

	RLC_TRY {
		gen = (t == NULL ? 0 : 1);
		if (!gen) {
			for (i = 0; i < (1 << (EP_WIDTH - 2)); i++) {
				ep2_null(t0[i]);
				ep2_new(t0[i]);
			}
			ep2_tab(t0, p, EP_WIDTH);
			t = (ep2_t *)t0;
		}

		/* Prepare the precomputation table. */
		for (i = 0; i < (1 << (EP_WIDTH - 2)); i++) {
			ep2_null(t1[i]);
			ep2_new(t1[i]);
		}
		/* Compute the precomputation table. */
		ep2_tab(t1, q, EP_WIDTH);

		/* Compute the w-TNAF representation of k. */
		if (gen) {
			w = EP_DEPTH;
		} else {
			w = EP_WIDTH;
		}
		l0 = l1 = 2 * RLC_FP_BITS + 1;
		bn_rec_naf(naf0, &l0, k, w);
		bn_rec_naf(naf1, &l1, m, EP_WIDTH);

		l = RLC_MAX(l0, l1);
		_k = naf0 + l - 1;
		_m = naf1 + l - 1;
		if (bn_sign(k) == RLC_NEG) {
			for (i =  0; i < l0; i++) {
				naf0[i] = -naf0[i];
			}
		}
		if (bn_sign(m) == RLC_NEG) {
			for (i =  0; i < l1; i++) {
				naf1[i] = -naf1[i];
			}
		}

		ep2_set_infty(r);
		for (i = l - 1; i >= 0; i--, _k--, _m--) {
			ep2_dbl(r, r);

			n0 = *_k;
			n1 = *_m;
			if (n0 > 0) {
				ep2_add(r, r, t[n0 / 2]);
			}
			if (n0 < 0) {
				ep2_sub(r, r, t[-n0 / 2]);
			}
			if (n1 > 0) {
				ep2_add(r, r, t1[n1 / 2]);
			}
			if (n1 < 0) {
				ep2_sub(r, r, t1[-n1 / 2]);
			}
		}
		/* Convert r to affine coordinates. */
		ep2_norm(r, r);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		/* Free the precomputation tables. */
		if (!gen) {
			for (i = 0; i < (1 << (EP_WIDTH - 2)); i++) {
				ep2_free(t0[i]);
			}
		}
		for (i = 0; i < (1 << (EP_WIDTH - 2)); i++) {
			ep2_free(t1[i]);
		}
	}
}


void ep2_mul_sim_dig(ep2_t r, const ep2_t p[], const dig_t k[], int len) {
	ep2_t t;
	int max;

	ep2_null(t);

	max = util_bits_dig(k[0]);
	for (int i = 1; i < len; i++) {
		max = RLC_MAX(max, util_bits_dig(k[i]));
	}

	RLC_TRY {
		ep2_new(t);

		ep2_set_infty(t);
		for (int i = max - 1; i >= 0; i--) {
			ep2_dbl(t, t);
			for (int j = 0; j < len; j++) {
				if (k[j] & ((dig_t)1 << i)) {
					ep2_add(t, t, p[j]);
				}
			}
		}

		ep2_norm(r, t);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		ep2_free(t);
	}
}


static void ep2_mul_sim_endom(ep2_t r, const ep2_t p, const bn_t k, ep2_t q,
		const bn_t m) {
	int i, j, l, _l[4];
	bn_t _k[4], _m[4], n, u;
	int8_t naf0[4][RLC_FP_BITS + 1];
	int8_t naf1[4][RLC_FP_BITS + 1];
	ep2_t _p[4], _q[4];

	bn_null(n);
	bn_null(u);

	RLC_TRY {
		bn_new(n);
		bn_new(u);
		for (i = 0; i < 4; i++) {
			bn_null(_k[i]);
			bn_new(_k[i]);
			bn_null(_m[i]);
			bn_new(_m[i]);
			ep2_null(_p[i]);
			ep2_null(_q[i]);
			ep2_new(_p[i]);
			ep2_new(_q[i]);
		}

		ep2_norm(_p[0], p);
		ep2_frb(_p[1], _p[0], 1);
		ep2_frb(_p[2], _p[1], 1);
		ep2_frb(_p[3], _p[2], 1);
		ep2_norm(_q[0], q);
		ep2_frb(_q[1], _q[0], 1);
		ep2_frb(_q[2], _q[1], 1);
		ep2_frb(_q[3], _q[2], 1);

		ep2_curve_get_ord(n);
		fp_prime_get_par(u);
		bn_mod(_k[0], k, n);
		bn_rec_frb(_k, 4, _k[0], u, n, ep_curve_is_pairf() == EP_BN);
		bn_mod(_m[0], m, n);
		bn_rec_frb(_m, 4, _m[0], u, n, ep_curve_is_pairf() == EP_BN);

		l = 0;
		for (i = 0; i < 4; i++) {
			_l[i] = RLC_FP_BITS + 1;
			bn_rec_naf(naf0[i], &_l[i], _k[i], 2);
			if (bn_sign(_k[i]) == RLC_NEG) {
				ep2_neg(_p[i], _p[i]);
			}
			l = RLC_MAX(l, _l[i]);
			_l[i] = RLC_FP_BITS + 1;
			bn_rec_naf(naf1[i], &_l[i], _m[i], 2);
			if (bn_sign(_m[i]) == RLC_NEG) {
				ep2_neg(_q[i], _q[i]);
			}
			l = RLC_MAX(l, _l[i]);
		}

		ep2_set_infty(r);
		for (i = l - 1; i >= 0; i--) {
			ep2_dbl(r, r);
			for (j = 0; j < 4; j++) {
				if (naf0[j][i] > 0) {
					ep2_add(r, r, _p[j]);
				}
				if (naf0[j][i] < 0) {
					ep2_sub(r, r, _p[j]);
				}
				if (naf1[j][i] > 0) {
					ep2_add(r, r, _q[j]);
				}
				if (naf1[j][i] < 0) {
					ep2_sub(r, r, _q[j]);
				}
			}
		}

		/* Convert r to affine coordinates. */
		ep2_norm(r, r);
	} RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	} RLC_FINALLY {
		bn_free(n);
		bn_free(u);
		for (i = 0; i < 4; i++) {
			bn_free(_k[i]);
			bn_free(_m[i]);
			ep2_free(_p[i]);
			ep2_free(_q[i]);
		}
	}
}


void ep2_mul_sim_joint(ep2_t r, const ep2_t p, const bn_t k, const ep2_t q,
		const bn_t m) {
	bn_t n, _k, _m;
	ep2_t t[5];
	int i, l, u_i, offset;
	int8_t jsf[2 * (RLC_FP_BITS + 1)];

	if (bn_is_zero(k) || ep2_is_infty(p)) {
		ep2_mul(r, q, m);
		return;
	}
	if (bn_is_zero(m) || ep2_is_infty(q)) {
		ep2_mul(r, p, k);
		return;
	}

	bn_null(n);
	bn_null(_k);
	bn_null(_m);

	RLC_TRY {
		bn_new(n);
		bn_new(_k);
		bn_new(_m);
		for (i = 0; i < 5; i++) {
			ep2_null(t[i]);
			ep2_new(t[i]);
		}

		ep2_curve_get_ord(n);
		bn_mod(_k, k, n);
		bn_mod(_m, m, n);

		ep2_set_infty(t[0]);
		ep2_copy(t[1], q);
		if (bn_sign(_m) == RLC_NEG) {
			ep2_neg(t[1], t[1]);
		}
		ep2_copy(t[2], p);
		if (bn_sign(_k) == RLC_NEG) {
			ep2_neg(t[2], t[2]);
		}
		ep2_add(t[3], t[2], t[1]);
		ep2_sub(t[4], t[2], t[1]);
#if defined(EP_MIXED)
		ep2_norm_sim(t + 3, t + 3, 2);
#endif

		l = 2 * (RLC_FP_BITS + 1);
		bn_rec_jsf(jsf, &l, _k, _m);

		ep2_set_infty(r);

		offset = RLC_MAX(bn_bits(_k), bn_bits(_m)) + 1;
		for (i = l - 1; i >= 0; i--) {
			ep2_dbl(r, r);
			if (jsf[i] != 0 && jsf[i] == -jsf[i + offset]) {
				u_i = jsf[i] * 2 + jsf[i + offset];
				if (u_i < 0) {
					ep2_sub(r, r, t[4]);
				} else {
					ep2_add(r, r, t[4]);
				}
			} else {
				u_i = jsf[i] * 2 + jsf[i + offset];
				if (u_i < 0) {
					ep2_sub(r, r, t[-u_i]);
				} else {
					ep2_add(r, r, t[u_i]);
				}
			}
		}
		ep2_norm(r, r);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(n);
		bn_free(_k);
		bn_free(_m);
		for (i = 0; i < 5; i++) {
			ep2_free(t[i]);
		}
	}
}


void ep2_read_bin(ep2_t a, const uint8_t *bin, int len) {
	if (len == 1) {
		if (bin[0] == 0) {
			ep2_set_infty(a);
			return;
		} else {
			RLC_THROW(ERR_NO_BUFFER);
			return;
		}
	}

	if (len != (2 * RLC_FP_BYTES + 1) && len != (4 * RLC_FP_BYTES + 1)) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}

	a->coord = BASIC;
	fp2_set_dig(a->z, 1);
	fp2_read_bin(a->x, bin + 1, 2 * RLC_FP_BYTES);
	if (len == 2 * RLC_FP_BYTES + 1) {
		switch(bin[0]) {
			case 2:
				fp2_zero(a->y);
				break;
			case 3:
				fp2_zero(a->y);
				fp_set_bit(a->y[0], 0, 1);
				fp_zero(a->y[1]);
				break;
			default:
				RLC_THROW(ERR_NO_VALID);
				break;
		}
		ep2_upk(a, a);
	}

	if (len == 4 * RLC_FP_BYTES + 1) {
		if (bin[0] == 4) {
			fp2_read_bin(a->y, bin + 2 * RLC_FP_BYTES + 1, 2 * RLC_FP_BYTES);
		} else {
			RLC_THROW(ERR_NO_VALID);
			return;
		}
	}

	if (!ep2_on_curve(a)) {
		RLC_THROW(ERR_NO_VALID);
	}
}


void ep2_write_bin(uint8_t *bin, int len, const ep2_t a, int pack) {
	ep2_t t;

	ep2_null(t);

	memset(bin, 0, len);

	if (ep2_is_infty(a)) {
		if (len < 1) {
			RLC_THROW(ERR_NO_BUFFER);
			return;
		} else {
			return;
		}
	}

	RLC_TRY {
		ep2_new(t);

		ep2_norm(t, a);

		if (pack) {
			if (len < 2 * RLC_FP_BYTES + 1) {
				RLC_THROW(ERR_NO_BUFFER);
			} else {
				ep2_pck(t, t);
				bin[0] = 2 | fp_get_bit(t->y[0], 0);
				fp2_write_bin(bin + 1, 2 * RLC_FP_BYTES, t->x, 0);
			}
		} else {
			if (len < 4 * RLC_FP_BYTES + 1) {
				RLC_THROW(ERR_NO_BUFFER);
			} else {
				bin[0] = 4;
				fp2_write_bin(bin + 1, 2 * RLC_FP_BYTES, t->x, 0);
				fp2_write_bin(bin + 2 * RLC_FP_BYTES + 1, 2 * RLC_FP_BYTES, t->y, 0);
			}
		}
	} RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	} RLC_FINALLY {
		ep2_free(t);
	}
}


void ep4_map(ep4_t p, const uint8_t *msg, int len) {
	bn_t x;
	fp4_t t0;
	uint8_t digest[RLC_MD_LEN];

	bn_null(x);
	fp4_null(t0);

	RLC_TRY {
		bn_new(x);
		fp4_new(t0);

		md_map(digest, msg, len);
		bn_read_bin(x, digest, RLC_MIN(RLC_FP_BYTES, RLC_MD_LEN));

		fp4_zero(p->x);
		fp_prime_conv(p->x[0][0], x);
		fp4_set_dig(p->z, 1);

		while (1) {
			ep4_rhs(t0, p);

			if (fp4_srt(p->y, t0)) {
				p->coord = BASIC;
				break;
			}

			fp_add_dig(p->x[0][0], p->x[0][0], 1);
		}

		ep4_mul_cof(p, p);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(x);
		fp4_free(t0);
	}
}


static void ep4_mul_naf_imp(ep4_t r, const ep4_t p, const bn_t k) {
	int l, i, n;
	int8_t naf[RLC_FP_BITS + 1];
	ep4_t t[1 << (EP_WIDTH - 2)];

	RLC_TRY {
		/* Prepare the precomputation table. */
		for (i = 0; i < (1 << (EP_WIDTH - 2)); i++) {
			ep4_null(t[i]);
			ep4_new(t[i]);
		}
		/* Compute the precomputation table. */
		ep4_tab(t, p, EP_WIDTH);

		/* Compute the w-NAF representation of k. */
		l = sizeof(naf);
		bn_rec_naf(naf, &l, k, EP_WIDTH);

		ep4_set_infty(r);
		for (i = l - 1; i >= 0; i--) {
			ep4_dbl(r, r);

			n = naf[i];
			if (n > 0) {
				ep4_add(r, r, t[n / 2]);
			}
			if (n < 0) {
				ep4_sub(r, r, t[-n / 2]);
			}
		}
		/* Convert r to affine coordinates. */
		ep4_norm(r, r);
		if (bn_sign(k) == RLC_NEG) {
			ep4_neg(r, r);
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		/* Free the precomputation table. */
		for (i = 0; i < (1 << (EP_WIDTH - 2)); i++) {
			ep4_free(t[i]);
		}
	}
}


static void ep4_mul_glv_imp(ep4_t r, const ep4_t p, const bn_t k) {
	int sign, i, j, l, _l[8];
	bn_t n, _k[8], u, v;
	int8_t naf[8][RLC_FP_BITS + 1];
	ep4_t q[8];

	bn_null(n);
	bn_null(u);
	bn_null(v);

	RLC_TRY {
		bn_new(n);
		bn_new(u);
		bn_new(v);
		for (i = 0; i < 8; i++) {
			bn_null(_k[i]);
			ep4_null(q[i]);
			bn_new(_k[i]);
			ep4_new(q[i]);
		}

        bn_abs(v, k);
		ep4_curve_get_ord(n);
        if (bn_cmp_abs(v, n) == RLC_GT) {
            bn_mod(v, v, n);
        }

		fp_prime_get_par(u);
		sign = bn_sign(u);
        bn_abs(u, u);

		ep4_norm(q[0], p);
		for (i = 0; i < 8; i++) {
			bn_mod(_k[i], v, u);
			bn_div(v, v, u);
			if ((sign == RLC_NEG) && (i % 2 != 0)) {
				bn_neg(_k[i], _k[i]);
			}
            if (bn_sign(k) == RLC_NEG) {
                bn_neg(_k[i], _k[i]);
            }
            if (i > 0) {
                ep4_frb(q[i], q[i - 1], 1);
            }
		}

        l = 0;
		for (i = 0; i < 8; i++) {
			if (bn_sign(_k[i]) == RLC_NEG) {
				ep4_neg(q[i], q[i]);
			}
			_l[i] = RLC_FP_BITS + 1;
			bn_rec_naf(naf[i], &_l[i], _k[i], 2);
            l = RLC_MAX(l, _l[i]);
		}

		ep4_set_infty(r);
		for (j = l - 1; j >= 0; j--) {
			ep4_dbl(r, r);

			for (i = 0; i < 8; i++) {
				if (naf[i][j] > 0) {
					ep4_add(r, r, q[i]);
				}
				if (naf[i][j] < 0) {
					ep4_sub(r, r, q[i]);
				}
			}
		}

		/* Convert r to affine coordinates. */
		ep4_norm(r, r);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(n);
        bn_free(u);
        bn_free(v);
		for (i = 0; i < 8; i++) {
			bn_free(_k[i]);
			ep4_free(q[i]);
		}

	}
}


void ep4_mul_slide(ep4_t r, const ep4_t p, const bn_t k) {
	ep4_t t[1 << (EP_WIDTH - 1)], q;
	int i, j, l;
	uint8_t win[RLC_FP_BITS + 1];

	ep4_null(q);

	if (bn_is_zero(k) || ep4_is_infty(p)) {
		ep4_set_infty(r);
		return;
	}

	RLC_TRY {
		for (i = 0; i < (1 << (EP_WIDTH - 1)); i ++) {
			ep4_null(t[i]);
			ep4_new(t[i]);
		}

		ep4_new(q);

		ep4_copy(t[0], p);
		ep4_dbl(q, p);

#if defined(EP_MIXED)
		ep4_norm(q, q);
#endif

		/* Create table. */
		for (i = 1; i < (1 << (EP_WIDTH - 1)); i++) {
			ep4_add(t[i], t[i - 1], q);
		}

#if defined(EP_MIXED)
		ep4_norm_sim(t + 1, t + 1, (1 << (EP_WIDTH - 1)) - 1);
#endif

		ep4_set_infty(q);
		l = RLC_FP_BITS + 1;
		bn_rec_slw(win, &l, k, EP_WIDTH);
		for (i = 0; i < l; i++) {
			if (win[i] == 0) {
				ep4_dbl(q, q);
			} else {
				for (j = 0; j < util_bits_dig(win[i]); j++) {
					ep4_dbl(q, q);
				}
				ep4_add(q, q, t[win[i] >> 1]);
			}
		}

		ep4_norm(r, q);
		if (bn_sign(k) == RLC_NEG) {
			ep4_neg(r, r);
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		for (i = 0; i < (1 << (EP_WIDTH - 1)); i++) {
			ep4_free(t[i]);
		}
		ep4_free(q);
	}
}


static void ep4_mul_pre_ordin(ep4_t *t, const ep4_t p) {
	int i;

	ep4_dbl(t[0], p);
#if defined(EP_MIXED)
	ep4_norm(t[0], t[0]);
#endif

#if EP_DEPTH > 2
	ep4_add(t[1], t[0], p);
	for (i = 2; i < (1 << (EP_DEPTH - 2)); i++) {
		ep4_add(t[i], t[i - 1], t[0]);
	}

#if defined(EP_MIXED)
	for (i = 1; i < (1 << (EP_DEPTH - 2)); i++) {
		ep4_norm(t[i], t[i]);
	}
#endif

#endif
	ep4_copy(t[0], p);
}


static void ep4_mul_fix_ordin(ep4_t r, const ep4_t *table, const bn_t k) {
	int len, i, n;
	int8_t naf[2 * RLC_FP_BITS + 1], *t;

	if (bn_is_zero(k)) {
		ep4_set_infty(r);
		return;
	}

	/* Compute the w-TNAF representation of k. */
	len = 2 * RLC_FP_BITS + 1;
	bn_rec_naf(naf, &len, k, EP_DEPTH);

	t = naf + len - 1;
	ep4_set_infty(r);
	for (i = len - 1; i >= 0; i--, t--) {
		ep4_dbl(r, r);

		n = *t;
		if (n > 0) {
			ep4_add(r, r, table[n / 2]);
		}
		if (n < 0) {
			ep4_sub(r, r, table[-n / 2]);
		}
	}
	/* Convert r to affine coordinates. */
	ep4_norm(r, r);
	if (bn_sign(k) == RLC_NEG) {
		ep4_neg(r, r);
	}
}


void ep4_mul_sim_trick(ep4_t r, const ep4_t p, const bn_t k, const ep4_t q,
		const bn_t m) {
	ep4_t t0[1 << (EP_WIDTH / 2)];
	ep4_t t1[1 << (EP_WIDTH / 2)];
	ep4_t t[1 << EP_WIDTH];
	bn_t n;
	int l0, l1, w = EP_WIDTH / 2;
	uint8_t w0[2 * RLC_FP_BITS], w1[2 * RLC_FP_BITS];

	bn_null(n);

	if (bn_is_zero(k) || ep4_is_infty(p)) {
		ep4_mul(r, q, m);
		return;
	}
	if (bn_is_zero(m) || ep4_is_infty(q)) {
		ep4_mul(r, p, k);
		return;
	}

	RLC_TRY {
		bn_new(n);

		ep4_curve_get_ord(n);

		for (int i = 0; i < (1 << w); i++) {
			ep4_null(t0[i]);
			ep4_null(t1[i]);
			ep4_new(t0[i]);
			ep4_new(t1[i]);
		}
		for (int i = 0; i < (1 << EP_WIDTH); i++) {
			ep4_null(t[i]);
			ep4_new(t[i]);
		}

		ep4_set_infty(t0[0]);
		ep4_copy(t0[1], p);
		if (bn_sign(k) == RLC_NEG) {
			ep4_neg(t0[1], t0[1]);
		}
		for (int i = 2; i < (1 << w); i++) {
			ep4_add(t0[i], t0[i - 1], t0[1]);
		}

		ep4_set_infty(t1[0]);
		ep4_copy(t1[1], q);
		if (bn_sign(m) == RLC_NEG) {
			ep4_neg(t1[1], t1[1]);
		}
		for (int i = 1; i < (1 << w); i++) {
			ep4_add(t1[i], t1[i - 1], t1[1]);
		}

		for (int i = 0; i < (1 << w); i++) {
			for (int j = 0; j < (1 << w); j++) {
				ep4_add(t[(i << w) + j], t0[i], t1[j]);
			}
		}

#if defined(EP_MIXED)
		ep4_norm_sim(t + 1, t + 1, (1 << (EP_WIDTH)) - 1);
#endif

		l0 = l1 = RLC_CEIL(2 * RLC_FP_BITS, w);
		bn_rec_win(w0, &l0, k, w);
		bn_rec_win(w1, &l1, m, w);

		for (int i = l0; i < l1; i++) {
			w0[i] = 0;
		}
		for (int i = l1; i < l0; i++) {
			w1[i] = 0;
		}

		ep4_set_infty(r);
		for (int i = RLC_MAX(l0, l1) - 1; i >= 0; i--) {
			for (int j = 0; j < w; j++) {
				ep4_dbl(r, r);
			}
			ep4_add(r, r, t[(w0[i] << w) + w1[i]]);
		}
		ep4_norm(r, r);
	} RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(n);
		for (int i = 0; i < (1 << w); i++) {
			ep4_free(t0[i]);
			ep4_free(t1[i]);
		}
		for (int i = 0; i < (1 << EP_WIDTH); i++) {
			ep4_free(t[i]);
		}
	}
}


void ep4_mul_sim_joint(ep4_t r, const ep4_t p, const bn_t k, const ep4_t q,
		const bn_t m) {
	ep4_t t[5];
	int i, l, u_i, offset;
	int8_t jsf[4 * (RLC_FP_BITS + 1)];

	if (bn_is_zero(k) || ep4_is_infty(p)) {
		ep4_mul(r, q, m);
		return;
	}
	if (bn_is_zero(m) || ep4_is_infty(q)) {
		ep4_mul(r, p, k);
		return;
	}

	RLC_TRY {
		for (i = 0; i < 5; i++) {
			ep4_null(t[i]);
			ep4_new(t[i]);
		}

		ep4_set_infty(t[0]);
		ep4_copy(t[1], q);
		if (bn_sign(m) == RLC_NEG) {
			ep4_neg(t[1], t[1]);
		}
		ep4_copy(t[2], p);
		if (bn_sign(k) == RLC_NEG) {
			ep4_neg(t[2], t[2]);
		}
		ep4_add(t[3], t[2], t[1]);
		ep4_sub(t[4], t[2], t[1]);
#if defined(EP_MIXED)
		ep4_norm_sim(t + 3, t + 3, 2);
#endif

		l = 4 * (RLC_FP_BITS + 1);
		bn_rec_jsf(jsf, &l, k, m);

		ep4_set_infty(r);

		offset = RLC_MAX(bn_bits(k), bn_bits(m)) + 1;
		for (i = l - 1; i >= 0; i--) {
			ep4_dbl(r, r);
			if (jsf[i] != 0 && jsf[i] == -jsf[i + offset]) {
				u_i = jsf[i] * 2 + jsf[i + offset];
				if (u_i < 0) {
					ep4_sub(r, r, t[4]);
				} else {
					ep4_add(r, r, t[4]);
				}
			} else {
				u_i = jsf[i] * 2 + jsf[i + offset];
				if (u_i < 0) {
					ep4_sub(r, r, t[-u_i]);
				} else {
					ep4_add(r, r, t[u_i]);
				}
			}
		}
		ep4_norm(r, r);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		for (i = 0; i < 5; i++) {
			ep4_free(t[i]);
		}
	}
}


static void ep4_mul_sim_plain(ep4_t r, const ep4_t p, const bn_t k,
		const ep4_t q, const bn_t m, ep4_t *t) {
	int i, l, l0, l1, n0, n1, w, gen;
	int8_t naf0[2 * RLC_FP_BITS + 1], naf1[2 * RLC_FP_BITS + 1], *_k, *_m;
	ep4_t t0[1 << (EP_WIDTH - 2)];
	ep4_t t1[1 << (EP_WIDTH - 2)];

	RLC_TRY {
		gen = (t == NULL ? 0 : 1);
		if (!gen) {
			for (i = 0; i < (1 << (EP_WIDTH - 2)); i++) {
				ep4_null(t0[i]);
				ep4_new(t0[i]);
			}
			ep4_tab(t0, p, EP_WIDTH);
			t = (ep4_t *)t0;
		}

		/* Prepare the precomputation table. */
		for (i = 0; i < (1 << (EP_WIDTH - 2)); i++) {
			ep4_null(t1[i]);
			ep4_new(t1[i]);
		}
		/* Compute the precomputation table. */
		ep4_tab(t1, q, EP_WIDTH);

		/* Compute the w-TNAF representation of k. */
		if (gen) {
			w = EP_DEPTH;
		} else {
			w = EP_WIDTH;
		}
		l0 = l1 = 2 * RLC_FP_BITS + 1;
		bn_rec_naf(naf0, &l0, k, w);
		bn_rec_naf(naf1, &l1, m, EP_WIDTH);

		l = RLC_MAX(l0, l1);
		_k = naf0 + l - 1;
		_m = naf1 + l - 1;
		if (bn_sign(k) == RLC_NEG) {
			for (i =  0; i < l0; i++) {
				naf0[i] = -naf0[i];
			}
		}
		if (bn_sign(m) == RLC_NEG) {
			for (i =  0; i < l1; i++) {
				naf1[i] = -naf1[i];
			}
		}

		ep4_set_infty(r);
		for (i = l - 1; i >= 0; i--, _k--, _m--) {
			ep4_dbl(r, r);

			n0 = *_k;
			n1 = *_m;
			if (n0 > 0) {
				ep4_add(r, r, t[n0 / 2]);
			}
			if (n0 < 0) {
				ep4_sub(r, r, t[-n0 / 2]);
			}
			if (n1 > 0) {
				ep4_add(r, r, t1[n1 / 2]);
			}
			if (n1 < 0) {
				ep4_sub(r, r, t1[-n1 / 2]);
			}
		}
		/* Convert r to affine coordinates. */
		ep4_norm(r, r);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		/* Free the precomputation tables. */
		if (!gen) {
			for (i = 0; i < (1 << (EP_WIDTH - 2)); i++) {
				ep4_free(t0[i]);
			}
		}
		for (i = 0; i < (1 << (EP_WIDTH - 2)); i++) {
			ep4_free(t1[i]);
		}
	}
}


void ep4_mul_sim_dig(ep4_t r, const ep4_t p[], const dig_t k[], int len) {
	ep4_t t;
	int max;

	ep4_null(t);

	max = util_bits_dig(k[0]);
	for (int i = 1; i < len; i++) {
		max = RLC_MAX(max, util_bits_dig(k[i]));
	}

	RLC_TRY {
		ep4_new(t);

		ep4_set_infty(t);
		for (int i = max - 1; i >= 0; i--) {
			ep4_dbl(t, t);
			for (int j = 0; j < len; j++) {
				if (k[j] & ((dig_t)1 << i)) {
					ep4_add(t, t, p[j]);
				}
			}
		}

		ep4_norm(r, t);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		ep4_free(t);
	}
}


void ep4_write_bin(uint8_t *bin, int len, const ep4_t a, int pack) {
	ep4_t t;

	ep4_null(t);

	memset(bin, 0, len);

	if (ep4_is_infty(a)) {
		if (len < 1) {
			RLC_THROW(ERR_NO_BUFFER);
			return;
		} else {
			return;
		}
	}

	RLC_TRY {
		ep4_new(t);

		ep4_norm(t, a);

		if (len < 8 * RLC_FP_BYTES + 1) {
			RLC_THROW(ERR_NO_BUFFER);
		} else {
			bin[0] = 4;
			fp4_write_bin(bin + 1, 4 * RLC_FP_BYTES, t->x);
			fp4_write_bin(bin + 4 * RLC_FP_BYTES + 1, 4 * RLC_FP_BYTES, t->y);
		}
	} RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	} RLC_FINALLY {
		ep4_free(t);
	}
}


void ep4_read_bin(ep4_t a, const uint8_t *bin, int len) {
	if (len == 1) {
		if (bin[0] == 0) {
			ep4_set_infty(a);
			return;
		} else {
			RLC_THROW(ERR_NO_BUFFER);
			return;
		}
	}

	if (len != (8 * RLC_FP_BYTES + 1)) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}

	a->coord = BASIC;
	fp4_set_dig(a->z, 1);
	fp4_read_bin(a->x, bin + 1, 4 * RLC_FP_BYTES);

	if (len == 8 * RLC_FP_BYTES + 1) {
		if (bin[0] == 4) {
			fp4_read_bin(a->y, bin + 4 * RLC_FP_BYTES + 1, 4 * RLC_FP_BYTES);
		} else {
			RLC_THROW(ERR_NO_VALID);
			return;
		}
	}

	if (!ep4_on_curve(a)) {
		RLC_THROW(ERR_NO_VALID);
	}
}


void fb_exp_basic(fb_t c, const fb_t a, const bn_t b) {
	int i, l;
	fb_t r;

	if (bn_is_zero(b)) {
		fb_set_dig(c, 1);
		return;
	}

	fb_null(r);

	RLC_TRY {
		fb_new(r);

		l = bn_bits(b);

		fb_copy(r, a);

		for (i = l - 2; i >= 0; i--) {
			fb_sqr(r, r);
			if (bn_get_bit(b, i)) {
				fb_mul(r, r, a);
			}
		}

		if (bn_sign(b) == RLC_NEG) {
			fb_inv(c, r);
		} else {
			fb_copy(c, r);
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		fb_free(r);
	}
}


void fb_exp_slide(fb_t c, const fb_t a, const bn_t b) {
	fb_t t[1 << (FB_WIDTH - 1)], r;
	int i, j, l;
	uint8_t win[RLC_FB_BITS + 1];

	fb_null(r);

	if (bn_is_zero(b)) {
		fb_set_dig(c, 1);
		return;
	}

	/* Initialize table. */
	for (i = 0; i < (1 << (FB_WIDTH - 1)); i++) {
		fb_null(t[i]);
	}

	RLC_TRY {
		for (i = 0; i < (1 << (FB_WIDTH - 1)); i ++) {
			fb_new(t[i]);
		}
		fb_new(r);

		fb_copy(t[0], a);
		fb_sqr(r, a);

		/* Create table. */
		for (i = 1; i < 1 << (FB_WIDTH - 1); i++) {
			fb_mul(t[i], t[i - 1], r);
		}

		fb_set_dig(r, 1);
		l = RLC_FB_BITS + 1;
		bn_rec_slw(win, &l, b, FB_WIDTH);
		for (i = 0; i < l; i++) {
			if (win[i] == 0) {
				fb_sqr(r, r);
			} else {
				for (j = 0; j < util_bits_dig(win[i]); j++) {
					fb_sqr(r, r);
				}
				fb_mul(r, r, t[win[i] >> 1]);
			}
		}

		if (bn_sign(b) == RLC_NEG) {
			fb_inv(c, r);
		} else {
			fb_copy(c, r);
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		for (i = 0; i < (1 << (FB_WIDTH - 1)); i++) {
			fb_free(t[i]);
		}
		fb_free(r);
	}
}


void fb_write_bin(uint8_t *bin, int len, const fb_t a) {
	bn_t t;

	bn_null(t);

	if (len != RLC_FB_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}

	RLC_TRY {
		bn_new(t);

		bn_read_raw(t, a, RLC_FB_DIGS);

		bn_write_bin(bin, len, t);
	} RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(t);
	}
}


void fb_read_str(fb_t a, const char *str, int len, int radix) {
	bn_t t;

	bn_null(t);

	if (!valid_radix(radix)) {
		RLC_THROW(ERR_NO_VALID);
	}

	RLC_TRY {
		bn_new(t);

		bn_read_str(t, str, len, radix);

		if (bn_bits(t) > RLC_FB_BITS) {
			RLC_THROW(ERR_NO_BUFFER);
		}

		fb_zero(a);
		dv_copy(a, t->dp, t->used);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(t);
	}
}


static int log_radix(int radix) {
	int l = 0;

	while (radix > 0) {
		radix = radix / 2;
		l++;
	}
	return --l;
}


int fb_get_bit(const fb_t a, int bit) {
	int d;

	RLC_RIP(bit, d, bit);

	return (a[d] >> bit) & 1;
}


void fb_write_str(char *str, int len, const fb_t a, int radix) {
	fb_t t;
	int d, l, i, j;
	char c;

	fb_null(t);

	l = fb_size_str(a, radix);
	if (len < l) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}
	len = l;

	l = log_radix(radix);
	if (!valid_radix(radix)) {
		RLC_THROW(ERR_NO_VALID);
		return;
	}

	if (fb_is_zero(a) == 1) {
		*str++ = '0';
		*str = '\0';
		return;
	}

	RLC_TRY {
		fb_new(t);
		fb_copy(t, a);

		j = 0;
		while (!fb_is_zero(t)) {
			d = t[0] % radix;
			fb_rshb_low(t, t, l);
			str[j] = util_conv_char(d);
			j++;
		}

		/* Reverse the digits of the string. */
		i = 0;
		j = len - 2;
		while (i < j) {
			c = str[i];
			str[i] = str[j];
			str[j] = c;
			++i;
			--j;
		}

		str[len - 1] = '\0';
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		fb_free(t);
	}
}


void fb_print(const fb_t a) {
	int i;

	/* Suppress possible unused parameter warning. */
	(void)a;
	for (i = RLC_FB_DIGS - 1; i > 0; i--) {
		util_print_dig(a[i], 1);
		util_print(" ");
	}
	util_print_dig(a[0], 1);
	util_print("\n");
}


void fb_read_bin(fb_t a, const uint8_t *bin, int len) {
	bn_t t;

	bn_null(t);

	if (len != RLC_FB_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}

	RLC_TRY {
		bn_new(t);

		bn_read_bin(t, bin, len);

		fb_copy(a, t->dp);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(t);
	}
}


void fb_set_bit(fb_t a, int bit, int value) {
	int d;
	dig_t mask;

	RLC_RIP(bit, d, bit);

	mask = (dig_t)1 << bit;

	if (value == 1) {
		a[d] |= mask;
	} else {
		a[d] &= ~mask;
	}
}


void fb_rand(fb_t a) {
	int bits, digits;

	rand_bytes((uint8_t *)a, RLC_FB_DIGS * sizeof(dig_t));

	RLC_RIP(bits, digits, RLC_FB_BITS);
	if (bits > 0) {
		dig_t mask = RLC_MASK(bits);
		a[RLC_FB_DIGS - 1] &= mask;
	}
}


static int valid_radix(int radix) {
	while (radix > 0) {
		if (radix != 1 && radix % 2 == 1)
			return 0;
		radix = radix / 2;
	}
	return 1;
}


int fb_bits(const fb_t a) {
	int i = RLC_FB_DIGS - 1;

	while (i >= 0 && a[i] == 0) {
		i--;
	}

	if (i > 0) {
		return (i << RLC_DIG_LOG) + util_bits_dig(a[i]);
	} else {
		return util_bits_dig(a[0]);
	}
}


int fb_size_str(const fb_t a, int radix) {
	bn_t t;
	int digits = 0;

	bn_null(t);

	if (!valid_radix(radix)) {
		RLC_THROW(ERR_NO_VALID);
		return 0;
	}

	RLC_TRY {
		bn_new(t);

		bn_read_raw(t, a, RLC_FB_DIGS);

		digits = bn_size_str(t, radix);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(t);
	}

	return digits;
}


void fp_exp_basic(fp_t c, const fp_t a, const bn_t b) {
	int i, l;
	fp_t r;

	fp_null(r);

	if (bn_is_zero(b)) {
		fp_set_dig(c, 1);
		return;
	}

	RLC_TRY {
		fp_new(r);

		l = bn_bits(b);

		fp_copy(r, a);

		for (i = l - 2; i >= 0; i--) {
			fp_sqr(r, r);
			if (bn_get_bit(b, i)) {
				fp_mul(r, r, a);
			}
		}

		if (bn_sign(b) == RLC_NEG) {
			fp_inv(c, r);
		} else {
			fp_copy(c, r);
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		fp_free(r);
	}
}


void fp_exp_slide(fp_t c, const fp_t a, const bn_t b) {
	fp_t t[1 << (FP_WIDTH - 1)], r;
	int i, j, l;
	uint8_t win[RLC_FP_BITS + 1];

	fp_null(r);

	if (bn_is_zero(b)) {
		fp_set_dig(c, 1);
		return;
	}

	/* Initialize table. */
	for (i = 0; i < (1 << (FP_WIDTH - 1)); i++) {
		fp_null(t[i]);
	}

	RLC_TRY {
		for (i = 0; i < (1 << (FP_WIDTH - 1)); i ++) {
			fp_new(t[i]);
		}
		fp_new(r);

		fp_copy(t[0], a);
		fp_sqr(r, a);

		/* Create table. */
		for (i = 1; i < 1 << (FP_WIDTH - 1); i++) {
			fp_mul(t[i], t[i - 1], r);
		}

		fp_set_dig(r, 1);
		l = RLC_FP_BITS + 1;
		bn_rec_slw(win, &l, b, FP_WIDTH);
		for (i = 0; i < l; i++) {
			if (win[i] == 0) {
				fp_sqr(r, r);
			} else {
				for (j = 0; j < util_bits_dig(win[i]); j++) {
					fp_sqr(r, r);
				}
				fp_mul(r, r, t[win[i] >> 1]);
			}
		}

		if (bn_sign(b) == RLC_NEG) {
			fp_inv(c, r);
		} else {
			fp_copy(c, r);
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		for (i = 0; i < (1 << (FP_WIDTH - 1)); i++) {
			fp_free(t[i]);
		}
		fp_free(r);
	}
}


void fp_prime_set_pmers(const int *f, int len) {
	bn_t p, t;

	bn_null(p);
	bn_null(t);

	RLC_TRY {
		bn_new(p);
		bn_new(t);

		if (len >= RLC_TERMS) {
			RLC_THROW(ERR_NO_VALID);
			return;
		}

		bn_set_2b(p, f[len - 1]);
		for (int i = len - 2; i > 0; i--) {
			if (f[i] > 0) {
				bn_set_2b(t, f[i]);
				bn_add(p, p, t);
			} else {
				bn_set_2b(t, -f[i]);
				bn_sub(p, p, t);
			}
		}
		if (f[0] > 0) {
			bn_add_dig(p, p, f[0]);
		} else {
			bn_sub_dig(p, p, -f[0]);
		}

#if FP_RDC == QUICK || !defined(STRIP)
		ctx_t *ctx = core_get();
		for (int i = 0; i < len; i++) {
			ctx->sps[i] = f[i];
		}
		ctx->sps[len] = 0;
		ctx->sps_len = len;
#endif /* FP_RDC == QUICK */

		fp_prime_set(p);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(p);
		bn_free(t);
	}
}


int fp_size_str(const fp_t a, int radix) {
	bn_t t;
	int digits = 0;

	bn_null(t);

	RLC_TRY {
		bn_new(t);

		fp_prime_back(t, a);

		digits = bn_size_str(t, radix);
	} RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(t);
	}

	return digits;
}


void fp_read_bin(fp_t a, const uint8_t *bin, int len) {
	bn_t t;

	bn_null(t);

	if (len != RLC_FP_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}

	RLC_TRY {
		bn_new(t);
		bn_read_bin(t, bin, len);

		/* Reject values out of bounds. */
		if (bn_sign(t) == RLC_NEG || bn_cmp(t, &core_get()->prime) != RLC_LT) {
			RLC_THROW(ERR_NO_VALID);
		} else {
			if (bn_is_zero(t)) {
				fp_zero(a);
			} else {
				if (t->used == 1) {
					fp_prime_conv_dig(a, t->dp[0]);
				} else {
					fp_prime_conv(a, t);
				}
			}
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(t);
	}
}


int fp_get_bit(const fp_t a, int bit) {
	int d;

	RLC_RIP(bit, d, bit);

	return (a[d] >> bit) & 1;
}


void fp_read_str(fp_t a, const char *str, int len, int radix) {
	bn_t t;

	bn_null(t);

	RLC_TRY {
		bn_new(t);
		bn_read_str(t, str, len, radix);
		if (bn_is_zero(t)) {
			fp_zero(a);
		} else {
			if (t->used == 1) {
				fp_prime_conv_dig(a, t->dp[0]);
				if (bn_sign(t) == RLC_NEG) {
					fp_neg(a, a);
				}
			} else {
				fp_prime_conv(a, t);
			}
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(t);
	}
}


int fp_bits(const fp_t a) {
	int i = RLC_FP_DIGS - 1;

	while (i >= 0 && a[i] == 0) {
		i--;
	}

	if (i > 0) {
		return (i << RLC_DIG_LOG) + util_bits_dig(a[i]);
	} else {
		return util_bits_dig(a[0]);
	}
}


void fp_write_bin(uint8_t *bin, int len, const fp_t a) {
	bn_t t;

	bn_null(t);

	if (len != RLC_FP_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}

	RLC_TRY {
		bn_new(t);

		fp_prime_back(t, a);

		bn_write_bin(bin, len, t);
	} RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(t);
	}
}


void fp_write_str(char *str, int len, const fp_t a, int radix) {
	bn_t t;

	bn_null(t);

	RLC_TRY {
		bn_new(t);

		fp_prime_back(t, a);

		bn_write_str(str, len, t, radix);
	} RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(t);
	}
}


void fp_set_bit(fp_t a, int bit, int value) {
	int d;
	dig_t mask;

	RLC_RIP(bit, d, bit);

	mask = (dig_t)1 << bit;

	if (value == 1) {
		a[d] |= mask;
	} else {
		a[d] &= ~mask;
	}
}


void fp8_exp_cyc(fp8_t c, const fp8_t a, const bn_t b) {
	fp8_t r, s, t[1 << (FP_WIDTH - 2)];
	int i, l;
	int8_t naf[RLC_FP_BITS + 1], *k;

	if (bn_is_zero(b)) {
		return fp8_set_dig(c, 1);
	}

	fp8_null(r);
	fp8_null(s);

	RLC_TRY {
		fp8_new(r);
		fp8_new(s);
		for (i = 0; i < (1 << (FP_WIDTH - 2)); i ++) {
			fp8_null(t[i]);
			fp8_new(t[i]);
		}

#if FP_WIDTH > 2
		fp8_sqr_cyc(t[0], a);
		fp8_mul(t[1], t[0], a);
		for (int i = 2; i < (1 << (FP_WIDTH - 2)); i++) {
			fp8_mul(t[i], t[i - 1], t[0]);
		}
#endif
		fp8_copy(t[0], a);

		l = RLC_FP_BITS + 1;
		fp8_set_dig(r, 1);
		bn_rec_naf(naf, &l, b, FP_WIDTH);

		k = naf + l - 1;

		for (i = l - 1; i >= 0; i--, k--) {
			fp8_sqr_cyc(r, r);

			if (*k > 0) {
				fp8_mul(r, r, t[*k / 2]);
			}
			if (*k < 0) {
				fp8_inv_cyc(s, t[-*k / 2]);
				fp8_mul(r, r, s);
			}
		}

		if (bn_sign(b) == RLC_NEG) {
			fp8_inv_cyc(c, r);
		} else {
			fp8_copy(c, r);
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		fp8_free(r);
		fp8_free(s);
		for (i = 0; i < (1 << (FP_WIDTH - 2)); i++) {
			fp8_free(t[i]);
		}
	}
}


void fp2_exp_cyc(fp2_t c, const fp2_t a, const bn_t b) {
	fp2_t r, s, t[1 << (FP_WIDTH - 2)];
	int i, l;
	int8_t naf[RLC_FP_BITS + 1], *k;

	if (bn_is_zero(b)) {
		return fp2_set_dig(c, 1);
	}

	fp2_null(r);
	fp2_null(s);

	RLC_TRY {
		fp2_new(r);
		fp2_new(s);
		for (i = 0; i < (1 << (FP_WIDTH - 2)); i ++) {
			fp2_null(t[i]);
			fp2_new(t[i]);
		}

#if FP_WIDTH > 2
		fp2_sqr(t[0], a);
		fp2_mul(t[1], t[0], a);
		for (int i = 2; i < (1 << (FP_WIDTH - 2)); i++) {
			fp2_mul(t[i], t[i - 1], t[0]);
		}
#endif
		fp2_copy(t[0], a);

		l = RLC_FP_BITS + 1;
		fp2_set_dig(r, 1);
		bn_rec_naf(naf, &l, b, FP_WIDTH);

		k = naf + l - 1;

		for (i = l - 1; i >= 0; i--, k--) {
			fp2_sqr(r, r);

			if (*k > 0) {
				fp2_mul(r, r, t[*k / 2]);
			}
			if (*k < 0) {
				fp2_inv_cyc(s, t[-*k / 2]);
				fp2_mul(r, r, s);
			}
		}

		if (bn_sign(b) == RLC_NEG) {
			fp2_inv_cyc(c, r);
		} else {
			fp2_copy(c, r);
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		fp2_free(r);
		fp2_free(s);
		for (i = 0; i < (1 << (FP_WIDTH - 2)); i++) {
			fp2_free(t[i]);
		}
	}
}


void fp24_exp_cyc_sps(fp24_t c, const fp24_t a, const int *b, int len, int sign) {
	int i, j, k, w = len;
    fp24_t t, *u = RLC_ALLOCA(fp24_t, w);

	if (len == 0) {
		RLC_FREE(u);
		fp24_set_dig(c, 1);
		return;
	}

	fp24_null(t);

	RLC_TRY {
		if (u == NULL) {
			RLC_THROW(ERR_NO_MEMORY);
		}
		for (i = 0; i < w; i++) {
			fp24_null(u[i]);
			fp24_new(u[i]);
		}
		fp24_new(t);

		fp24_copy(t, a);
		if (b[0] == 0) {
			for (j = 0, i = 1; i < len; i++) {
				k = (b[i] < 0 ? -b[i] : b[i]);
				for (; j < k; j++) {
					fp24_sqr_pck(t, t);
				}
				if (b[i] < 0) {
					fp24_inv_cyc(u[i - 1], t);
				} else {
					fp24_copy(u[i - 1], t);
				}
			}

			fp24_back_cyc_sim(u, u, w - 1);

			fp24_copy(c, a);
			for (i = 0; i < w - 1; i++) {
				fp24_mul(c, c, u[i]);
			}
		} else {
			for (j = 0, i = 0; i < len; i++) {
				k = (b[i] < 0 ? -b[i] : b[i]);
				for (; j < k; j++) {
					fp24_sqr_pck(t, t);
				}
				if (b[i] < 0) {
					fp24_inv_cyc(u[i], t);
				} else {
					fp24_copy(u[i], t);
				}
			}

			fp24_back_cyc_sim(u, u, w);

			fp24_copy(c, u[0]);
			for (i = 1; i < w; i++) {
				fp24_mul(c, c, u[i]);
			}
		}

		if (sign == RLC_NEG) {
			fp24_inv_cyc(c, c);
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		for (i = 0; i < w; i++) {
			fp24_free(u[i]);
		}
		fp24_free(t);
		RLC_FREE(u);
	}
}


void fp12_exp_cyc_sps(fp12_t c, const fp12_t a, const int *b, int len, int sign) {
	int i, j, k, w = len;
    fp12_t t, *u = RLC_ALLOCA(fp12_t, w);

	if (len == 0) {
		RLC_FREE(u);
		fp12_set_dig(c, 1);
		return;
	}

	fp12_null(t);

	RLC_TRY {
		if (u == NULL) {
			RLC_THROW(ERR_NO_MEMORY);
		}
		for (i = 0; i < w; i++) {
			fp12_null(u[i]);
			fp12_new(u[i]);
		}
		fp12_new(t);

		fp12_copy(t, a);
		if (b[0] == 0) {
			for (j = 0, i = 1; i < len; i++) {
				k = (b[i] < 0 ? -b[i] : b[i]);
				for (; j < k; j++) {
					fp12_sqr_pck(t, t);
				}
				if (b[i] < 0) {
					fp12_inv_cyc(u[i - 1], t);
				} else {
					fp12_copy(u[i - 1], t);
				}
			}

			fp12_back_cyc_sim(u, u, w - 1);

			fp12_copy(c, a);
			for (i = 0; i < w - 1; i++) {
				fp12_mul(c, c, u[i]);
			}
		} else {
			for (j = 0, i = 0; i < len; i++) {
				k = (b[i] < 0 ? -b[i] : b[i]);
				for (; j < k; j++) {
					fp12_sqr_pck(t, t);
				}
				if (b[i] < 0) {
					fp12_inv_cyc(u[i], t);
				} else {
					fp12_copy(u[i], t);
				}
			}

			fp12_back_cyc_sim(u, u, w);

			fp12_copy(c, u[0]);
			for (i = 1; i < w; i++) {
				fp12_mul(c, c, u[i]);
			}
		}

		if (sign == RLC_NEG) {
			fp12_inv_cyc(c, c);
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		for (i = 0; i < w; i++) {
			fp12_free(u[i]);
		}
		fp12_free(t);
		RLC_FREE(u);
	}
}


void fp2_exp_cyc_sim(fp2_t e, const fp2_t a, const bn_t b, const fp2_t c, const bn_t d) {
	int i, l, n0, n1, l0, l1;
	int8_t naf0[RLC_FP_BITS + 1], naf1[RLC_FP_BITS + 1], *_k, *_m;
	fp2_t r, t0[1 << (EP_WIDTH - 2)];
	fp2_t s, t1[1 << (EP_WIDTH - 2)];

	if (bn_is_zero(b)) {
		return fp2_exp_cyc(e, c, d);
	}

	if (bn_is_zero(d)) {
		return fp2_exp_cyc(e, a, b);
	}

	fp2_null(r);
	fp2_null(s);

	RLC_TRY {
		fp2_new(r);
		fp2_new(s);
		for (i = 0; i < (1 << (FP_WIDTH - 2)); i ++) {
			fp2_null(t0[i]);
			fp2_null(t1[i]);
			fp2_new(t0[i]);
			fp2_new(t1[i]);
		}

#if FP_WIDTH > 2
		fp2_sqr(t0[0], a);
		fp2_mul(t0[1], t0[0], a);
		for (int i = 2; i < (1 << (FP_WIDTH - 2)); i++) {
			fp2_mul(t0[i], t0[i - 1], t0[0]);
		}

		fp2_sqr(t1[0], c);
		fp2_mul(t1[1], t1[0], c);
		for (int i = 2; i < (1 << (FP_WIDTH - 2)); i++) {
			fp2_mul(t1[i], t1[i - 1], t1[0]);
		}
#endif
		fp2_copy(t0[0], a);
		fp2_copy(t1[0], c);

		l0 = l1 = RLC_FP_BITS + 1;
		bn_rec_naf(naf0, &l0, b, FP_WIDTH);
		bn_rec_naf(naf1, &l1, d, FP_WIDTH);

		l = RLC_MAX(l0, l1);
		if (bn_sign(b) == RLC_NEG) {
			for (i = 0; i < l0; i++) {
				naf0[i] = -naf0[i];
			}
		}
		if (bn_sign(d) == RLC_NEG) {
			for (i = 0; i < l1; i++) {
				naf1[i] = -naf1[i];
			}
		}

		_k = naf0 + l - 1;
		_m = naf1 + l - 1;

		fp2_set_dig(r, 1);
		for (i = l - 1; i >= 0; i--, _k--, _m--) {
			fp2_sqr(r, r);

			n0 = *_k;
			n1 = *_m;

			if (n0 > 0) {
				fp2_mul(r, r, t0[n0 / 2]);
			}
			if (n0 < 0) {
				fp2_inv_cyc(s, t0[-n0 / 2]);
				fp2_mul(r, r, s);
			}
			if (n1 > 0) {
				fp2_mul(r, r, t1[n1 / 2]);
			}
			if (n1 < 0) {
				fp2_inv_cyc(s, t1[-n1 / 2]);
				fp2_mul(r, r, s);
			}
		}

		fp2_copy(e, r);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		fp2_free(r);
		fp2_free(s);
		for (i = 0; i < (1 << (FP_WIDTH - 2)); i++) {
			fp2_free(t0[i]);
			fp2_free(t1[i]);
		}
	}
}


void fp48_exp_cyc_sps(fp48_t c, const fp48_t a, const int *b, int len, int sign) {
	int i, j, k, w = len;
    fp48_t t, *u = RLC_ALLOCA(fp48_t, w);

	if (len == 0) {
		RLC_FREE(u);
		fp48_set_dig(c, 1);
		return;
	}

	fp48_null(t);

	RLC_TRY {
		if (u == NULL) {
			RLC_THROW(ERR_NO_MEMORY);
		}
		for (i = 0; i < w; i++) {
			fp48_null(u[i]);
			fp48_new(u[i]);
		}
		fp48_new(t);

		fp48_copy(t, a);
		if (b[0] == 0) {
			for (j = 0, i = 1; i < len; i++) {
				k = (b[i] < 0 ? -b[i] : b[i]);
				for (; j < k; j++) {
					fp48_sqr_pck(t, t);
				}
				if (b[i] < 0) {
					fp48_inv_cyc(u[i - 1], t);
				} else {
					fp48_copy(u[i - 1], t);
				}
			}

			fp48_back_cyc_sim(u, u, w - 1);

			fp48_copy(c, a);
			for (i = 0; i < w - 1; i++) {
				fp48_mul(c, c, u[i]);
			}
		} else {
			for (j = 0, i = 0; i < len; i++) {
				k = (b[i] < 0 ? -b[i] : b[i]);
				for (; j < k; j++) {
					fp48_sqr_pck(t, t);
				}
				if (b[i] < 0) {
					fp48_inv_cyc(u[i], t);
				} else {
					fp48_copy(u[i], t);
				}
			}

			fp48_back_cyc_sim(u, u, w);

			fp48_copy(c, u[0]);
			for (i = 1; i < w; i++) {
				fp48_mul(c, c, u[i]);
			}
		}

		if (sign == RLC_NEG) {
			fp48_inv_cyc(c, c);
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		for (i = 0; i < w; i++) {
			fp48_free(u[i]);
		}
		fp48_free(t);
		RLC_FREE(u);
	}
}


void fp54_exp_cyc_sps(fp54_t c, const fp54_t a, const int *b, int len, int sign) {
	int i, j, k, w = len;
    fp54_t t, *u = RLC_ALLOCA(fp54_t, w);

	if (len == 0) {
		RLC_FREE(u);
		fp54_set_dig(c, 1);
		return;
	}

	fp54_null(t);

	RLC_TRY {
		if (u == NULL) {
			RLC_THROW(ERR_NO_MEMORY);
		}
		for (i = 0; i < w; i++) {
			fp54_null(u[i]);
			fp54_new(u[i]);
		}
		fp54_new(t);

		fp54_copy(t, a);
		if (b[0] == 0) {
			for (j = 0, i = 1; i < len; i++) {
				k = (b[i] < 0 ? -b[i] : b[i]);
				for (; j < k; j++) {
					fp54_sqr_pck(t, t);
				}
				if (b[i] < 0) {
					fp54_inv_cyc(u[i - 1], t);
				} else {
					fp54_copy(u[i - 1], t);
				}
			}

			fp54_back_cyc_sim(u, u, w - 1);

			fp54_copy(c, a);
			for (i = 0; i < w - 1; i++) {
				fp54_mul(c, c, u[i]);
			}
		} else {
			for (j = 0, i = 0; i < len; i++) {
				k = (b[i] < 0 ? -b[i] : b[i]);
				for (; j < k; j++) {
					fp54_sqr_pck(t, t);
				}
				if (b[i] < 0) {
					fp54_inv_cyc(u[i], t);
				} else {
					fp54_copy(u[i], t);
				}
			}

			fp54_back_cyc_sim(u, u, w);

			fp54_copy(c, u[0]);
			for (i = 1; i < w; i++) {
				fp54_mul(c, c, u[i]);
			}
		}

		if (sign == RLC_NEG) {
			fp54_inv_cyc(c, c);
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		for (i = 0; i < w; i++) {
			fp54_free(u[i]);
		}
		fp54_free(t);
		RLC_FREE(u);
	}
}


void fp12_exp_dig(fp12_t c, const fp12_t a, dig_t b) {
	bn_t _b;
	fp12_t t, v;
	int8_t u, naf[RLC_DIG + 1];
	int l;

	if (b == 0) {
		fp12_set_dig(c, 1);
		return;
	}

	bn_null(_b);
	fp12_null(t);
	fp12_null(v);

	RLC_TRY {
		bn_new(_b);
		fp12_new(t);
		fp12_new(v);

		fp12_copy(t, a);

		if (fp12_test_cyc(a)) {
			fp12_inv_cyc(v, a);
			bn_set_dig(_b, b);

			l = RLC_DIG + 1;
			bn_rec_naf(naf, &l, _b, 2);

			for (int i = bn_bits(_b) - 2; i >= 0; i--) {
				fp12_sqr_cyc(t, t);

				u = naf[i];
				if (u > 0) {
					fp12_mul(t, t, a);
				} else if (u < 0) {
					fp12_mul(t, t, v);
				}
			}
		} else {
			for (int i = util_bits_dig(b) - 2; i >= 0; i--) {
				fp12_sqr(t, t);
				if (b & ((dig_t)1 << i)) {
					fp12_mul(t, t, a);
				}
			}
		}

		fp12_copy(c, t);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(_b);
		fp12_free(t);
		fp12_free(v);
	}
}


void fp24_exp_dig(fp24_t c, const fp24_t a, dig_t b) {
	bn_t _b;
	fp24_t t, v;
	int8_t u, naf[RLC_DIG + 1];
	int l;

	if (b == 0) {
		fp24_set_dig(c, 1);
		return;
	}

	bn_null(_b);
	fp24_null(t);
	fp24_null(v);

	RLC_TRY {
		bn_new(_b);
		fp24_new(t);
		fp24_new(v);

		fp24_copy(t, a);

		if (fp24_test_cyc(a)) {
			fp24_inv_cyc(v, a);
			bn_set_dig(_b, b);

			l = RLC_DIG + 1;
			bn_rec_naf(naf, &l, _b, 2);

			for (int i = bn_bits(_b) - 2; i >= 0; i--) {
				fp24_sqr_cyc(t, t);

				u = naf[i];
				if (u > 0) {
					fp24_mul(t, t, a);
				} else if (u < 0) {
					fp24_mul(t, t, v);
				}
			}
		} else {
			for (int i = util_bits_dig(b) - 2; i >= 0; i--) {
				fp24_sqr(t, t);
				if (b & ((dig_t)1 << i)) {
					fp24_mul(t, t, a);
				}
			}
		}

		fp24_copy(c, t);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(_b);
		fp24_free(t);
		fp24_free(v);
	}
}


void fp48_exp_dig(fp48_t c, const fp48_t a, dig_t b) {
	bn_t _b;
	fp48_t t, v;
	int8_t u, naf[RLC_DIG + 1];
	int l;

	if (b == 0) {
		fp48_set_dig(c, 1);
		return;
	}

	bn_null(_b);
	fp48_null(t);
	fp48_null(v);

	RLC_TRY {
		bn_new(_b);
		fp48_new(t);
		fp48_new(v);

		fp48_copy(t, a);

		if (fp48_test_cyc(a)) {
			fp48_inv_cyc(v, a);
			bn_set_dig(_b, b);

			l = RLC_DIG + 1;
			bn_rec_naf(naf, &l, _b, 2);

			for (int i = bn_bits(_b) - 2; i >= 0; i--) {
				fp48_sqr_cyc(t, t);

				u = naf[i];
				if (u > 0) {
					fp48_mul(t, t, a);
				} else if (u < 0) {
					fp48_mul(t, t, v);
				}
			}
		} else {
			for (int i = util_bits_dig(b) - 2; i >= 0; i--) {
				fp48_sqr(t, t);
				if (b & ((dig_t)1 << i)) {
					fp48_mul(t, t, a);
				}
			}
		}

		fp48_copy(c, t);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(_b);
		fp48_free(t);
		fp48_free(v);
	}
}


void fp54_exp_dig(fp54_t c, const fp54_t a, dig_t b) {
	bn_t _b;
	fp54_t t, v;
	int8_t u, naf[RLC_DIG + 1];
	int l;

	if (b == 0) {
		fp54_set_dig(c, 1);
		return;
	}

	bn_null(_b);
	fp54_null(t);
	fp54_null(v);

	RLC_TRY {
		bn_new(_b);
		fp54_new(t);
		fp54_new(v);

		fp54_copy(t, a);

		if (fp54_test_cyc(a)) {
			fp54_inv_cyc(v, a);
			bn_set_dig(_b, b);

			l = RLC_DIG + 1;
			bn_rec_naf(naf, &l, _b, 2);

			for (int i = bn_bits(_b) - 2; i >= 0; i--) {
				fp54_sqr_cyc(t, t);

				u = naf[i];
				if (u > 0) {
					fp54_mul(t, t, a);
				} else if (u < 0) {
					fp54_mul(t, t, v);
				}
			}
		} else {
			for (int i = util_bits_dig(b) - 2; i >= 0; i--) {
				fp54_sqr(t, t);
				if (b & ((dig_t)1 << i)) {
					fp54_mul(t, t, a);
				}
			}
		}

		fp54_copy(c, t);
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		bn_free(_b);
		fp54_free(t);
		fp54_free(v);
	}
}


void fp4_read_bin(fp4_t a, const uint8_t *bin, int len) {
	if (len != 4 * RLC_FP_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}
	fp2_read_bin(a[0], bin, 2 * RLC_FP_BYTES);
	fp2_read_bin(a[1], bin + 2 * RLC_FP_BYTES, 2 * RLC_FP_BYTES);
}


void fp6_read_bin(fp6_t a, const uint8_t *bin, int len) {
	if (len != 6 * RLC_FP_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}
	fp2_read_bin(a[0], bin, 2 * RLC_FP_BYTES);
	fp2_read_bin(a[1], bin + 2 * RLC_FP_BYTES, 2 * RLC_FP_BYTES);
	fp2_read_bin(a[2], bin + 4 * RLC_FP_BYTES, 2 * RLC_FP_BYTES);
}


void fp2_read_bin(fp2_t a, const uint8_t *bin, int len) {
	if (len != RLC_FP_BYTES + 1 && len != 2 * RLC_FP_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}
	if (len == RLC_FP_BYTES + 1) {
		fp_read_bin(a[0], bin, RLC_FP_BYTES);
		fp_zero(a[1]);
		fp_set_bit(a[1], 0, bin[RLC_FP_BYTES]);
		fp2_upk(a, a);
	}
	if (len == 2 * RLC_FP_BYTES) {
		fp_read_bin(a[0], bin, RLC_FP_BYTES);
		fp_read_bin(a[1], bin + RLC_FP_BYTES, RLC_FP_BYTES);
	}
}


void fp48_write_bin(uint8_t *bin, int len, const fp48_t a, int pack) {
	fp48_t t;

	fp48_null(t);

	RLC_TRY {
		fp48_new(t);

		if (pack) {
			if (len != 32 * RLC_FP_BYTES) {
				RLC_THROW(ERR_NO_BUFFER);
			}
			fp48_pck(t, a);
			fp8_write_bin(bin, 8 * RLC_FP_BYTES, a[0][1]);
			fp8_write_bin(bin + 8 * RLC_FP_BYTES, 8 * RLC_FP_BYTES, a[0][2]);
			fp8_write_bin(bin + 16 * RLC_FP_BYTES, 8 * RLC_FP_BYTES, a[1][0]);
			fp8_write_bin(bin + 24 * RLC_FP_BYTES, 8 * RLC_FP_BYTES, a[1][2]);
		} else {
			if (len != 48 * RLC_FP_BYTES) {
				RLC_THROW(ERR_NO_BUFFER);
			}
			fp24_write_bin(bin, 24 * RLC_FP_BYTES, a[0], 0);
			fp24_write_bin(bin + 24 * RLC_FP_BYTES, 24 * RLC_FP_BYTES, a[1], 0);
		}
	} RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	} RLC_FINALLY {
		fp48_free(t);
	}
}


void fp8_write_bin(uint8_t *bin, int len, const fp8_t a) {
	if (len != 8 * RLC_FP_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}
	fp4_write_bin(bin, 4 * RLC_FP_BYTES, a[0]);
	fp4_write_bin(bin + 4 * RLC_FP_BYTES, 4 * RLC_FP_BYTES, a[1]);
}


void fp12_write_bin(uint8_t *bin, int len, const fp12_t a, int pack) {
	fp12_t t;

	fp12_null(t);

	RLC_TRY {
		fp12_new(t);

		if (pack) {
			if (len != 8 * RLC_FP_BYTES) {
				RLC_THROW(ERR_NO_BUFFER);
			}
			fp12_pck(t, a);
			fp2_write_bin(bin, 2 * RLC_FP_BYTES, a[0][1], 0);
			fp2_write_bin(bin + 2 * RLC_FP_BYTES, 2 * RLC_FP_BYTES, a[0][2], 0);
			fp2_write_bin(bin + 4 * RLC_FP_BYTES, 2 * RLC_FP_BYTES, a[1][0], 0);
			fp2_write_bin(bin + 6 * RLC_FP_BYTES, 2 * RLC_FP_BYTES, a[1][2], 0);
		} else {
			if (len != 12 * RLC_FP_BYTES) {
				RLC_THROW(ERR_NO_BUFFER);
			}
			fp6_write_bin(bin, 6 * RLC_FP_BYTES, a[0]);
			fp6_write_bin(bin + 6 * RLC_FP_BYTES, 6 * RLC_FP_BYTES, a[1]);
		}
	} RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	} RLC_FINALLY {
		fp12_free(t);
	}
}


void fp24_write_bin(uint8_t *bin, int len, const fp24_t a, int pack) {
	fp24_t t;

	fp24_null(t);

	RLC_TRY {
		fp24_new(t);

		if (pack) {
			if (len != 16 * RLC_FP_BYTES) {
				RLC_THROW(ERR_NO_BUFFER);
			}
			fp24_pck(t, a);
			fp4_write_bin(bin, 4 * RLC_FP_BYTES, a[1][0]);
			fp4_write_bin(bin + 4 * RLC_FP_BYTES, 4 * RLC_FP_BYTES, a[1][1]);
			fp4_write_bin(bin + 8 * RLC_FP_BYTES, 4 * RLC_FP_BYTES, a[2][0]);
			fp4_write_bin(bin + 12 * RLC_FP_BYTES, 4 * RLC_FP_BYTES, a[2][1]);
		} else {
			if (len != 24 * RLC_FP_BYTES) {
				RLC_THROW(ERR_NO_BUFFER);
			}
			fp8_write_bin(bin, 8 * RLC_FP_BYTES, a[0]);
			fp8_write_bin(bin + 8 * RLC_FP_BYTES, 8 * RLC_FP_BYTES, a[1]);
			fp8_write_bin(bin + 16 * RLC_FP_BYTES, 8 * RLC_FP_BYTES, a[2]);
		}
	} RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	} RLC_FINALLY {
		fp24_free(t);
	}
}


void fp12_read_bin(fp12_t a, const uint8_t *bin, int len) {
	if (len != 8 * RLC_FP_BYTES && len != 12 * RLC_FP_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}
	if (len == 8 * RLC_FP_BYTES) {
		fp2_zero(a[0][0]);
		fp2_read_bin(a[0][1], bin, 2 * RLC_FP_BYTES);
		fp2_read_bin(a[0][2], bin + 2 * RLC_FP_BYTES, 2 * RLC_FP_BYTES);
		fp2_read_bin(a[1][0], bin + 4 * RLC_FP_BYTES, 2 * RLC_FP_BYTES);
		fp2_zero(a[1][1]);
		fp2_read_bin(a[1][2], bin + 6 * RLC_FP_BYTES, 2 * RLC_FP_BYTES);
		fp12_back_cyc(a, a);
	}
	if (len == 12 * RLC_FP_BYTES) {
		fp6_read_bin(a[0], bin, 6 * RLC_FP_BYTES);
		fp6_read_bin(a[1], bin + 6 * RLC_FP_BYTES, 6 * RLC_FP_BYTES);
	}
}


void fp2_write_bin(uint8_t *bin, int len, const fp2_t a, int pack) {
	fp2_t t;

	fp2_null(t);

	RLC_TRY {
		fp2_new(t);

		if (pack && fp2_test_cyc(a)) {
			if (len < RLC_FP_BYTES + 1) {
				RLC_THROW(ERR_NO_BUFFER);
				return;
			} else {
				fp2_pck(t, a);
				fp_write_bin(bin, RLC_FP_BYTES, t[0]);
				bin[RLC_FP_BYTES] = fp_get_bit(t[1], 0);
			}
		} else {
			if (len < 2 * RLC_FP_BYTES) {
				RLC_THROW(ERR_NO_BUFFER);
				return;
			} else {
				fp_write_bin(bin, RLC_FP_BYTES, a[0]);
				fp_write_bin(bin + RLC_FP_BYTES, RLC_FP_BYTES, a[1]);
			}
		}
	} RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		fp2_free(t);
	}
}


void fp9_read_bin(fp9_t a, const uint8_t *bin, int len) {
	if (len != 9 * RLC_FP_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}
	fp3_read_bin(a[0], bin, 3 * RLC_FP_BYTES);
	fp3_read_bin(a[1], bin + 3 * RLC_FP_BYTES, 3 * RLC_FP_BYTES);
	fp3_read_bin(a[2], bin + 6 * RLC_FP_BYTES, 3 * RLC_FP_BYTES);
}


void fp4_write_bin(uint8_t *bin, int len, const fp4_t a) {
	if (len != 4 * RLC_FP_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}
	fp2_write_bin(bin, 2 * RLC_FP_BYTES, a[0], 0);
	fp2_write_bin(bin + 2 * RLC_FP_BYTES, 2 * RLC_FP_BYTES, a[1], 0);
}


void fp9_write_bin(uint8_t *bin, int len, const fp9_t a) {
	if (len != 9 * RLC_FP_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}
	fp3_write_bin(bin, 3 * RLC_FP_BYTES, a[0]);
	fp3_write_bin(bin + 3 * RLC_FP_BYTES, 3 * RLC_FP_BYTES, a[1]);
	fp3_write_bin(bin + 6 * RLC_FP_BYTES, 3 * RLC_FP_BYTES, a[2]);
}


void fp18_write_bin(uint8_t *bin, int len, const fp18_t a) {
	if (len != 18 * RLC_FP_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}
	fp9_write_bin(bin, 9 * RLC_FP_BYTES, a[0]);
	fp9_write_bin(bin + 9 * RLC_FP_BYTES, 9 * RLC_FP_BYTES, a[1]);
}


void fp24_read_bin(fp24_t a, const uint8_t *bin, int len) {
	if (len != 16 * RLC_FP_BYTES && len != 24 * RLC_FP_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}
	if (len == 16 * RLC_FP_BYTES) {
		fp4_zero(a[0][0]);
		fp4_zero(a[0][1]);
		fp4_read_bin(a[1][0], bin, 4 * RLC_FP_BYTES);
		fp4_read_bin(a[1][1], bin + 4 * RLC_FP_BYTES, 4 * RLC_FP_BYTES);
		fp4_read_bin(a[2][0], bin + 8 * RLC_FP_BYTES, 4 * RLC_FP_BYTES);
		fp4_read_bin(a[2][1], bin + 12 * RLC_FP_BYTES, 4 * RLC_FP_BYTES);
		fp24_back_cyc(a, a);
	}
	if (len == 24 * RLC_FP_BYTES) {
		fp8_read_bin(a[0], bin, 8 * RLC_FP_BYTES);
		fp8_read_bin(a[1], bin + 8 * RLC_FP_BYTES, 8 * RLC_FP_BYTES);
		fp8_read_bin(a[2], bin + 16 * RLC_FP_BYTES, 8 * RLC_FP_BYTES);
	}
}


void fp6_write_bin(uint8_t *bin, int len, const fp6_t a) {
	if (len != 6 * RLC_FP_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}
	fp2_write_bin(bin, 2 * RLC_FP_BYTES, a[0], 0);
	fp2_write_bin(bin + 2 * RLC_FP_BYTES, 2 * RLC_FP_BYTES, a[1], 0);
	fp2_write_bin(bin + 4 * RLC_FP_BYTES, 2 * RLC_FP_BYTES, a[2], 0);
}


void fp54_read_bin(fp54_t a, const uint8_t *bin, int len) {
	if (len != 36 * RLC_FP_BYTES && len != 54 * RLC_FP_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}
	if (len == 36 * RLC_FP_BYTES) {
		fp9_zero(a[0][0]);
		fp9_zero(a[0][1]);
		fp9_read_bin(a[1][0], bin, 9 * RLC_FP_BYTES);
		fp9_read_bin(a[1][1], bin + 9 * RLC_FP_BYTES, 9 * RLC_FP_BYTES);
		fp9_read_bin(a[2][0], bin + 18 * RLC_FP_BYTES, 9 * RLC_FP_BYTES);
		fp9_read_bin(a[2][1], bin + 27 * RLC_FP_BYTES, 9 * RLC_FP_BYTES);
		fp54_back_cyc(a, a);
	}
	if (len == 54 * RLC_FP_BYTES) {
		fp18_read_bin(a[0], bin, 18 * RLC_FP_BYTES);
		fp18_read_bin(a[1], bin + 18 * RLC_FP_BYTES, 18 * RLC_FP_BYTES);
		fp18_read_bin(a[2], bin + 36 * RLC_FP_BYTES, 18 * RLC_FP_BYTES);
	}
}


void fp8_read_bin(fp8_t a, const uint8_t *bin, int len) {
	if (len != 8 * RLC_FP_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}
	fp4_read_bin(a[0], bin, 4 * RLC_FP_BYTES);
	fp4_read_bin(a[1], bin + 4 * RLC_FP_BYTES, 4 * RLC_FP_BYTES);
}


void fp18_read_bin(fp18_t a, const uint8_t *bin, int len) {
	if (len != 18 * RLC_FP_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}
	fp9_read_bin(a[0], bin, 9 * RLC_FP_BYTES);
	fp9_read_bin(a[1], bin + 9 * RLC_FP_BYTES, 9 * RLC_FP_BYTES);
}


void fp48_read_bin(fp48_t a, const uint8_t *bin, int len) {
	if (len != 32 * RLC_FP_BYTES && len != 48 * RLC_FP_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}
	if (len == 32 * RLC_FP_BYTES) {
		fp8_zero(a[0][0]);
		fp8_read_bin(a[0][1], bin, 8 * RLC_FP_BYTES);
		fp8_read_bin(a[0][2], bin + 8 * RLC_FP_BYTES, 8 * RLC_FP_BYTES);
		fp8_read_bin(a[1][0], bin + 16 * RLC_FP_BYTES, 8 * RLC_FP_BYTES);
		fp8_zero(a[1][1]);
		fp8_read_bin(a[1][2], bin + 24 * RLC_FP_BYTES, 8 * RLC_FP_BYTES);
		fp48_back_cyc(a, a);
	}
	if (len == 48 * RLC_FP_BYTES) {
		fp24_read_bin(a[0], bin, 24 * RLC_FP_BYTES);
		fp24_read_bin(a[1], bin + 24 * RLC_FP_BYTES, 24 * RLC_FP_BYTES);
	}
}


void fp54_write_bin(uint8_t *bin, int len, const fp54_t a, int pack) {
	fp54_t t;

	fp54_null(t);

	RLC_TRY {
		fp54_new(t);

		if (pack) {
			if (len != 36 * RLC_FP_BYTES) {
				RLC_THROW(ERR_NO_BUFFER);
			}
			fp54_pck(t, a);
			fp9_write_bin(bin, 9 * RLC_FP_BYTES, a[1][0]);
			fp9_write_bin(bin + 9 * RLC_FP_BYTES, 9 * RLC_FP_BYTES, a[1][1]);
			fp9_write_bin(bin + 18 * RLC_FP_BYTES, 9 * RLC_FP_BYTES, a[2][0]);
			fp9_write_bin(bin + 27 * RLC_FP_BYTES, 9 * RLC_FP_BYTES, a[2][1]);
		} else {
			if (len != 54 * RLC_FP_BYTES) {
				RLC_THROW(ERR_NO_BUFFER);
			}
			fp18_write_bin(bin, 18 * RLC_FP_BYTES, a[0]);
			fp18_write_bin(bin + 18 * RLC_FP_BYTES, 18 * RLC_FP_BYTES, a[1]);
			fp18_write_bin(bin + 36 * RLC_FP_BYTES, 18 * RLC_FP_BYTES, a[2]);
		}
	} RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	} RLC_FINALLY {
		fp54_free(t);
	}
}


void fp3_read_bin(fp3_t a, const uint8_t *bin, int len) {
	if (len != 3 * RLC_FP_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}
	fp_read_bin(a[0], bin, RLC_FP_BYTES);
	fp_read_bin(a[1], bin + RLC_FP_BYTES, RLC_FP_BYTES);
	fp_read_bin(a[2], bin + 2 * RLC_FP_BYTES, RLC_FP_BYTES);
}


void fp3_write_bin(uint8_t *bin, int len, const fp3_t a) {
	if (len != 3 * RLC_FP_BYTES) {
		RLC_THROW(ERR_NO_BUFFER);
		return;
	}
	fp_write_bin(bin, RLC_FP_BYTES, a[0]);
	fp_write_bin(bin + RLC_FP_BYTES, RLC_FP_BYTES, a[1]);
	fp_write_bin(bin + 2 * RLC_FP_BYTES, RLC_FP_BYTES, a[2]);
}


void md_map_b2s256(uint8_t *hash, const uint8_t *msg, int len) {
	memset(hash, 0, RLC_MD_LEN_B2S256);
	blake2s(hash, RLC_MD_LEN_B2S256, msg, len, NULL, 0);
}


void md_map_b2s160(uint8_t *hash, const uint8_t *msg, int len) {
	memset(hash, 0, RLC_MD_LEN_B2S160);
	blake2s(hash, RLC_MD_LEN_B2S160, msg, len, NULL, 0);
}


void md_hmac(uint8_t *mac, const uint8_t *in, int in_len, const uint8_t *key,
    int key_len) {
#if MD_MAP == SH224 || MD_MAP == SH256 || MD_MAP == B2S160 || MD_MAP == B2S256
  #define block_size 64
#elif MD_MAP == SH384 || MD_MAP == SH512
  #define block_size  128
#endif
    uint8_t opad[block_size + RLC_MD_LEN];
    uint8_t *ipad = RLC_ALLOCA(uint8_t, block_size + in_len);
	uint8_t _key[RLC_MAX(RLC_MD_LEN, block_size)];

    if (ipad == NULL) {
        RLC_THROW(ERR_NO_MEMORY);
		return;
    }

	if (key_len > block_size) {
		md_map(_key, key, key_len);
		key = _key;
		key_len = RLC_MD_LEN;
	}
	if (key_len <= block_size) {
		memcpy(_key, key, key_len);
		memset(_key + key_len, 0, block_size - key_len);
		key = _key;
	}
	for (int i = 0; i < block_size; i++) {
		opad[i] = 0x5C ^ key[i];
		ipad[i] = 0x36 ^ key[i];
	}
	memcpy(ipad + block_size, in, in_len);
	md_map(opad + block_size, ipad, block_size + in_len);
	md_map(mac, opad, block_size + RLC_MD_LEN);

    RLC_FREE(ipad);
}


void md_kdf(uint8_t *key, int key_len, const uint8_t *in,
		int in_len) {
	uint32_t i, j, d;
	uint8_t* buffer = RLC_ALLOCA(uint8_t, in_len + sizeof(uint32_t));
	uint8_t* t = RLC_ALLOCA(uint8_t, key_len + RLC_MD_LEN);
	if (buffer == NULL || t == NULL) {
		RLC_FREE(buffer);
		RLC_FREE(t);
		RLC_THROW(ERR_NO_MEMORY);
		return;
	}

	/* d = ceil(kLen/hLen). */
	d = RLC_CEIL(key_len, RLC_MD_LEN);
	memcpy(buffer, in, in_len);
	for (i = 1; i <= d; i++) {
		j = util_conv_big(i);
		/* c = integer_to_string(c, 4). */
		memcpy(buffer + in_len, &j, sizeof(uint32_t));
		/* t = t || hash(z || c). */
		md_map(t + (i - 1) * RLC_MD_LEN, buffer, in_len + sizeof(uint32_t));
	}
	memcpy(key, t, key_len);

	RLC_FREE(buffer);
	RLC_FREE(t);
}


void md_mgf(uint8_t *key, int key_len, const uint8_t *in,
		int in_len) {
	uint32_t i, j, d;
	uint8_t *buffer = RLC_ALLOCA(uint8_t, in_len + sizeof(uint32_t));
	uint8_t *t = RLC_ALLOCA(uint8_t, key_len + RLC_MD_LEN);

	if (buffer == NULL || t == NULL) {
		RLC_FREE(buffer);
		RLC_FREE(t);
		RLC_THROW(ERR_NO_MEMORY);
		return;
	}

	/* d = ceil(kLen/hLen). */
	d = RLC_CEIL(key_len, RLC_MD_LEN);
	memcpy(buffer, in, in_len);
	for (i = 0; i < d; i++) {
		j = util_conv_big(i);
		/* c = integer_to_string(c, 4). */
		memcpy(buffer + in_len, &j, sizeof(uint32_t));
		/* t = t || hash(z || c). */
		md_map(t + i * RLC_MD_LEN, buffer, in_len + sizeof(uint32_t));
	}
	memcpy(key, t, key_len);

	RLC_FREE(buffer);
	RLC_FREE(t);
}


void md_map_sh224(uint8_t *hash, const uint8_t *msg, int len) {
	SHA224Context ctx;

	if (SHA224Reset(&ctx) != shaSuccess) {
		RLC_THROW(ERR_NO_VALID);
		return;
	}
	if (SHA224Input(&ctx, msg, len) != shaSuccess) {
		RLC_THROW(ERR_NO_VALID);
		return;
	}
	if (SHA224Result(&ctx, hash) != shaSuccess) {
		RLC_THROW(ERR_NO_VALID);
		return;
	}
}


void md_map_sh256(uint8_t *hash, const uint8_t *msg, int len) {
	SHA256Context ctx;

	if (SHA256Reset(&ctx) != shaSuccess) {
		RLC_THROW(ERR_NO_VALID);
		return;
	}
	if (SHA256Input(&ctx, msg, len) != shaSuccess) {
		RLC_THROW(ERR_NO_VALID);
		return;
	}
	if (SHA256Result(&ctx, hash) != shaSuccess) {
		RLC_THROW(ERR_NO_VALID);
		return;
	}
}


void md_map_sh384(uint8_t *hash, const uint8_t *msg, int len) {
	SHA384Context ctx;

	if (SHA384Reset(&ctx) != shaSuccess) {
		RLC_THROW(ERR_NO_VALID);
		return;
	}
	if (SHA384Input(&ctx, msg, len) != shaSuccess) {
		RLC_THROW(ERR_NO_VALID);
		return;
	}
	if (SHA384Result(&ctx, hash) != shaSuccess) {
		RLC_THROW(ERR_NO_VALID);
		return;
	}
}


void md_map_sh512(uint8_t *hash, const uint8_t *msg, int len) {
	SHA512Context ctx;

	if (SHA512Reset(&ctx) != shaSuccess) {
		RLC_THROW(ERR_NO_VALID);
		return;
	}
	if (SHA512Input(&ctx, msg, len) != shaSuccess) {
		RLC_THROW(ERR_NO_VALID);
		return;
	}
	if (SHA512Result(&ctx, hash) != shaSuccess) {
		RLC_THROW(ERR_NO_VALID);
		return;
	}
}


static void pp_mil_k12(fp12_t r, ep2_t *t, ep2_t *q, ep_t *p, int m, bn_t a) {
	fp12_t l;
	ep_t *_p = RLC_ALLOCA(ep_t, m);
	ep2_t *_q = RLC_ALLOCA(ep2_t, m);
	int i, j, len = bn_bits(a) + 1;
	int8_t s[RLC_FP_BITS + 1];

	if (m == 0) {
		return;
	}

	fp12_null(l);

	RLC_TRY {
		fp12_new(l);
		if (_p == NULL || _q == NULL) {
			RLC_THROW(ERR_NO_MEMORY);
		}
		for (j = 0; j < m; j++) {
			ep_null(_p[j]);
			ep2_null(_q[j]);
			ep_new(_p[j]);
			ep2_new(_q[j]);
			ep2_copy(t[j], q[j]);
			ep2_neg(_q[j], q[j]);
#if EP_ADD == BASIC
			ep_neg(_p[j], p[j]);
#else
			fp_add(_p[j]->x, p[j]->x, p[j]->x);
			fp_add(_p[j]->x, _p[j]->x, p[j]->x);
			fp_neg(_p[j]->y, p[j]->y);
#endif
		}

		fp12_zero(l);
		bn_rec_naf(s, &len, a, 2);
		pp_dbl_k12(r, t[0], t[0], _p[0]);
		for (j = 1; j < m; j++) {
			pp_dbl_k12(l, t[j], t[j], _p[j]);
			fp12_mul_dxs(r, r, l);
		}
		if (s[len - 2] > 0) {
			for (j = 0; j < m; j++) {
				pp_add_k12(l, t[j], q[j], p[j]);
				fp12_mul_dxs(r, r, l);
			}
		}
		if (s[len - 2] < 0) {
			for (j = 0; j < m; j++) {
				pp_add_k12(l, t[j], _q[j], p[j]);
				fp12_mul_dxs(r, r, l);
			}
		}

		for (i = len - 3; i >= 0; i--) {
			fp12_sqr(r, r);
			for (j = 0; j < m; j++) {
				pp_dbl_k12(l, t[j], t[j], _p[j]);
				fp12_mul_dxs(r, r, l);
				if (s[i] > 0) {
					pp_add_k12(l, t[j], q[j], p[j]);
					fp12_mul_dxs(r, r, l);
				}
				if (s[i] < 0) {
					pp_add_k12(l, t[j], _q[j], p[j]);
					fp12_mul_dxs(r, r, l);
				}
			}
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		fp12_free(l);
		for (j = 0; j < m; j++) {
			ep_free(_p[j]);
			ep2_free(_q[j]);
		}
		RLC_FREE(_p);
		RLC_FREE(_q);
	}
}


static void pp_mil_k24(fp24_t r, ep4_t *t, ep4_t *q, ep_t *p, int m, bn_t a) {
	fp24_t l;
	ep_t *_p = RLC_ALLOCA(ep_t, m);
	ep4_t *_q = RLC_ALLOCA(ep4_t, m);
	int i, j, len = bn_bits(a) + 1;
	int8_t s[RLC_FP_BITS + 1];

	if (m == 0) {
		return;
	}

	fp24_null(l);

	RLC_TRY {
		fp24_new(l);
		if (_p == NULL || _q == NULL) {
			RLC_THROW(ERR_NO_MEMORY);
		}
		for (j = 0; j < m; j++) {
			ep_null(_p[j]);
			ep4_null(_q[j]);
			ep_new(_p[j]);
			ep4_new(_q[j]);
			ep4_copy(t[j], q[j]);
			ep4_neg(_q[j], q[j]);
#if EP_ADD == BASIC
			ep_neg(_p[j], p[j]);
#else
			fp_add(_p[j]->x, p[j]->x, p[j]->x);
			fp_add(_p[j]->x, _p[j]->x, p[j]->x);
			fp_neg(_p[j]->y, p[j]->y);
#endif
		}

		fp24_zero(l);
		bn_rec_naf(s, &len, a, 2);
		pp_dbl_k24(r, t[0], t[0], _p[0]);
		for (j = 1; j < m; j++) {
			pp_dbl_k24(l, t[j], t[j], _p[j]);
			fp24_mul_dxs(r, r, l);
		}
		if (s[len - 2] > 0) {
			for (j = 0; j < m; j++) {
				pp_add_k24(l, t[j], q[j], p[j]);
				fp24_mul_dxs(r, r, l);
			}
		}
		if (s[len - 2] < 0) {
			for (j = 0; j < m; j++) {
				pp_add_k24(l, t[j], _q[j], p[j]);
				fp24_mul_dxs(r, r, l);
			}
		}

		for (i = len - 3; i >= 0; i--) {
			fp24_sqr(r, r);
			for (j = 0; j < m; j++) {
				pp_dbl_k24(l, t[j], t[j], _p[j]);
				fp24_mul_dxs(r, r, l);
				if (s[i] > 0) {
					pp_add_k24(l, t[j], q[j], p[j]);
					fp24_mul_dxs(r, r, l);
				}
				if (s[i] < 0) {
					pp_add_k24(l, t[j], _q[j], p[j]);
					fp24_mul_dxs(r, r, l);
				}
			}
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		fp24_free(l);
		for (j = 0; j < m; j++) {
			ep_free(_p[j]);
			ep4_free(_q[j]);
		}
		RLC_FREE(_p);
		RLC_FREE(_q);
	}
}


static void pp_mil_k48(fp48_t r, const fp8_t qx, const fp8_t qy, const ep_t p,
		const bn_t a) {
	fp48_t l;
	ep_t _p;
	fp8_t rx, ry, rz, qn;
	int i, len = bn_bits(a) + 1;
	int8_t s[RLC_FP_BITS + 1];

	fp48_null(l);
	ep_null(_p);
	fp8_null(rx);
	fp8_null(ry);
	fp8_null(rz);
	fp8_null(qn);

	RLC_TRY {
		fp48_new(l);
		ep_new(_p);
		fp8_new(rx);
		fp8_new(ry);
		fp8_new(rz);
		fp8_new(qn);

		fp48_zero(l);
		fp8_copy(rx, qx);
		fp8_copy(ry, qy);
		fp8_set_dig(rz, 1);
#if EP_ADD == BASIC
		ep_neg(_p, p);
#else
		fp_add(_p->x, p->x, p->x);
		fp_add(_p->x, _p->x, p->x);
		fp_neg(_p->y, p->y);
#endif
		fp8_neg(qn, qy);

		bn_rec_naf(s, &len, a, 2);
		for (i = len - 2; i >= 0; i--) {
			fp48_sqr(r, r);
			pp_dbl_k48(l, rx, ry, rz, _p);
			fp48_mul_dxs(r, r, l);
			if (s[i] > 0) {
				pp_add_k48(l, rx, ry, rz, qx, qy, p);
				fp48_mul_dxs(r, r, l);
			}
			if (s[i] < 0) {
				pp_add_k48(l, rx, ry, rz, qx, qn, p);
				fp48_mul_dxs(r, r, l);
			}
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		fp48_free(l);
		ep_free(_p);
		fp8_free(rx);
		fp8_free(ry);
		fp8_free(rz);
		fp8_free(qn);
	}
}


static void pp_mil_k8(fp8_t r, ep2_t *t, ep2_t *q, ep_t *p, int m, bn_t a) {
	fp8_t l;
	ep_t *_p = RLC_ALLOCA(ep_t, m);
	ep2_t *_q = RLC_ALLOCA(ep2_t, m);
	int i, j, len = bn_bits(a) + 1;
	int8_t s[RLC_FP_BITS + 1];

	if (m == 0) {
		return;
	}

	fp8_null(l);

	RLC_TRY {
		fp8_new(l);
		if (_p == NULL || _q == NULL) {
			RLC_THROW(ERR_NO_MEMORY);
		}

		for (j = 0; j < m; j++) {
			ep_null(_p[j]);
			ep2_null(_q[j]);
			ep_new(_p[j]);
			ep2_new(_q[j]);
			ep2_copy(t[j], q[j]);
			ep2_neg(_q[j], q[j]);

#if EP_ADD == BASIC
			ep_neg(_p[j], p[j]);
#else
			fp_neg(_p[j]->x, p[j]->x);
			fp_copy(_p[j]->y, p[j]->y);
#endif
		}

		fp8_zero(l);
		bn_rec_naf(s, &len, a, 2);
		for (i = len - 2; i >= 0; i--) {
			fp8_sqr(r, r);
			for (j = 0; j < m; j++) {
				pp_dbl_k8(l, t[j], t[j], _p[j]);
				fp8_mul(r, r, l);
				if (s[i] > 0) {
					pp_add_k8(l, t[j], q[j], _p[j]);
					fp8_mul_dxs(r, r, l);
				}
				if (s[i] < 0) {
					pp_add_k8(l, t[j], _q[j], _p[j]);
					fp8_mul_dxs(r, r, l);
				}
			}
		}
	}
	RLC_CATCH_ANY {
		RLC_THROW(ERR_CAUGHT);
	}
	RLC_FINALLY {
		fp8_free(l);
		for (j = 0; j < m; j++) {
			ep_free(_p[j]);
			ep2_free(_q[j]);
		}
		RLC_FREE(_p);
		RLC_FREE(_q);
	}
}


int rand_check(uint8_t *buf, int size) {
	int count = 0;

	for (int i = 1; i < size; i++) {
		if (buf[i] == buf[i - 1]) {
			count++;
		} else {
			count = 0;
		}
	}

	if (count > RAND_REP) {
		return RLC_ERR;
	}
	return RLC_OK;
}


static int rand_inc(uint8_t *data, int size, int digit) {
	int carry = digit;
	for (int i = size - 1; i >= 0; i--) {
		int16_t s;
		s = (data[i] + carry);
		data[i] = s & 0xFF;
		carry = s >> 8;
	}
	return carry;
}


static void rand_gen(uint8_t *out, int out_len) {
	int m = RLC_CEIL(out_len, RLC_MD_LEN);
	uint8_t hash[RLC_MD_LEN], data[(RLC_RAND_SIZE - 1)/2];
	ctx_t *ctx = core_get();

	/* data = V */
	memcpy(data, ctx->rand + 1, (RLC_RAND_SIZE - 1)/2);
	for (int i = 0; i < m; i++) {
		/* w_i = Hash(data) */
		md_map(hash, data, sizeof(data));
		/* W = W || w_i */
		memcpy(out, hash, RLC_MIN(RLC_MD_LEN, out_len));
		out += RLC_MD_LEN;
		out_len -= RLC_MD_LEN;
		/* data = data + 1 mod 2^b. */
		rand_inc(data, (RLC_RAND_SIZE - 1)/2, 1);
	}
}


void rand_bytes(uint8_t *buf, int size) {
	uint8_t hash[RLC_MD_LEN];
	int carry, len  = (RLC_RAND_SIZE - 1)/2;
	ctx_t *ctx = core_get();

	if (sizeof(int) > 2 && size > (1 << 16)) {
		RLC_THROW(ERR_NO_VALID);
		return;
	}

	/* buf = hash_gen(size) */
	rand_gen(buf, size);
	/* H = hash(03 || V) */
	ctx->rand[0] = 0x3;
	md_map(hash, ctx->rand, 1 + len);
	/* V = V + H + C  + reseed_counter. */
	rand_add(ctx->rand + 1, ctx->rand + 1 + len, len);
	carry = rand_add(ctx->rand + 1 + (len - RLC_MD_LEN), hash, RLC_MD_LEN);
	rand_inc(ctx->rand, len - RLC_MD_LEN + 1, carry);
	rand_inc(ctx->rand, len + 1, ctx->counter);
	ctx->counter = ctx->counter + 1;
}


static int rand_add(uint8_t *state, uint8_t *hash, int size) {
	int carry = 0;
	for (int i = size - 1; i >= 0; i--) {
		/* Make sure carries are detected. */
		int16_t s;
		s = (state[i] + hash[i] + carry);
		state[i] = s & 0xFF;
		carry = s >> 8;
	}
	return carry;
}


static void rand_hash(uint8_t *out, int out_len, uint8_t *in, int in_len) {
	uint32_t j = util_conv_big(8 * out_len);
	int len = RLC_CEIL(out_len, RLC_MD_LEN);
	uint8_t* buf = RLC_ALLOCA(uint8_t, 1 + sizeof(uint32_t) + in_len);
	uint8_t hash[RLC_MD_LEN];

	if (buf == NULL) {
		RLC_THROW(ERR_NO_MEMORY);
		return;
	}

	buf[0] = 1;
	memcpy(buf + 1, &j, sizeof(uint32_t));
	memcpy(buf + 1 + sizeof(uint32_t), in, in_len);

	for (int i = 0; i < len; i++) {
		/* h = Hash(counter || bits_to_return || input_string) */
		md_map(hash, buf, 1 + sizeof(uint32_t) + in_len);
		/* temp = temp || h */
		memcpy(out, hash, RLC_MIN(RLC_MD_LEN, out_len));
		out += RLC_MD_LEN;
		out_len -= RLC_MD_LEN;
		/* counter = counter + 1 */
		buf[0]++;
	}

	RLC_FREE(buf);
}


void rand_seed(uint8_t *buf, int size) {
	ctx_t *ctx = core_get();
	int len = (RLC_RAND_SIZE - 1) / 2;

	if (size <= 0) {
		RLC_THROW(ERR_NO_VALID);
		return;
	}

	if (sizeof(int) > 4 && size > (1 << 32)) {
		RLC_THROW(ERR_NO_VALID);
		return;
	}

	ctx->rand[0] = 0x0;
	if (ctx->seeded == 0) {
		/* V = hash_df(seed). */
		rand_hash(ctx->rand + 1, len, buf, size);
		/* C = hash_df(00 || V). */
		rand_hash(ctx->rand + 1 + len, len, ctx->rand, len + 1);
	} else {
		/* V = hash_df(01 || V || seed). */
        int tmp_size = 1 + len + size;
		uint8_t* tmp = RLC_ALLOCA(uint8_t, tmp_size);
		if (tmp == NULL) {
			RLC_THROW(ERR_NO_MEMORY);
			return;
		}
		tmp[0] = 1;
		memcpy(tmp + 1, ctx->rand + 1, len);
		memcpy(tmp + 1 + len, buf, size);
		rand_hash(ctx->rand + 1, len, tmp, tmp_size);
		/* C = hash_df(00 || V). */
		rand_hash(ctx->rand + 1 + len, len, ctx->rand, len + 1);
		RLC_FREE(tmp);
	}
	ctx->counter = ctx->seeded = 1;
}


int util_bits_dig(dig_t a) {
    return RLC_DIG - arch_lzcnt(a);
}


static int square_root(void) {
	int bits, code = RLC_ERR;
	bn_t a, b, c;

	bn_null(a);
	bn_null(b);
	bn_null(c);

	RLC_TRY {
		bn_new(a);
		bn_new(b);
		bn_new(c);

		TEST_ONCE("square root extraction is correct") {
			for (bits = 0; bits < RLC_BN_BITS / 2; bits++) {
				bn_rand(a, RLC_POS, bits);
				bn_sqr(c, a);
				bn_srt(b, c);
				TEST_ASSERT(bn_cmp(a, b) == RLC_EQ, end);
			}
			for (bits = 0; bits < RLC_BN_BITS; bits++) {
				bn_rand(a, RLC_POS, bits);
				bn_srt(b, a);
				bn_sqr(c, b);
				TEST_ASSERT(bn_cmp(c, a) != RLC_GT, end);
			}
		}
		TEST_END;

		TEST_ONCE("square root of powers of 2 is correct") {
			for (bits = 0; bits < RLC_BN_BITS / 2; bits++) {
				bn_set_2b(a, bits);
				bn_sqr(c, a);
				bn_srt(b, c);
				TEST_ASSERT(bn_cmp(a, b) == RLC_EQ, end);
			}
		}
		TEST_END;
	}
	RLC_CATCH_ANY {
		RLC_ERROR(end);
	}
	code = RLC_OK;
  end:
	bn_free(a);
	bn_free(b);
	bn_free(c);
	return code;
}


int util(void) {
	int l, code = RLC_ERR;
	gt_t a, b, c;
	uint8_t bin[24 * RLC_PC_BYTES];

	gt_null(a);
	gt_null(b);
	gt_null(c);

	RLC_TRY {
		gt_new(a);
		gt_new(b);
		gt_new(c);

		TEST_CASE("comparison is consistent") {
			gt_rand(a);
			gt_rand(b);
			TEST_ASSERT(gt_cmp(a, b) != RLC_EQ, end);
		}
		TEST_END;

		TEST_CASE("copy and comparison are consistent") {
			gt_rand(a);
			gt_rand(b);
			gt_rand(c);
			if (gt_cmp(a, c) != RLC_EQ) {
				gt_copy(c, a);
				TEST_ASSERT(gt_cmp(c, a) == RLC_EQ, end);
			}
			if (gt_cmp(b, c) != RLC_EQ) {
				gt_copy(c, b);
				TEST_ASSERT(gt_cmp(b, c) == RLC_EQ, end);
			}
		}
		TEST_END;

		TEST_CASE("inversion and comparison are consistent") {
			gt_rand(a);
			gt_inv(b, a);
			TEST_ASSERT(gt_cmp(a, b) != RLC_EQ, end);
		}
		TEST_END;

		TEST_CASE
				("assignment to random/infinity and comparison are consistent")
		{
			gt_rand(a);
			gt_set_unity(c);
			TEST_ASSERT(gt_cmp(a, c) != RLC_EQ, end);
			TEST_ASSERT(gt_cmp(c, a) != RLC_EQ, end);
		}
		TEST_END;

		TEST_CASE("assignment to unity and unity test are consistent") {
			gt_set_unity(a);
			TEST_ASSERT(gt_is_unity(a), end);
		}
		TEST_END;
	}
	RLC_CATCH_ANY {
		util_print("FATAL ERROR!\n");
		RLC_ERROR(end);
	}
	code = RLC_OK;
  end:
	gt_free(a);
	gt_free(b);
	gt_free(c);
	return code;
}


static int test(void) {
	uint8_t out[64];
	int len = sizeof(out) / 2, code = RLC_ERR;

	TEST_ONCE("rdrand hardware generator is non-trivial") {
		memset(out, 0, 2 * len);
		rand_bytes(out, len);
		/* This fails with negligible probability. */
		TEST_ASSERT(memcmp(out, out + len, len) != 0, end);
	}
	TEST_END;

	code = RLC_OK;

  end:
	return code;
}