From a17e89d374bb411c2d3710ca5710ad9170e7b1aa Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Wed, 13 Mar 2024 03:07:21 +0100 Subject: [PATCH 01/37] Add new hash function. --- src/ep/relic_ep_map.c | 401 ++++++++++-------------------------- src/ep/relic_ep_param.c | 11 +- src/fp/relic_fp_param.c | 7 +- src/low/x64-asm-12l/macro.s | 26 +-- 4 files changed, 137 insertions(+), 308 deletions(-) diff --git a/src/ep/relic_ep_map.c b/src/ep/relic_ep_map.c index 714efdf5b..0396c8442 100644 --- a/src/ep/relic_ep_map.c +++ b/src/ep/relic_ep_map.c @@ -240,7 +240,7 @@ void ep_map_swift(ep_t p, const uint8_t *msg, size_t len) { /* enough space for two field elements plus extra bytes for uniformity */ const size_t len_per_elm = (FP_PRIME + ep_param_level() + 7) / 8; uint8_t s, *pseudo_random_bytes = RLC_ALLOCA(uint8_t, 2 * len_per_elm + 1); - fp_t a, b, c, t, u, v, w, y, x1, y1, z1; + fp_t a, b, c, d, t, u, v, w, y, x1, y1, z1, den[3]; ctx_t *ctx = core_get(); bn_t k; @@ -248,6 +248,7 @@ void ep_map_swift(ep_t p, const uint8_t *msg, size_t len) { fp_null(a); fp_null(b); fp_null(c); + fp_null(d); fp_null(t); fp_null(u); fp_null(v); @@ -256,12 +257,16 @@ void ep_map_swift(ep_t p, const uint8_t *msg, size_t len) { fp_null(x1); fp_null(y1); fp_null(z1); + fp_null(den[0]); + fp_null(den[1]); + fp_null(den[2]); RLC_TRY { bn_new(k); fp_new(a); fp_new(b); fp_new(c); + fp_new(d); fp_new(t); fp_new(u); fp_new(v); @@ -270,6 +275,9 @@ void ep_map_swift(ep_t p, const uint8_t *msg, size_t len) { fp_new(x1); fp_new(y1); fp_new(z1); + fp_new(den[0]); + fp_new(den[1]); + fp_new(den[2]); md_xmd(pseudo_random_bytes, 2 * len_per_elm + 1, msg, len, (const uint8_t *)"RELIC", 5); @@ -282,296 +290,109 @@ void ep_map_swift(ep_t p, const uint8_t *msg, size_t len) { fp_copy(a, ep_curve_get_a()); - if ((ep_curve_opt_b() == RLC_ZERO) && (ctx->mod8 == 1)) { - /* This is the approach due to Koshelev introduced in - * https://eprint.iacr.org/2021/1034.pdf */ - if (fp_is_sqr(a)) { - /* Compute t^2 = 3c*sqrt(a)*(2c^3*x^6 - 3*c^2*x^4 - 3*c*x^2 + 2).*/ - /* Compute w = 3*c. */ - fp_set_dig(c, -fp_prime_get_qnr()); - fp_neg(c, c); - fp_dbl(w, c); - fp_add(w, w, c); - - /* Compute x^2, x^4 and x^6 in sequence. */ - fp_sqr(z1, u); - fp_sqr(y1, z1); - fp_mul(t, z1, y1); - - fp_dbl(t, t); - fp_mul(t, t, c); - fp_mul(t, t, c); - fp_mul(t, t, c); - - fp_mul(v, y1, c); - fp_mul(v, v, w); - fp_sub(t, t, v); - - /* v = -3*c*x^2. */ - fp_mul(v, w, z1); - fp_neg(v, v); - fp_add(t, t, v); - fp_add_dig(t, t, 2); - - /* Assume a = 1 for simplicitly. */ - fp_mul(t, t, w); - fp_mul(t, t, ctx->ep_map_c[6]); - dig_t c1 = fp_is_sqr(t); - /* If t is not square, compute u = 1/(uc), t = sqrt(t/c)/(c*u^3)*/ - fp_inv(v, c); - fp_inv(x1, u); - fp_mul(y1, t, v); - /* If t is a square, extract its square root. */ - dv_copy_cond(t, y1, RLC_FP_DIGS, !c1); - fp_srt(t, t); - fp_mul(y1, t, v); - fp_sqr(y, x1); - fp_mul(y, y, x1); - fp_mul(y1, y1, y); - fp_mul(x1, x1, v); - dv_copy_cond(u, x1, RLC_FP_DIGS, !c1); - dv_copy_cond(t, y1, RLC_FP_DIGS, !c1); - - /* Compute x = sqrt(a)*(c*x^2 - 2)/(-3*c*x^2). */ - fp_sqr(z1, u); - fp_mul(v, w, z1); - fp_neg(v, v); - fp_inv(v, v); - fp_mul(p->x, z1, c); - fp_sub_dig(p->x, p->x, 2); - fp_mul(p->x, p->x, v); - fp_mul(p->x, p->x, ctx->ep_map_c[6]); - - /* Compute y = y*2*sqrt(a)/(3^2*c^2*x^3). */ - fp_mul(z1, z1, u); - fp_sqr(w, w); - fp_mul(w, w, z1); - fp_inv(w, w); - fp_dbl(p->y, ctx->ep_map_c[6]); - fp_mul(p->y, p->y, t); - fp_mul(p->y, p->y, w); - fp_set_dig(p->z, 1); - p->coord = BASIC; - } else { - /* Compute c = 3*a^2, t^2 = 6a(9u^5 − 14au^3 + 3cu).*/ - fp_neg(a, a); - fp_sqr(c, a); - fp_dbl(t, c); - fp_add(c, c, t); - fp_dbl(t, c); - fp_add(t, t, c); - fp_mul(t, t, u); - - fp_sqr(v, u); - fp_mul(w, v, u); - fp_mul(x1, w, a); - fp_mul_dig(x1, x1, 14); - fp_sub(t, t, x1); - - fp_mul(w, w, v); - fp_dbl(x1, w); - fp_add(w, w, x1); - fp_dbl(x1, w); - fp_add(w, w, x1); - fp_add(t, t, w); - fp_mul(t, t, a); - fp_dbl(t, t); - fp_dbl(x1, t); - fp_add(t, t, x1); - dig_t c1 = fp_is_sqr(t); - /* If t is not square, compute u = a/u, t = a*sqrt(a*t)/u^3*/ - fp_inv(x1, u); - fp_mul(y1, t, a); - /* If t is a square, extract its square root. */ - dv_copy_cond(t, y1, RLC_FP_DIGS, !c1); - fp_srt(t, t); - fp_mul(y1, t, a); - fp_sqr(y, x1); - fp_mul(y, y, x1); - fp_mul(y1, y1, y); - fp_mul(x1, x1, a); - dv_copy_cond(u, x1, RLC_FP_DIGS, !c1); - dv_copy_cond(t, y1, RLC_FP_DIGS, !c1); - - /* Compute x = 2^4*i*3*a^2*u / (3*(3*u^2 - a))^2. */ - fp_copy(y, ctx->ep_map_c[6]); - fp_mul(c, c, u); - fp_mul(x1, c, y); - fp_dbl(x1, x1); - fp_dbl(x1, x1); - fp_dbl(x1, x1); - fp_dbl(p->x, x1); - fp_sqr(v, u); - fp_dbl(z1, v); - fp_add(z1, z1, v); - fp_sub(z1, z1, a); - fp_dbl(p->z, z1); - fp_add(p->z, p->z, z1); - - /* Compute y = 3*2*(i-1)*a*(3^2*u^2 + a)*t / (3*(3*u^2 - a))^3. */ - fp_sub_dig(y, y, 1); - fp_mul(y1, y, a); - fp_dbl(y1, y1); - fp_dbl(p->y, y1); - fp_add(p->y, p->y, y1); - fp_mul(p->y, p->y, t); - fp_dbl(y1, v); - fp_add(y1, y1, v); - fp_dbl(v, y1); - fp_add(y1, y1, v); - fp_add(y1, y1, a); - fp_mul(p->y, p->y, y1); - - /* Multiply by cofactor. */ - p->coord = JACOB; - ep_norm(p, p); - } - } else if ((ep_curve_opt_b() == RLC_ZERO) && (ctx->mod8 != 1)) { - /* This is the approach due to Koshelev introduced in - * https://eprint.iacr.org/2021/1604.pdf */ - fp_set_dig(c, -fp_prime_get_qnr()); - fp_neg(c, c); - - /* u = t0, t = t1, v = t0^4, y = t1^4, w = c^2, z1 = 8*a^2*c. */ - fp_sqr(v, u); - fp_sqr(v, v); - fp_sqr(y, t); - fp_sqr(y, y); - fp_sqr(w, c); - fp_sqr(z1, a); - fp_mul(z1, z1, c); - fp_dbl(z1, z1); - fp_dbl(z1, z1); - fp_dbl(z1, z1); - /* w = c^2*t0^4+t1^4, y1 = c^4*t0^8, x1 = 2*c^2*t0^4*t1^4, y = t1^8. */ - fp_mul(w, w, v); - fp_sqr(y1, w); - fp_mul(x1, w, y); - fp_dbl(x1, x1); - fp_add(w, w, y); - fp_sqr(y, y); - /* w = den = 8*a^2*c(c^2*t0^4 + t1^4), z1 = 16*a^3*c^2. */ - fp_mul(w, w, z1); - fp_inv(p->z, w); - fp_mul(z1, z1, c); - fp_mul(z1, z1, a); - fp_dbl(z1, z1); - /* v = num2 = c^4*t0^8 - 2*c^2t0^4*t1^4 + t1^8 - 16*a^3*c^2*/ - fp_sub(v, y1, x1); - fp_add(v, v, y); - fp_sub(v, v, z1); - /* w = num0 = t0 * ac(-3*c^4t0^8 + 2c^2*t0^4*t1^4 + t1^8 + 16*a^3*c^2)*/ - fp_add(w, y, z1); - fp_add(w, w, x1); - fp_sub(w, w, y1); - fp_sub(w, w, y1); - fp_sub(w, w, y1); - fp_mul(w, w, u); - fp_mul(w, w, c); - fp_mul(w, w, a); - /* z1 = num1 = t1 * ac^2(c^4t0^8 + 2c^2t0^4*t1^4 - 3^t1^8 + 16a^3c^2)*/ - fp_sub(z1, z1, y); - fp_sub(z1, z1, y); - fp_sub(z1, z1, y); - fp_add(z1, z1, x1); - fp_add(z1, z1, y1); - fp_mul(z1, z1, t); - fp_mul(z1, z1, c); - fp_mul(z1, z1, c); - fp_mul(z1, z1, a); - /* v2 = num2/den = v/w. */ - fp_mul(w, w, p->z); - fp_mul(z1, z1, p->z); - fp_mul(v, v, p->z); - fp_inv(v, v); - - bn_read_raw(k, fp_prime_get(), RLC_FP_DIGS); - if ((k->dp[0] & 0xF) == 5) { - /* n = (3p + 1)/16 */ - bn_mul_dig(k, k, 3); - bn_add_dig(k, k, 1); - } else if ((k->dp[0] & 0xF) == 13) { - /* n = (p + 3)/16 */ - bn_add_dig(k, k, 3); - } else { - RLC_THROW(ERR_NO_VALID); - } - bn_rsh(k, k, 4); - /* Compute x1 = f = (1/v2)^3 + a*(1/v2) = (1/v2)((1/v2)^2 + a). */ - fp_sqr(x1, v); - fp_add(x1, x1, a); - fp_mul(x1, x1, v); - /* Compute y = theta, zp = theta^4. */ - fp_exp(y, x1, k); - fp_sqr(p->z, y); - fp_sqr(p->z, p->z); - /* Perform the base change from (t0,t1) to (u0, u1). */ - fp_sqr(u, u); - fp_mul(u, u, c); - fp_sqr(t, t); - fp_mul(t, t, c); - /* Compute c = i^r * f. */ - fp_mul(c, ctx->ep_map_c[5], x1); - fp_sqr(p->y, y); - /* We use zp as temporary, but there is no problem with \psi. */ - int index = 0; - fp_copy(y1, u); - fp_copy(a, v); - fp_sqr(b, y); - fp_copy(p->x, a); - fp_copy(p->y, b); - for (int m = 0; m < 4; m++) { - fp_mul(y1, y1, ctx->ep_map_c[5]); - index += (fp_bits(y1) < fp_bits(u)); - } - /* Apply consecutive endomorphisms. */ - for (int m = 0; m < 4; m++) { - fp_neg(a, a); - fp_mul(b, b, ep_curve_get_beta()); - dv_copy_cond(p->x, a, RLC_FP_DIGS, m < index); - dv_copy_cond(p->y, b, RLC_FP_DIGS, m < index); - } - fp_neg(y1, x1); - /* Compute 1/d * 1/theta. */ - fp_inv(y, y); - fp_mul(y, y, ctx->ep_map_c[4]); - dig_t c0 = fp_cmp(p->z, x1) == RLC_EQ; - dig_t c1 = fp_cmp(p->z, y1) == RLC_EQ; - dig_t c2 = fp_cmp(p->z, c) == RLC_EQ; - fp_neg(c, c); - dig_t c3 = fp_cmp(p->z, c) == RLC_EQ; - c2 = !c0 && !c1 && c2; - c3 = !c0 && !c1 && !c2 && c3; - fp_copy(p->z, ctx->ep_map_c[6]); - fp_mul(p->z, p->z, p->y); - dv_copy_cond(p->y, p->z, RLC_FP_DIGS, c1); - fp_copy(y1, ctx->ep_map_c[4]); - /* Convert from projective coordinates on the surface to affine. */ - fp_mul(u, u, v); - fp_mul(t, t, v); - fp_sqr(v, v); - fp_mul(w, w, v); + if (ep_curve_opt_b() == RLC_ZERO) { + fp_set_dig(u, 1); + fp_set_dig(t, 2); + fp_sqr(a, u); + fp_sqr(b, a); + fp_mul(c, b, a); + fp_add_dig(c, c, 64); + fp_sqr(d, t); + + fp_mul(v, a, d); + fp_mul(v, v, u); + fp_mul_dig(v, v, 24); + fp_mul(v, v, core_get()->ep_map_c[4]); + + fp_sub_dig(p->x, core_get()->ep_map_c[4], 1); + fp_hlv(p->x, p->x); + + fp_sqr(w, b); + fp_mul(y, v, a); + fp_add(y, y, d); + fp_add(y, y, d); + fp_add(y, y, d); + fp_add(y, y, d); + fp_mul(y, y, p->x); + + fp_add(den[0], c, v); + fp_mul(den[0], den[0], u); + fp_mul(den[0], den[0], core_get()->ep_map_c[4]); + fp_mul(den[0], den[0], p->x); + fp_dbl(den[0], den[0]); + fp_neg(den[0], den[0]); + fp_mul(den[1], den[0], p->x); + fp_sub_dig(den[2], a, 4); + fp_sqr(den[2], den[2]); + fp_mul_dig(den[2], den[2], 216); + fp_dbl(den[2], den[2]); + fp_neg(den[2], den[2]); + fp_mul(den[2], den[2], b); + fp_mul(den[2], den[2], d); + + fp_inv_sim(den, den, 3); + fp_dbl(a, a); + fp_dbl(a, a); + fp_dbl(a, a); + fp_dbl(a, a); + fp_add(y1, a, v); + fp_dbl(y1, y1); + fp_dbl(y1, y1); + fp_add(y1, y1, w); + fp_mul(z1, y, p->x); + fp_add(x1, x1, z1); + fp_add(y1, y1, y); + fp_add(z1, a, b); + fp_add(z1, z1, b); + fp_add(z1, z1, b); + fp_add(z1, z1, b); + fp_dbl(t, z1); + fp_add(z1, z1, t); + fp_add(z1, z1, c); + fp_sub(z1, z1, v); fp_mul(z1, z1, v); - /* Compute (x,y) = (x0/(d*theta)^2, y0/(d*theta)^3). */ - fp_sqr(y1, y); + fp_dbl(a, a); + fp_dbl(a, a); + fp_dbl(a, a); + fp_add(a, a, w); + fp_mul(u, a, b); + fp_sub(z1, u, z1); + fp_set_dig(d, 64); + fp_sqr(d, d); + fp_add(z1, z1, d); + + fp_mul(x1, x1, den[0]); + fp_mul(y1, y1, den[1]); + fp_mul(z1, z1, den[2]); + + fp_sqr(t, x1); + fp_add_dig(t, t, 1); + fp_mul(t, t, x1); + fp_sqr(u, y1); + fp_add_dig(u, u, 1); fp_mul(u, u, y1); - fp_mul(w, w, y); - fp_mul(w, w, y1); - dv_copy_cond(p->x, u, RLC_FP_DIGS, c2); - dv_copy_cond(p->y, w, RLC_FP_DIGS, c2); - /* Compute (x,y) = (x1/(d^3*theta)^2, y1/(d^3*theta)^3). */ - fp_mul(z1, z1, y); - fp_mul(t, t, y1); - fp_mul(z1, z1, y1); - fp_sqr(y, ctx->ep_map_c[4]); - fp_mul(z1, z1, y); - fp_sqr(y, y); - fp_mul(t, t, y); - fp_mul(z1, z1, y); - dv_copy_cond(p->x, t, RLC_FP_DIGS, c3); - dv_copy_cond(p->y, z1, RLC_FP_DIGS, c3); - p->coord = BASIC; + fp_sqr(v, z1); + fp_add_dig(v, v, 1); + fp_mul(v, v, z1); + + int c2 = fp_is_sqr(u); + int c3 = fp_is_sqr(v); + + dv_swap_cond(t, u, RLC_FP_DIGS, c2); + dv_swap_cond(x1, y1, RLC_FP_DIGS, c2); + dv_swap_cond(t, v, RLC_FP_DIGS, c3); + dv_swap_cond(x1, z1, RLC_FP_DIGS, c3); + + if (!fp_srt(t, t)) { + RLC_THROW(ERR_NO_VALID); + } + fp_neg(u, t); + dv_swap_cond(t, u, RLC_FP_DIGS, fp_is_even(t) ^ s); + + fp_copy(p->x, x1); + fp_copy(p->y, t); fp_set_dig(p->z, 1); + p->coord = BASIC; } else { /* This is the SwiftEC case per se. */ if (ep_curve_opt_a() != RLC_ZERO) { @@ -652,6 +473,7 @@ void ep_map_swift(ep_t p, const uint8_t *msg, size_t len) { fp_free(a); fp_free(b); fp_free(c); + fp_free(d); fp_free(t); fp_free(u); fp_free(v); @@ -660,6 +482,9 @@ void ep_map_swift(ep_t p, const uint8_t *msg, size_t len) { fp_free(x1); fp_free(y1); fp_free(z1); + fp_free(den[0]); + fp_free(den[1]); + fp_free(den[2]); RLC_FREE(pseudo_random_bytes); } } diff --git a/src/ep/relic_ep_param.c b/src/ep/relic_ep_param.c index 81e10d48c..f1df8c22a 100644 --- a/src/ep/relic_ep_param.c +++ b/src/ep/relic_ep_param.c @@ -704,10 +704,10 @@ /** @{ */ #define N16_P766_A "1" #define N16_P766_B "0" -#define N16_P766_X "09B60388917DF4F526CE1869B8A069F7722A4EABF0543BAE29B7CABACC1BE50626878F5CC3C5157ADCC6B80DE516239BE3CCC8E66173CBD91092C87B1AAEBF072F3C92CC5B6A8F33A6A2A165AC171A76C4687274BA0E54A7C049F0781D6EB3F5" -#define N16_P766_Y "280BAA585CD0AB9090B8AB2990410AA093511C661554ACC497D77B67BE3B3CCDDFBCBE296A0119EF8F8FA19D613CA25D16232EF4A5A019C6FDD4C2F0F8DBC238C84F07326BACB3D0478AB5596DCC8BCAD483BF2C4AD89A6C29683E85E77DF120" -#define N16_P766_R "FFFFFF8401001A46937D417AB554F4F3438C3F42C66CBA08998426591ED55EBA6A16CB364728D491BC20010000000001" -#define N16_P766_H "3FFFFFE100400691A4DF505EAD553D3CD0E38FD0B1746ED22B12363612FBBA252C222C274D60ADA6C3F09E1010080100" +#define N16_P766_X "1DA7F278391EA7B1C265142B0B93160AF6D5C55B0BB07FC8162541BBC5E6DA87799D32E8CD4280E0828D12536129BBB97D33E39FC968F60CF6CCEF1BAC565862336904E52512422A26631A8A1B2123C3454226538883E62255BB1DD7954D7BCA" +#define N16_P766_Y "17423CCF769BB12CFDD1FB16000CD0E6D7D3B1DF2E466509715C9F80FF954EA369429796AE76CB72842868A66F2A7076FCFFE4043339CB68034046B48BEA223C9898F5122F70E9CCDB3CA24CB0B2DE291F54F3C28548F99CF936A2525FF83750" +#define N16_P766_R "FFFFFD820002B7A1BE4E96B988C5AEB8365570F5BEB177AEE7A3A8BE6DA478CBB6010000000000000000000000000001" +#define N16_P766_H "3FFFFF608000ADE86F93A5AE62316BAE0D95DC3D6EE4FDEC36213A08E8DE3839FCE3625C70F337F82000063604000000" /** @} */ #endif @@ -1768,6 +1768,9 @@ void ep_param_print(void) { case N16_P765: util_banner("Curve N16-P765:", 0); break; + case N16_P766: + util_banner("Curve N16-P766:", 0); + break; case FM16_P765: util_banner("Curve FM16-P765:", 0); break; diff --git a/src/fp/relic_fp_param.c b/src/fp/relic_fp_param.c index 674ffb3de..0a8c2c6dd 100644 --- a/src/fp/relic_fp_param.c +++ b/src/fp/relic_fp_param.c @@ -605,13 +605,14 @@ void fp_param_set(int param) { fp_prime_set_pairf(t0, EP_K16); break; case N16_766: - /* u = 2^48-2^20+2^15+2^5 */ + /* u = 2^48-2^22-2^20+2^14 */ bn_set_2b(t0, 48); + bn_set_2b(t1, 22); + bn_sub(t0, t0, t1); bn_set_2b(t1, 20); bn_sub(t0, t0, t1); - bn_set_2b(t1, 15); + bn_set_2b(t1, 14); bn_add(t0, t0, t1); - bn_add_dig(t0, t0, 32); fp_prime_set_pairf(t0, EP_N16); break; #elif FP_PRIME == 768 diff --git a/src/low/x64-asm-12l/macro.s b/src/low/x64-asm-12l/macro.s index 31cddde65..f93019271 100644 --- a/src/low/x64-asm-12l/macro.s +++ b/src/low/x64-asm-12l/macro.s @@ -61,19 +61,19 @@ #define U0 0xC18CA908C52344BB */ /* AFG16-766 */ -#define P0 0xD1C2DA3812080101 -#define P1 0x7C7B86E2E778F618 -#define P2 0xCBDEA14B5B88FF11 -#define P3 0xCC0258598794E74A -#define P4 0x2C3C97E23451D33D -#define P5 0xD865BA50F2687698 -#define P6 0x7FE816EA1FC66244 -#define P7 0x28B32989A8983A80 -#define P8 0xA388C01776314278 -#define P9 0x103F6BCC973EF5C3 -#define P10 0x0BB883B2C64AF7BD -#define P11 0X3FFFFFC200801C27 -#define U0 0x30B120EB030700FF +#define P0 0x2000063604000001 +#define P1 0xD1AAA714D2D9A838 +#define P2 0x0A2A75998A7AE913 +#define P3 0x25FD0E418877DE7B +#define P4 0x5EFA7C359D311101 +#define P5 0xB873EC97B19F46B6 +#define P6 0xACB58312AC85DEC1 +#define P7 0xA93FC4B3675D7960 +#define P8 0x5067F4B6C6D62C93 +#define P9 0x9583EF65E7E3C6F4 +#define P10 0xDBC478D27904ED8A +#define P11 0x3FFFFEC10002E951 +#define U0 0x6FF0063603FFFFFF #elif FP_PRIME == 765 /* AFG16-765 */ #define P0 0x0000000000000001 From a22aec229e2b67b2bd2244290789fa6e344f62dd Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Wed, 13 Mar 2024 17:18:18 +0100 Subject: [PATCH 02/37] Change parameters again. --- src/ep/relic_ep_param.c | 8 ++++---- src/epx/relic_ep4_curve.c | 20 ++++++++++---------- src/fp/relic_fp_param.c | 9 ++++----- src/low/x64-asm-12l/macro.s | 26 +++++++++++++------------- 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/src/ep/relic_ep_param.c b/src/ep/relic_ep_param.c index f1df8c22a..9234bd7ef 100644 --- a/src/ep/relic_ep_param.c +++ b/src/ep/relic_ep_param.c @@ -704,10 +704,10 @@ /** @{ */ #define N16_P766_A "1" #define N16_P766_B "0" -#define N16_P766_X "1DA7F278391EA7B1C265142B0B93160AF6D5C55B0BB07FC8162541BBC5E6DA87799D32E8CD4280E0828D12536129BBB97D33E39FC968F60CF6CCEF1BAC565862336904E52512422A26631A8A1B2123C3454226538883E62255BB1DD7954D7BCA" -#define N16_P766_Y "17423CCF769BB12CFDD1FB16000CD0E6D7D3B1DF2E466509715C9F80FF954EA369429796AE76CB72842868A66F2A7076FCFFE4043339CB68034046B48BEA223C9898F5122F70E9CCDB3CA24CB0B2DE291F54F3C28548F99CF936A2525FF83750" -#define N16_P766_R "FFFFFD820002B7A1BE4E96B988C5AEB8365570F5BEB177AEE7A3A8BE6DA478CBB6010000000000000000000000000001" -#define N16_P766_H "3FFFFF608000ADE86F93A5AE62316BAE0D95DC3D6EE4FDEC36213A08E8DE3839FCE3625C70F337F82000063604000000" +#define N16_P766_X "177E5E088795AE368F68ADB4938F647BCCB7D0BCB70456E3B3BC0C6EE12FA3D0E1DFC2FD81C215EC25E602DE8BCEE98A7F8FC23E4A296B9DDEF34BB90A27DD4804B90F7783FE2C891A820941DB16535E1FD73E73038A520AAE396F0949D7E46D" +#define N16_P766_Y "1CE39931AB952C962CBF6F4E8BE79D6AD3D931B2B100185707032C109C9476305F44D30E1D551E965D44D661327D878EC47ED7A9D1DCDDCA96091E74FB4FF6D42CE809579AB9C72417C55849377E4BA31A0B5F687D840E4CE99E63D583050147" +#define N16_P766_R "FFFF7000238FFAF4807374994CF93FE6E28D406881B18D350193FE6E3E533E4073749FEBD2000238FFFFDC0000010001" +#define N16_P766_H "3FFFDC0008E3FEBD201CDD26533E4FF9B8A3D019F36C69AB3FF0479FDCFCBD856CEE5D9B2D34778FD7D2F6D2DC004204" /** @} */ #endif diff --git a/src/epx/relic_ep4_curve.c b/src/epx/relic_ep4_curve.c index cb695d924..e52dd7ce1 100644 --- a/src/epx/relic_ep4_curve.c +++ b/src/epx/relic_ep4_curve.c @@ -209,16 +209,16 @@ #define N16_P766_B1 "0" #define N16_P766_B2 "0" #define N16_P766_B3 "0" -#define N16_P766_X0 "2DD92375B2A68D713D1D997683DA3E93B0C1713870A6759B37076504F22AFEC776AA9986FFE48F5507793DB266C9C563D26B64423B653977CC9D7A3EA67D52CF708A55E0110E1F21E4676EEC13FF37228D05B74FC124AC6F15931F84039FFF5B" -#define N16_P766_X1 "3BFCCB56ED55A4FBC07136D3577EA5DC5D1ED55079EA0CD7FA75F945D616AF8957935ABF77D532AE66E568CE090109867C58114CD9AE9CCF220BAE9B22148BBEF77A0AF557527A4F87D36CD84DD481FCF78ED87C388E0D4777456B0DF240898E" -#define N16_P766_X2 "1B9DAFE0399513AE63149C5BD87F531A00A9BA81BB42FE35C499BBAD9B985EDDDB94EEEAF944C21B6E36B25768CB3A6EB5EAD839BDEB05E1F86819BCAFD5EB0EF82EFC64D62223CD23ED960D645D213D8B2DE094FE2F78F0C34AFC09EBD39B65" -#define N16_P766_X3 "B22A088D63FC479596ED186B382DECBB180CC51CD5615F1CE9D600B4BCD81DCAFB9369A86E261767B75C2CE12BD4AE372311F8E9E328BA006D52021D1F09DC51B853C3365FCD61B4DA1BD24AB504CE63E11EB4FB0C2FA56704E009A7D1B1944" -#define N16_P766_Y0 "2AB5268BD543054A99076F5DD83E2D8947CC9DBBCCB125C29D791386412830E074378F94D1CC70891ADDF24CE9398490D3F27FBA8EE7F6CB2D71DFE01112BBD0B9B21FE566393AC278562B4390F0673762FB29A6540186B515A0AB9DB96E848C" -#define N16_P766_Y1 "36962D909FC17BD54162AD530987217464D81007D2B07CEBFEDD9E7ACAC7423242132E11169B1F49A9DCDD3EA9EFF0A24CE7AA7A68BFADFD3E07B0517D47F097AB0F9568B54E8AAE190A2D53D430D1118570C0B5EB878364BA9900A44D97505B" -#define N16_P766_Y2 "2B259E06C780DA39E283C221C392A9EE03ACE066967A30A5A4ADEE49E2ECA40DCAAD4CD234FABAA4CFAB20105EE1BEE54403CC17D5BE544B926A699495A5923C6EC7575A64EE412BFAF4C67E4C449F28814D26C4B8F85947EABF97E3818A3097" -#define N16_P766_Y3 "1495AA76C3DA6431BF12D17B346AEDF5EFE50F4F7135F2618075887884DDA700FCC2918462DDE2CF728034461ECCB4C1F76892A809192939D069D3BA3A06D7F7FD94C1E08D74261C847C3E6DCD36B8D93B87D8277EC23300619530B5A5584B8C" -#define N16_P766_R "FFFFFF8401001A46937D417AB554F4F3438C3F42C66CBA08998426591ED55EBA6A16CB364728D491BC20010000000001" -#define N16_P766_H "FFFFFC9C0705A529AA711C4917897B571AB9D0963B7F885C31C06824D3F2584BA226785992D5E850AA5C0B13502F293A1681AF78BC665FEB126F7C4BA70D01AE2CE52E02140F5C16C4AF3B3A49E6351F191B54245F28C8C4C335ECA923DBF5FF15EBDFF8D29E3B70B027074B50A2AF3622E60B09A63B11B30F61AD59875B59751803A75D55EF5123CED27A3E7F29D20CD2617F6532FDCB87592DF3A6720415EB7871CE9D858844438E66F90066DB58804FB9F54832AEEC21CBACEB00A49D6D5AE806679C4AC7DAD3DBD527E4D81095E256780325B815449FA6C11EF0D70D8AEE93923245E95D8CE60384D06F98BC9AC3D2E91CD725371F495C8B2D1E5161377E801D7732A37179669E91008D8152B75C19324815BD89CCB54EC01212F5B906604C08A55DB203EA6049568FFAA1B88B74657771B64387FF992C5CB64A9E758DEC94FE63AB50F5B88140B2AC32248382" +#define N16_P766_X0 "36097A5BDF2276730FCAC23EA21B0C85D46B013D5A33B2D403BD82155F681BC3A1213F7AD40BFD5B64409C6B909A44F1AC391DE4222B56C55CC74DCB5DCEC23696575F80C402A1256C267F9D1CA325558C3357B116AC85CF856E51590FE7C34F" +#define N16_P766_X1 "17CC3006229289EEC6AAA00FD81F17F26CECDE216E03B5DF64B61FEBA6DFF9D3F032642D66F8E5D4DE44934FB1CB99AFB0A6A939ADAFDA1E6197A474C3BBDDBA71E6120B3AAFE1007A8D0F360651B316312F902B1EB346DD2B276FEEE75ABDD1" +#define N16_P766_X2 "2B79EC9BDF7F80A86DD7F3CE18A3D1FAAEAEDE9312797735EA7F090760B4730CEE401EF9CAB0978C14E967FCE1CEEF5660DFA40F367F698D0CDE9C0F0EF361D70E186EA991CD0F6DB63FE88C45EC5909DFC866862C7B1D1DE6A04843761E5E3" +#define N16_P766_X3 "2005908CF374B24B1C7CF8C5CE656D04515082E61F3088C28D58B9E0249CA864AF1773F479D3272C567311FB287E579547743E8CA51A9D4B45FF5D3D6B4A2BB1865B6E4FB045953D716C68A73095A74CCDDAD5AFCEA74E676D37452916B5CB24" +#define N16_P766_Y0 "137A898DDF4399F2ABB29920707790495F645B373F419C4E2A96DA05B90488BED334F2B8A44AF71C92D947009618358A14F8DE18F0D777F0DE4F98625C4024DBEB8EA858A7AEFAEC093EA12ED7A7C70142AE3583D27982B26AFD75FB441A4B66" +#define N16_P766_Y1 "4D0ADEC5B4B77DFBEB78227FC664F7520A469DBEA674C845F64B537ABFB808F9857EEFBDE088D0422C919ECD562D7C668A42F6C2DAB0B26D7C4D4C3B835CBEA2A843CD3A449B44F0D6D4026512670C3600B02CEFDF9BBF21975B596DD732C13" +#define N16_P766_Y2 "2FCE7D8564D037E95317C3110542BB780D8824436B6F3626C8A4BF1D1C1FB7C1FDF12E7BC52D9F19914404EF71873EFB36C95AF0C6635329D556431A902BA01186D968C0D4BF55F17F7FED3072D77D9DE8D151135952C6EE7855E8F6176B4D66" +#define N16_P766_Y3 "2BAD268ACF32E9D617EC80588A3F1063C5565008B9DF82E8E9B8451F874C36157A280057EF467E0BFCA1FE7AF5C79CDCD055011B2F0F14764B8F1C97BEA256BD5A40FC8020486507E52413A11B70F8D6A3ACF396D5F0D8902950A3E3821B4C64" +#define N16_P766_R "FFFF7000238FFAF4807374994CF93FE6E28D406881B18D350193FE6E3E533E4073749FEBD2000238FFFFDC0000010001" +#define N16_P766_H "FFFC10079DE659DE808DDCE130F752738172840B38A5FC5FC7E040BFD9DCEE27BC5A7B95CED5C4229B3FA11F8F6FAE01BE1FBBBAF34CEF81E7CAFC72FAA4ECB8710AB4E1097D5F96BC6E8A5A88DE77C8B1F025B2691EEE839B4DBE2AC3433AA9E3567E22C5F26F401F6D0458C5F931050EB64B76A27B1D74E69AF8751AB329F9AA6F80C46E4F46D6727D476002C6DD1893DDC185AE4BE7842818B8DF91045FAF7F0A85549CFF3D6F3F45C0010E9F70243663C0840EEE489118422163CFDA9A843ABA6C408D3EC6C858FEDF4BDE93A28C7FD16B34CC1E1074ADB78D9F4F7F5E96DC4F0C5DCA4F360BCB4933A50ABAC5204B89055875059167BA03E0402A1CB965B48313B481483B26F3F1192C673BC76FBDA8395741062530251758B6B86500570F6C3FAF490A5C01D1B62653C6C1DF188A9358877DC50DAB36655B01F092A8AF90F6259FB91E5D57E2DA8FDD5F06A2" /** @} */ #endif diff --git a/src/fp/relic_fp_param.c b/src/fp/relic_fp_param.c index 0a8c2c6dd..307437052 100644 --- a/src/fp/relic_fp_param.c +++ b/src/fp/relic_fp_param.c @@ -605,14 +605,13 @@ void fp_param_set(int param) { fp_prime_set_pairf(t0, EP_K16); break; case N16_766: - /* u = 2^48-2^22-2^20+2^14 */ + /* u = 2^48-2^28-2^25+2^2 */ bn_set_2b(t0, 48); - bn_set_2b(t1, 22); + bn_set_2b(t1, 28); bn_sub(t0, t0, t1); - bn_set_2b(t1, 20); + bn_set_2b(t1, 25); bn_sub(t0, t0, t1); - bn_set_2b(t1, 14); - bn_add(t0, t0, t1); + bn_add_dig(t0, t0, 4); fp_prime_set_pairf(t0, EP_N16); break; #elif FP_PRIME == 768 diff --git a/src/low/x64-asm-12l/macro.s b/src/low/x64-asm-12l/macro.s index f93019271..17cb0f874 100644 --- a/src/low/x64-asm-12l/macro.s +++ b/src/low/x64-asm-12l/macro.s @@ -61,19 +61,19 @@ #define U0 0xC18CA908C52344BB */ /* AFG16-766 */ -#define P0 0x2000063604000001 -#define P1 0xD1AAA714D2D9A838 -#define P2 0x0A2A75998A7AE913 -#define P3 0x25FD0E418877DE7B -#define P4 0x5EFA7C359D311101 -#define P5 0xB873EC97B19F46B6 -#define P6 0xACB58312AC85DEC1 -#define P7 0xA93FC4B3675D7960 -#define P8 0x5067F4B6C6D62C93 -#define P9 0x9583EF65E7E3C6F4 -#define P10 0xDBC478D27904ED8A -#define P11 0x3FFFFEC10002E951 -#define U0 0x6FF0063603FFFFFF +#define P0 0xF45D1E791E054605 +#define P1 0xFCBC4220020A6592 +#define P2 0x211730168CD3106C +#define P3 0xC81A3A94170A2E5A +#define P4 0x04A837318C7897B6 +#define P5 0xA2902CAC0A117EAD +#define P6 0x4C8C62B406D882C8 +#define P7 0xDC0F8D8300A6748A +#define P8 0x67D35D389C7BD43C +#define P9 0x89F38FC6E7249EF1 +#define P10 0xC2E122127943E41E +#define P11 0x3FFFB8002607F379 +#define U0 0xF282381A203F6933 #elif FP_PRIME == 765 /* AFG16-765 */ #define P0 0x0000000000000001 From 42143dd01132255184b5ed6052a4fc2ffc9e6ac4 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Thu, 14 Mar 2024 01:00:57 +0100 Subject: [PATCH 03/37] Fixes to the formulas. --- include/relic_core.h | 2 +- src/ep/relic_ep_curve.c | 46 ++----------- src/ep/relic_ep_map.c | 139 ++++++++++++++++++++-------------------- 3 files changed, 76 insertions(+), 111 deletions(-) diff --git a/include/relic_core.h b/include/relic_core.h index 02e08741c..ae3b08241 100644 --- a/include/relic_core.h +++ b/include/relic_core.h @@ -278,7 +278,7 @@ typedef struct _ctx_t { /** The distinguished non-square used by the mapping function */ fp_st ep_map_u; /** Precomputed constants for hashing. */ - fp_st ep_map_c[7]; + fp_st ep_map_c[5]; #ifdef EP_ENDOM fp_st beta; #if EP_MUL == LWNAF || EP_FIX == COMBS || EP_FIX == LWNAF || EP_SIM == INTER || !defined(STRIP) diff --git a/src/ep/relic_ep_curve.c b/src/ep/relic_ep_curve.c index 80e3d0ae1..6d2d9a2a3 100644 --- a/src/ep/relic_ep_curve.c +++ b/src/ep/relic_ep_curve.c @@ -91,8 +91,6 @@ static void ep_curve_set_map(void) { dig_t *c2 = ctx->ep_map_c[2]; dig_t *c3 = ctx->ep_map_c[3]; dig_t *c4 = ctx->ep_map_c[4]; - dig_t *c5 = ctx->ep_map_c[5]; - dig_t *c6 = ctx->ep_map_c[6]; RLC_TRY { bn_new(t); @@ -173,47 +171,11 @@ static void ep_curve_set_map(void) { fp_mul_dig(c3, c3, 4); /* c3 *= 4 */ } - /* if b = 0, precompute constants. */ - if (ep_curve_opt_b() == RLC_ZERO) { - dig_t r = 0; - - fp_set_dig(c4, -fp_prime_get_qnr()); - fp_neg(c4, c4); - - bn_read_raw(t, fp_prime_get(), RLC_FP_DIGS); - bn_sub_dig(t, t, 1); - bn_rsh(t, t, 2); - fp_exp(c5, c4, t); - - bn_read_raw(t, fp_prime_get(), RLC_FP_DIGS); - if ((t->dp[0] & 0xF) == 5) { - /* n = (3p + 1)/16 */ - bn_mul_dig(t, t, 3); - bn_add_dig(t, t, 1); - r = 1; - } else { - /* n = (p + 3)/16 */ - bn_add_dig(t, t, 3); - r = 3; - } - bn_rsh(t, t, 4); - /* Compute d = 1/c^n. */ - fp_exp(c4, c4, t); - fp_inv(c4, c4); - fp_exp_dig(c5, c5, r); - /* Compute 1/sqrt(-1) as well. */ - fp_set_dig(c6, 1); - fp_neg(c6, c6); - fp_srt(c6, c6); - } - /* If a = 0, precompute and store a square root of -3. */ - if (ep_curve_opt_a() == RLC_ZERO) { - fp_set_dig(c4, 3); - fp_neg(c4, c4); - if (!fp_srt(c4, c4)) { - RLC_THROW(ERR_NO_VALID); - } + fp_set_dig(c4, 3); + fp_neg(c4, c4); + if (!fp_srt(c4, c4)) { + RLC_THROW(ERR_NO_VALID); } } RLC_CATCH_ANY { diff --git a/src/ep/relic_ep_map.c b/src/ep/relic_ep_map.c index 0396c8442..97cbaedfe 100644 --- a/src/ep/relic_ep_map.c +++ b/src/ep/relic_ep_map.c @@ -291,8 +291,6 @@ void ep_map_swift(ep_t p, const uint8_t *msg, size_t len) { fp_copy(a, ep_curve_get_a()); if (ep_curve_opt_b() == RLC_ZERO) { - fp_set_dig(u, 1); - fp_set_dig(t, 2); fp_sqr(a, u); fp_sqr(b, a); fp_mul(c, b, a); @@ -309,10 +307,10 @@ void ep_map_swift(ep_t p, const uint8_t *msg, size_t len) { fp_sqr(w, b); fp_mul(y, v, a); - fp_add(y, y, d); - fp_add(y, y, d); - fp_add(y, y, d); - fp_add(y, y, d); + fp_add(y, y, c); + fp_add(y, y, c); + fp_add(y, y, c); + fp_add(y, y, c); fp_mul(y, y, p->x); fp_add(den[0], c, v); @@ -330,69 +328,74 @@ void ep_map_swift(ep_t p, const uint8_t *msg, size_t len) { fp_mul(den[2], den[2], b); fp_mul(den[2], den[2], d); - fp_inv_sim(den, den, 3); - fp_dbl(a, a); - fp_dbl(a, a); - fp_dbl(a, a); - fp_dbl(a, a); - fp_add(y1, a, v); - fp_dbl(y1, y1); - fp_dbl(y1, y1); - fp_add(y1, y1, w); - fp_mul(z1, y, p->x); - fp_add(x1, x1, z1); - fp_add(y1, y1, y); - fp_add(z1, a, b); - fp_add(z1, z1, b); - fp_add(z1, z1, b); - fp_add(z1, z1, b); - fp_dbl(t, z1); - fp_add(z1, z1, t); - fp_add(z1, z1, c); - fp_sub(z1, z1, v); - fp_mul(z1, z1, v); - fp_dbl(a, a); - fp_dbl(a, a); - fp_dbl(a, a); - fp_add(a, a, w); - fp_mul(u, a, b); - fp_sub(z1, u, z1); - fp_set_dig(d, 64); - fp_sqr(d, d); - fp_add(z1, z1, d); - - fp_mul(x1, x1, den[0]); - fp_mul(y1, y1, den[1]); - fp_mul(z1, z1, den[2]); - - fp_sqr(t, x1); - fp_add_dig(t, t, 1); - fp_mul(t, t, x1); - fp_sqr(u, y1); - fp_add_dig(u, u, 1); - fp_mul(u, u, y1); - fp_sqr(v, z1); - fp_add_dig(v, v, 1); - fp_mul(v, v, z1); - - int c2 = fp_is_sqr(u); - int c3 = fp_is_sqr(v); - - dv_swap_cond(t, u, RLC_FP_DIGS, c2); - dv_swap_cond(x1, y1, RLC_FP_DIGS, c2); - dv_swap_cond(t, v, RLC_FP_DIGS, c3); - dv_swap_cond(x1, z1, RLC_FP_DIGS, c3); - - if (!fp_srt(t, t)) { - RLC_THROW(ERR_NO_VALID); - } - fp_neg(u, t); - dv_swap_cond(t, u, RLC_FP_DIGS, fp_is_even(t) ^ s); + if (fp_is_zero(den[0]) || fp_is_zero(den[1]) || fp_is_zero(den[2])) { + ep_set_infty(p); + } else { + fp_inv_sim(den, den, 3); + fp_dbl(a, a); + fp_dbl(a, a); + fp_dbl(a, a); + fp_dbl(a, a); + fp_add(y1, a, v); + fp_dbl(y1, y1); + fp_dbl(y1, y1); + fp_add(y1, y1, w); + fp_mul(z1, y, p->x); + fp_add(x1, y1, z1); + fp_add(y1, y1, y); + + fp_add(z1, a, b); + fp_add(z1, z1, b); + fp_add(z1, z1, b); + fp_add(z1, z1, b); + fp_dbl(t, z1); + fp_add(z1, z1, t); + fp_sub(z1, c, z1); + fp_sub(z1, z1, v); + fp_mul(z1, z1, v); + fp_dbl(a, a); + fp_dbl(a, a); + fp_dbl(a, a); + fp_add(a, a, w); + fp_mul(u, a, b); + fp_sub(z1, u, z1); + fp_set_dig(d, 64); + fp_sqr(d, d); + fp_add(z1, z1, d); + + fp_mul(x1, x1, den[0]); + fp_mul(y1, y1, den[1]); + fp_mul(z1, z1, den[2]); + + fp_sqr(t, x1); + fp_add_dig(t, t, 1); + fp_mul(t, t, x1); + fp_sqr(u, y1); + fp_add_dig(u, u, 1); + fp_mul(u, u, y1); + fp_sqr(v, z1); + fp_add_dig(v, v, 1); + fp_mul(v, v, z1); - fp_copy(p->x, x1); - fp_copy(p->y, t); - fp_set_dig(p->z, 1); - p->coord = BASIC; + int c2 = fp_is_sqr(u); + int c3 = fp_is_sqr(v); + + dv_swap_cond(t, u, RLC_FP_DIGS, c2); + dv_swap_cond(x1, y1, RLC_FP_DIGS, c2); + dv_swap_cond(t, v, RLC_FP_DIGS, c3); + dv_swap_cond(x1, z1, RLC_FP_DIGS, c3); + + if (!fp_srt(t, t)) { + RLC_THROW(ERR_NO_VALID); + } + fp_neg(u, t); + dv_swap_cond(t, u, RLC_FP_DIGS, fp_is_even(t) ^ s); + + fp_copy(p->x, x1); + fp_copy(p->y, t); + fp_set_dig(p->z, 1); + p->coord = BASIC; + } } else { /* This is the SwiftEC case per se. */ if (ep_curve_opt_a() != RLC_ZERO) { From 4ccd5c0d94b722932fd75e9ceefe642aecafe763 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Thu, 14 Mar 2024 03:34:47 +0100 Subject: [PATCH 04/37] New hash function. --- src/epx/relic_ep4_map.c | 402 ++++++++++++++++++++++------------------ 1 file changed, 223 insertions(+), 179 deletions(-) diff --git a/src/epx/relic_ep4_map.c b/src/epx/relic_ep4_map.c index 24a9cfab0..c43083a42 100644 --- a/src/epx/relic_ep4_map.c +++ b/src/epx/relic_ep4_map.c @@ -41,13 +41,15 @@ void ep4_map(ep4_t p, const uint8_t *msg, size_t len) { /* enough space for two field elements plus extra bytes for uniformity */ const size_t elm = (FP_PRIME + ep_param_level() + 7) / 8; uint8_t t0z, t0, t1, s[2], sign, *h = RLC_ALLOCA(uint8_t, 8 * elm + 1); - fp4_t a, c, t, u, v, w, y, x1, y1, z1; + fp4_t a, b, c, d, t, u, v, w, y, x1, y1, z1, den[3]; ctx_t *ctx = core_get(); bn_t k; bn_null(k); fp4_null(a); + fp4_null(b); fp4_null(c); + fp4_null(d); fp4_null(t); fp4_null(u); fp4_null(v); @@ -56,11 +58,16 @@ void ep4_map(ep4_t p, const uint8_t *msg, size_t len) { fp4_null(x1); fp4_null(y1); fp4_null(z1); + fp4_null(den[0]); + fp4_null(den[1]); + fp4_null(den[2]); RLC_TRY { bn_new(k); fp4_new(a); + fp4_new(b); fp4_new(c); + fp4_new(d); fp4_new(t); fp4_new(u); fp4_new(v); @@ -69,214 +76,246 @@ void ep4_map(ep4_t p, const uint8_t *msg, size_t len) { fp4_new(x1); fp4_new(y1); fp4_new(z1); + fp4_new(den[0]); + fp4_new(den[1]); + fp4_new(den[2]); - if (ep4_curve_opt_b() == RLC_ZERO) { - /* This is the approach due to Koshelev introduced in - * https://eprint.iacr.org/2021/1034.pdf */ - - md_xmd(h, 4 * elm + 1, msg, len, (const uint8_t *)"RELIC", 5); - for (int i = 0; i < 2; i++) { - for (int j = 0; j < 2; j++) { - bn_read_bin(k, h, elm); - fp_prime_conv(u[i][j], k); - h += elm; - } - } - h -= 4*elm; - - /* Compute c = 3*a^2, t^2 = 6a(9u^5 − 14au^3 + 3cu).*/ - ep4_curve_get_a(a); - fp4_neg(a, a); - fp4_sqr(c, a); - fp4_dbl(t, c); - fp4_add(c, c, t); - fp4_dbl(t, c); - fp4_add(t, t, c); - fp4_mul(t, t, u); - - fp4_sqr(v, u); - fp4_mul(w, v, u); - fp4_mul(x1, w, a); - fp4_mul_dig(x1, x1, 14); - fp4_sub(t, t, x1); - - fp4_mul(w, w, v); - fp4_dbl(x1, w); - fp4_add(w, w, x1); - fp4_dbl(x1, w); - fp4_add(w, w, x1); - fp4_add(t, t, w); - fp4_mul(t, t, a); - fp4_dbl(t, t); - fp4_dbl(x1, t); - fp4_add(t, t, x1); - dig_t c1 = fp4_is_sqr(t); - /* If t is not square, compute u = a/u, t = a*sqrt(a*t)/u^3*/ - fp4_inv(x1, u); - fp4_mul(y1, t, a); - /* If t is a square, extract its square root. */ - dv_copy_cond(t[0][0], y1[0][0], RLC_FP_DIGS, !c1); - dv_copy_cond(t[0][1], y1[0][1], RLC_FP_DIGS, !c1); - dv_copy_cond(t[1][0], y1[1][0], RLC_FP_DIGS, !c1); - dv_copy_cond(t[1][1], y1[1][1], RLC_FP_DIGS, !c1); - fp4_srt(t, t); - fp4_mul(y1, t, a); - fp4_sqr(y, x1); - fp4_mul(y, y, x1); - fp4_mul(y1, y1, y); - fp4_mul(x1, x1, a); - dv_copy_cond(u[0][0], x1[0][0], RLC_FP_DIGS, !c1); - dv_copy_cond(u[0][1], x1[0][1], RLC_FP_DIGS, !c1); - dv_copy_cond(u[1][0], x1[1][0], RLC_FP_DIGS, !c1); - dv_copy_cond(u[1][1], x1[1][1], RLC_FP_DIGS, !c1); - dv_copy_cond(t[0][0], y1[0][0], RLC_FP_DIGS, !c1); - dv_copy_cond(t[0][1], y1[0][1], RLC_FP_DIGS, !c1); - dv_copy_cond(t[1][0], y1[1][0], RLC_FP_DIGS, !c1); - dv_copy_cond(t[1][1], y1[1][1], RLC_FP_DIGS, !c1); - - /* Compute x = 2^4*i*3*a^2*u / (3*(3*u^2 - a))^2. */ - fp4_zero(y); - fp_copy(y[0][0], ctx->ep_map_c[6]); - fp4_mul(c, c, u); - for (int i = 0; i < 2; i++) { - for (int j = 0; j < 2; j++) { - fp_mul(x1[i][j], c[i][j], y[0][0]); - } + md_xmd(h, 8 * elm + 1, msg, len, (const uint8_t *)"RELIC", 5); + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + bn_read_bin(k, h, elm); + fp_prime_conv(u[i][j], k); + h += elm; + bn_read_bin(k, h, elm); + fp_prime_conv(t[i][j], k); + h += elm; } - fp4_dbl(x1, x1); - fp4_dbl(x1, x1); - fp4_dbl(x1, x1); - fp4_dbl(p->x, x1); - fp4_sqr(v, u); - fp4_dbl(z1, v); - fp4_add(z1, z1, v); - fp4_sub(z1, z1, a); - fp4_dbl(p->z, z1); - fp4_add(p->z, p->z, z1); + } + sign = h[0] & 1; + h -= 8*elm; - /* Compute y = 3*2*(i-1)*a*(3^2*u^2 + a)*t / (3*(3*u^2 - a))^3. */ - fp_sub_dig(y[0][0], y[0][0], 1); - fp4_mul(y1, y, a); - fp4_dbl(y1, y1); - fp4_dbl(p->y, y1); - fp4_add(p->y, p->y, y1); - fp4_mul(p->y, p->y, t); - fp4_dbl(y1, v); - fp4_add(y1, y1, v); - fp4_dbl(v, y1); - fp4_add(y1, y1, v); - fp4_add(y1, y1, a); - fp4_mul(p->y, p->y, y1); + if (ep_curve_opt_b() == RLC_ZERO) { + ep4_curve_get_a(p->y); + fp4_sqr(a, u); + fp4_sqr(b, a); + fp4_mul(c, b, a); + fp4_dbl(p->y, p->y); + fp4_dbl(p->y, p->y); + fp4_sqr(p->z, p->y); + fp4_mul(p->z, p->z, p->y); + fp4_add(c, c, p->z); + fp4_sqr(d, t); - /* Multiply by cofactor. */ - p->coord = JACOB; - ep4_norm(p, p); - } + fp4_mul(v, a, d); + fp4_mul(v, v, u); + fp4_mul_dig(v, v, 24); + fp_mul(v[0][0], v[0][0], core_get()->ep_map_c[4]); + fp_mul(v[0][1], v[0][1], core_get()->ep_map_c[4]); + fp_mul(v[1][0], v[1][0], core_get()->ep_map_c[4]); + fp_mul(v[1][1], v[1][1], core_get()->ep_map_c[4]); - if (ep_curve_opt_a() == RLC_ZERO) { - md_xmd(h, 8 * elm + 1, msg, len, (const uint8_t *)"RELIC", 5); - for (int i = 0; i < 2; i++) { - for (int j = 0; j < 2; j++) { - bn_read_bin(k, h, elm); - fp_prime_conv(u[i][j], k); - h += elm; - bn_read_bin(k, h, elm); - fp_prime_conv(t[i][j], k); - h += elm; - } - } - sign = h[0] & 1; - h -= 8*elm; + fp4_zero(p->x); + fp_sub_dig(p->x[0][0], core_get()->ep_map_c[4], 1); + fp_hlv(p->x[0][0], p->x[0][0]); - fp4_sqr(x1, u); - fp4_mul(x1, x1, u); - fp4_sqr(y1, t); - fp4_add(x1, x1, ctx->ep4_b); - fp4_sub(x1, x1, y1); - fp4_dbl(y1, y1); - fp4_add(y1, y1, x1); - fp4_copy(z1, u); - fp_mul(z1[0][0], z1[0][0], ctx->ep_map_c[4]); - fp_mul(z1[0][1], z1[0][1], ctx->ep_map_c[4]); - fp_mul(z1[1][0], z1[1][0], ctx->ep_map_c[4]); - fp_mul(z1[1][1], z1[1][1], ctx->ep_map_c[4]); - fp4_mul(x1, x1, z1); - fp4_mul(z1, z1, t); - fp4_dbl(z1, z1); + fp4_sqr(w, b); + fp4_mul(y, v, a); + fp4_mul(t, p->y, c); + fp4_add(y, y, t); + fp_mul(y[0][0], y[0][0], p->x[0][0]); + fp_mul(y[0][1], y[0][1], p->x[0][0]); + fp_mul(y[1][0], y[1][0], p->x[0][0]); + fp_mul(y[1][1], y[1][1], p->x[0][0]); - fp4_dbl(y, y1); - fp4_sqr(y, y); - fp4_mul(v, y1, u); - fp4_sub(v, x1, v); - fp4_mul(v, v, z1); - fp4_mul(w, y1, z1); - fp4_dbl(w, w); + fp4_add(den[0], c, v); + fp4_mul(den[0], den[0], u); + fp_mul(den[0][0][0], den[0][0][0], core_get()->ep_map_c[4]); + fp_mul(den[0][0][1], den[0][0][1], core_get()->ep_map_c[4]); + fp_mul(den[0][1][0], den[0][1][0], core_get()->ep_map_c[4]); + fp_mul(den[0][1][1], den[0][1][1], core_get()->ep_map_c[4]); + fp4_mul(den[0], den[0], p->x); + fp4_dbl(den[0], den[0]); + fp4_neg(den[0], den[0]); + fp4_mul(den[1], den[0], p->x); + fp4_sub(den[2], a, p->y); + fp4_sqr(den[2], den[2]); + fp4_mul_dig(den[2], den[2], 216); + fp4_dbl(den[2], den[2]); + fp4_neg(den[2], den[2]); + fp4_mul(den[2], den[2], b); + fp4_mul(den[2], den[2], d); - if (fp4_is_zero(w)) { + if (fp4_is_zero(den[0]) || fp4_is_zero(den[1]) || fp4_is_zero(den[2])) { ep4_set_infty(p); } else { - fp4_inv(w, w); - fp4_mul(x1, v, w); - fp4_add(y1, u, x1); - fp4_neg(y1, y1); - fp4_mul(z1, y, w); - fp4_sqr(z1, z1); - fp4_add(z1, z1, u); - - ep4_curve_get_b(w); + fp4_inv_sim(den, den, 3); + fp4_mul(t, a, p->z); + fp4_mul(y1, p->y, v); + fp4_add(y1, y1, t); + fp4_add(y1, y1, w); + fp_mul(z1[0][0], y[0][0], p->x[0][0]); + fp_mul(z1[0][1], y[0][1], p->x[0][0]); + fp_mul(z1[1][0], y[1][0], p->x[0][0]); + fp_mul(z1[1][1], y[1][1], p->x[0][0]); + fp4_add(x1, y1, z1); + fp4_add(y1, y1, y); + fp4_mul(z1, a, p->y); + fp4_add(z1, z1, b); + fp4_mul(z1, z1, p->y); + fp4_dbl(p->x, z1); + fp4_add(z1, z1, p->x); + fp4_add(z1, z1, v); + fp4_sub(z1, c, z1); + fp4_mul(z1, z1, v); + fp4_sqr(p->z, p->z); + fp4_sub(z1, p->z, z1); + fp4_add(w, w, t); + fp4_add(w, w, t); + fp4_mul(w, w, b); + fp4_add(z1, z1, w); + fp4_mul(x1, x1, den[0]); + fp4_mul(y1, y1, den[1]); + fp4_mul(z1, z1, den[2]); + + ep4_curve_get_a(p->y); fp4_sqr(t, x1); + fp4_add(t, t, p->y); fp4_mul(t, t, x1); - fp4_add(t, t, w); - fp4_sqr(u, y1); + fp4_add(u, u, p->y); fp4_mul(u, u, y1); - fp4_add(u, u, w); - fp4_sqr(v, z1); + fp4_add(v, v, p->y); fp4_mul(v, v, z1); - fp4_add(v, v, w); - dig_t c2 = fp4_is_sqr(u); - dig_t c3 = fp4_is_sqr(v); + int c2 = fp4_is_sqr(u); + int c3 = fp4_is_sqr(v); - for (int i = 0; i < 2; i++) { - for (int j = 0; j < 2; j++) { - dv_swap_cond(x1[i][j], y1[i][j], RLC_FP_DIGS, c2); - dv_swap_cond(t[i][j], u[i][j], RLC_FP_DIGS, c2); - dv_swap_cond(x1[i][j], z1[i][j], RLC_FP_DIGS, c3); - dv_swap_cond(t[i][j], v[i][j], RLC_FP_DIGS, c3); - } - } + dv_swap_cond(t[0][0], u[0][0], RLC_FP_DIGS, c2); + dv_swap_cond(t[0][1], u[0][1], RLC_FP_DIGS, c2); + dv_swap_cond(t[1][0], u[1][0], RLC_FP_DIGS, c2); + dv_swap_cond(t[1][1], u[1][1], RLC_FP_DIGS, c2); + dv_swap_cond(x1[0][0], y1[0][0], RLC_FP_DIGS, c2); + dv_swap_cond(x1[0][1], y1[0][1], RLC_FP_DIGS, c2); + dv_swap_cond(x1[1][0], y1[1][0], RLC_FP_DIGS, c2); + dv_swap_cond(x1[1][1], y1[1][1], RLC_FP_DIGS, c2); + dv_swap_cond(t[0][0], v[0][0], RLC_FP_DIGS, c3); + dv_swap_cond(t[0][1], v[0][1], RLC_FP_DIGS, c3); + dv_swap_cond(t[1][0], v[1][0], RLC_FP_DIGS, c3); + dv_swap_cond(t[1][1], v[1][1], RLC_FP_DIGS, c3); + dv_swap_cond(x1[0][0], z1[0][0], RLC_FP_DIGS, c3); + dv_swap_cond(x1[0][1], z1[0][1], RLC_FP_DIGS, c3); + dv_swap_cond(x1[1][0], z1[1][0], RLC_FP_DIGS, c3); + dv_swap_cond(x1[1][1], z1[1][1], RLC_FP_DIGS, c3); if (!fp4_srt(t, t)) { RLC_THROW(ERR_NO_VALID); } - - for (int i = 0; i < 2; i++) { - t0z = fp_is_zero(t[i][0]); - fp_prime_back(k, t[i][0]); - t0 = bn_get_bit(k, 0); - fp_prime_back(k, t[i][1]); - t1 = bn_get_bit(k, 0); - /* t[0] == 0 ? sgn0(t[1]) : sgn0(t[0]) */ - s[i] = t0 | (t0z & t1); - } - - t0z = fp2_is_zero(t[0]); - sign ^= (s[0] | (t0z & s[1])); - fp4_neg(u, t); - dv_swap_cond(t[0][0], u[0][0], RLC_FP_DIGS, sign); - dv_swap_cond(t[0][1], u[0][1], RLC_FP_DIGS, sign); - dv_swap_cond(t[1][0], u[1][0], RLC_FP_DIGS, sign); - dv_swap_cond(t[1][1], u[1][1], RLC_FP_DIGS, sign); + c2 = fp_is_even(t[0][0]); + dv_swap_cond(t[0][0], u[0][0], RLC_FP_DIGS, c2 ^ sign); + dv_swap_cond(t[0][1], u[0][1], RLC_FP_DIGS, c2 ^ sign); + dv_swap_cond(t[1][0], u[1][0], RLC_FP_DIGS, c2 ^ sign); + dv_swap_cond(t[1][1], u[1][1], RLC_FP_DIGS, c2 ^ sign); fp4_copy(p->x, x1); fp4_copy(p->y, t); fp4_set_dig(p->z, 1); p->coord = BASIC; } + } else { + if (ep_curve_opt_a() == RLC_ZERO) { + fp4_sqr(x1, u); + fp4_mul(x1, x1, u); + fp4_sqr(y1, t); + fp4_add(x1, x1, ctx->ep4_b); + fp4_sub(x1, x1, y1); + fp4_dbl(y1, y1); + fp4_add(y1, y1, x1); + fp4_copy(z1, u); + fp_mul(z1[0][0], z1[0][0], ctx->ep_map_c[4]); + fp_mul(z1[0][1], z1[0][1], ctx->ep_map_c[4]); + fp_mul(z1[1][0], z1[1][0], ctx->ep_map_c[4]); + fp_mul(z1[1][1], z1[1][1], ctx->ep_map_c[4]); + fp4_mul(x1, x1, z1); + fp4_mul(z1, z1, t); + fp4_dbl(z1, z1); + + fp4_dbl(y, y1); + fp4_sqr(y, y); + fp4_mul(v, y1, u); + fp4_sub(v, x1, v); + fp4_mul(v, v, z1); + fp4_mul(w, y1, z1); + fp4_dbl(w, w); + + if (fp4_is_zero(w)) { + ep4_set_infty(p); + } else { + fp4_inv(w, w); + fp4_mul(x1, v, w); + fp4_add(y1, u, x1); + fp4_neg(y1, y1); + fp4_mul(z1, y, w); + fp4_sqr(z1, z1); + fp4_add(z1, z1, u); + + ep4_curve_get_b(w); + + fp4_sqr(t, x1); + fp4_mul(t, t, x1); + fp4_add(t, t, w); + + fp4_sqr(u, y1); + fp4_mul(u, u, y1); + fp4_add(u, u, w); + + fp4_sqr(v, z1); + fp4_mul(v, v, z1); + fp4_add(v, v, w); + + dig_t c2 = fp4_is_sqr(u); + dig_t c3 = fp4_is_sqr(v); + + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + dv_swap_cond(x1[i][j], y1[i][j], RLC_FP_DIGS, c2); + dv_swap_cond(t[i][j], u[i][j], RLC_FP_DIGS, c2); + dv_swap_cond(x1[i][j], z1[i][j], RLC_FP_DIGS, c3); + dv_swap_cond(t[i][j], v[i][j], RLC_FP_DIGS, c3); + } + } + + if (!fp4_srt(t, t)) { + RLC_THROW(ERR_NO_VALID); + } + + for (int i = 0; i < 2; i++) { + t0z = fp_is_zero(t[i][0]); + fp_prime_back(k, t[i][0]); + t0 = bn_get_bit(k, 0); + fp_prime_back(k, t[i][1]); + t1 = bn_get_bit(k, 0); + /* t[0] == 0 ? sgn0(t[1]) : sgn0(t[0]) */ + s[i] = t0 | (t0z & t1); + } + + t0z = fp2_is_zero(t[0]); + sign ^= (s[0] | (t0z & s[1])); + + fp4_neg(u, t); + dv_swap_cond(t[0][0], u[0][0], RLC_FP_DIGS, sign); + dv_swap_cond(t[0][1], u[0][1], RLC_FP_DIGS, sign); + dv_swap_cond(t[1][0], u[1][0], RLC_FP_DIGS, sign); + dv_swap_cond(t[1][1], u[1][1], RLC_FP_DIGS, sign); + + fp4_copy(p->x, x1); + fp4_copy(p->y, t); + fp4_set_dig(p->z, 1); + p->coord = BASIC; + } + } } ep4_mul_cof(p, p); @@ -287,7 +326,9 @@ void ep4_map(ep4_t p, const uint8_t *msg, size_t len) { RLC_FINALLY { bn_free(k); fp4_free(a); + fp4_free(b); fp4_free(c); + fp4_free(d); fp4_free(t); fp4_free(u); fp4_free(v); @@ -296,6 +337,9 @@ void ep4_map(ep4_t p, const uint8_t *msg, size_t len) { fp4_free(x1); fp4_free(y1); fp4_free(z1); + fp4_free(den[0]); + fp4_free(den[1]); + fp4_free(den[2]); RLC_FREE(h); } } From e8c157265f7ba7d9c1bde5c6374e8ab5696f9aa9 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Thu, 14 Mar 2024 16:45:22 +0100 Subject: [PATCH 05/37] Remove N16_P765 parameters. --- include/relic_ep.h | 2 -- include/relic_fp.h | 2 -- src/ep/relic_ep_param.c | 28 ++-------------------------- src/epx/relic_ep4_curve.c | 26 -------------------------- src/fp/relic_fp_param.c | 13 +------------ src/low/x64-asm-12l/macro.s | 17 +---------------- 6 files changed, 4 insertions(+), 84 deletions(-) diff --git a/include/relic_ep.h b/include/relic_ep.h index 4279ce9b3..5e5cdc5a7 100644 --- a/include/relic_ep.h +++ b/include/relic_ep.h @@ -175,8 +175,6 @@ enum { K18_P638, /** Scott-Guillevic curve with embedding degree 18. */ SG18_P638, - /** New family with embeeding degree 16. */ - N16_P765, /* Fotiadis-Moartindale with embedding degree 16. */ FM16_P765, /** Kachisa-Schaefer-Scott with embedding degree 16. */ diff --git a/include/relic_fp.h b/include/relic_fp.h index 8dabbf887..4bcff5b3b 100644 --- a/include/relic_fp.h +++ b/include/relic_fp.h @@ -156,8 +156,6 @@ enum { K18_638, /** 638-bit prime for SG curve with embedding degree 18. */ SG18_638, - /** 765-bit prime for new family with embedding degree 16. */ - N16_765, /** 765-bit prime for FM curve with embedding degree 16. */ FM16_765, /** 766-bit prime for KSS curve with embedding degree 16. */ diff --git a/src/ep/relic_ep_param.c b/src/ep/relic_ep_param.c index 9234bd7ef..b41addc7d 100644 --- a/src/ep/relic_ep_param.c +++ b/src/ep/relic_ep_param.c @@ -659,19 +659,6 @@ /** @} */ #endif -#if defined(EP_ENDOM) && FP_PRIME == 765 -/** - * Parameters for a 765-bit pairing-friendly prime curve. - */ -/** @{ */ -#define N16_P765_A "1" -#define N16_P765_B "0" -#define N16_P765_X "71A955588AD4E8236811AA1770428A86CA487504E3964600E51FAD83E8EDF03883360471538D685B7CA156BC9AD56E6FED4BE76C099A752E70E867A8FD79CDFBD0C00294E59C4F2F348302FB270336BE8D2EC25E6234D33CB33C8840BC059D4" -#define N16_P765_Y "F6DEB4CAA67257010A3286CECBE4E4127D53701CF5897E3426F675BEFE36F60CD0E779433306B0A34C826584307F96100ECA6EB01F69637C2EB0B295E6C13E9721A5EA0FC05A04B47FC565AEBF41016525A69F554BC9D68D9EF2B5CD77D1D4" -#define N16_P765_R "9965D956A0DBC8AF273C0100000000000000000000000000000000000000000000000000000000000000000000000001" -#define N16_P765_H "26597655A836F22BC9CF003FFFFFFFFFFFFFA30FAB330D5A7F0000000000000000000000384F01000000000000000000" -/** @} */ - /** * Parameters for a 765-bit pairing-friendly prime curve. */ @@ -1176,11 +1163,6 @@ void ep_param_set(int param) { break; #endif #if defined(EP_ENDOM) && FP_PRIME == 765 - case N16_P765: - ASSIGN(N16_P765, N16_765); - endom = 1; - pairf = EP_N16; - break; case FM16_P765: ASSIGN(FM16_P765, FM16_765); endom = 1; @@ -1482,8 +1464,7 @@ int ep_param_set_any_endom(void) { //ep_param_set(SG18_P638); #endif #elif FP_PRIME == 765 - ep_param_set(N16_P765); - //ep_param_set(FM16_P765); + ep_param_set(FM16_P765); #elif FP_PRIME == 766 //ep_param_set(K16_P766); ep_param_set(N16_P766); @@ -1603,8 +1584,7 @@ int ep_param_set_any_pairf(void) { extension = 3; #endif #elif FP_PRIME == 765 - ep_param_set(N16_P765); - //ep_param_set(FM16_P765); + ep_param_set(FM16_P765); type = RLC_EP_MTYPE; extension = 4; #elif FP_PRIME == 766 @@ -1765,9 +1745,6 @@ void ep_param_print(void) { case SG18_P638: util_banner("Curve SG18-P638:", 0); break; - case N16_P765: - util_banner("Curve N16-P765:", 0); - break; case N16_P766: util_banner("Curve N16-P766:", 0); break; @@ -1839,7 +1816,6 @@ int ep_param_level(void) { case B12_P455: return 140; case NIST_P384: - case N16_P765: case FM16_P765: case K16_P766: case FM18_P768: diff --git a/src/epx/relic_ep4_curve.c b/src/epx/relic_ep4_curve.c index e52dd7ce1..140a6dad6 100644 --- a/src/epx/relic_ep4_curve.c +++ b/src/epx/relic_ep4_curve.c @@ -130,29 +130,6 @@ /** @} */ #endif -#if defined(EP_ENDOM) && FP_PRIME == 765 -/** @{ */ -#define N16_P765_A0 "0" -#define N16_P765_A1 "0" -#define N16_P765_A2 "1" -#define N16_P765_A3 "0" -#define N16_P765_B0 "0" -#define N16_P765_B1 "0" -#define N16_P765_B2 "0" -#define N16_P765_B3 "0" -#define N16_P765_X0 "004C4A977FFCA75E15AEB9C8B8EAB0CAB30A488EF0424C9658FB8A05E4CA6A1E2997FAD9F0DE053D69751CBA6F49059CB2E0BAC08AA25A575BC1E1468E2E1BD78FB87C2ABCA3C20AB55B8B18F5266CD05FC4B26DFB091FB4A130312132D09614" -#define N16_P765_X1 "0EE552F7143FA01D9919C462036AEC50C76BE752823C49910EF121F05B22494897A213DDF33166C6F8CA16995A83B9526EADE2D74366C349313DD126CC89249F282C1F3715BB690A1AD176F53F6EF6D75873E8A857FC0794F2AE1AE7C12D8F8B" -#define N16_P765_X2 "14A32856133B3D59AC0DC99C3D34D830A7003BF41CED95B126BF0D2BAAB3C92A0E24CAFE5B6030F0FBD29E7CF08F808787A273B171B05C50CD052A3C031288BFBDE6CF4F53270477B12621F35193B5FAD47E6A77B33BD2B9FF9D3687AE6AE48D" -#define N16_P765_X3 "0C478BC2CC455CDE662F112B2C4F9B24A0DA57515A44DD61591E3F3532AA8C640D84CAFADECADAE4D249D1038F3D030E173CFA87C51BF90BF49B1DFAB84B9ACC0ABD72349A3ADAB2BF056EF39AB6101F1D89C6C9D7761BFC9D5E0E050E299138" -#define N16_P765_Y0 "00DEDAE825C143B4DFCB3A8B5F87BA7BADE72AF792AB7DFF198FE234AE16D8B5B45D9B033415FD4B59099DAFEF8A5782AC4D86BCD555AFCBA31111E07E34D1233A59505BEABB6FDEDE470A5182D0B6DDE2E48BE313D445EA0D11062D109382D6" -#define N16_P765_Y1 "0D77B57018036B23B857B823374756FC62C05244E47D5237961304B9B7BFDE3BE874B58B5F7B4805726BB0042CEEFF3FBF76F7EBEA3D36CC1E46BBAF4819F76DB1BFBC05E8577D4E992407C245418170CE03D6DD3A153DCC217F995191749773" -#define N16_P765_Y2 "0ADFD23B22ABAF4C64ECE323D37878D1D437EB77860ABF4EC0C2ADA1054D4BAB06422FF17E3A59FE4AE7B254F2228138A50E819616A6F6F44671CE2FB19962D4687510516A6786E06060560D714FF35C05C7F3B8600E5ADBE796DC7FD331C8C4" -#define N16_P765_Y3 "0E9EA14175A8DAA9C83F5A6C7CCBE6E7CBE534B5AEB9A92B8689F73AF7356EEC955FBE18F6D687561E3D13D781DCB3B90C78392093343E30FE68A5E92A61434F366945C20896A3AD11856AF4B4558791CA9BDE9598734BBE1B33CE618E809982" -#define N16_P765_R "9965D956A0DBC8AF273C0100000000000000000000000000000000000000000000000000000000000000000000000001" -#define N16_P765_H "719C0F18991838D271B16518B194F6F145242127E49DC9A094D6CB692E28DC5F17FFA92321BB6498A829BA8C587373ED1198514FA52F945FEF0BD7DB7AEECF44BA1DC59375BEFBFC8467B4B2DCB8BC87E9FBD6615875E2CDA99813230882AE3B19CC78906339C451CE982DED4C8C2B44D01B86D2440D89A5F5AC3BF04E1393A4DC6712246A0E6F966D734F449D38655E7BF22CC56C0609840678EB8285D561CE1E65F73F1142F3C526CA2E28484D72F0B0492E78604F0AAB2DBBD13A082DFB9513D5C0D5C6CCFD13A6D3DA2EEC9A2AEFD40EC51A940F59D64F45BADB3EBEB96FCDAE11951717C5DF0125F274306A90C0FCF19D34FCB0D37683A2743F8C3ECAFE55863AA6B35BD14C7A894E6D7DC05D12E0D551AE0E46AB11D80610EA749AAEA445FE78651B64091AB67E32F33AA4CE905E2AF4A64A03B25A5C08233C52CBB94BF8000E13C04000003C08000000002" -/** @} */ -#endif - #if defined(EP_ENDOM) && FP_PRIME == 765 /** @{ */ #define FM16_P765_A0 "0" @@ -444,9 +421,6 @@ void ep4_curve_set_twist(int type) { ASSIGN(B24_P509); break; #elif FP_PRIME == 765 - case N16_P765: - ASSIGN(N16_P765); - break; case FM16_P765: ASSIGN(FM16_P765); break; diff --git a/src/fp/relic_fp_param.c b/src/fp/relic_fp_param.c index 307437052..a3f545520 100644 --- a/src/fp/relic_fp_param.c +++ b/src/fp/relic_fp_param.c @@ -569,16 +569,6 @@ void fp_param_set(int param) { fp_prime_set_pairf(t0, EP_SG18); break; #elif FP_PRIME == 765 - case N16_765: - /* u = -(2^48 - 2^44 + 2^37) */ - bn_set_2b(t0, 48); - bn_set_2b(t1, 44); - bn_sub(t0, t0, t1); - bn_set_2b(t1, 37); - bn_add(t0, t0, t1); - bn_neg(t0, t0); - fp_prime_set_pairf(t0, EP_N16); - break; case FM16_765: /* u = 2^48-2^44-2^38+2^31 */ bn_set_2b(t0, 48); @@ -821,8 +811,7 @@ int fp_param_set_any_tower(void) { //fp_param_set(SG18_638); #endif #elif FP_PRIME == 765 - fp_param_set(N16_765); - //fp_param_set(FM16_765); + fp_param_set(FM16_765); #elif FP_PRIME == 766 fp_param_set(N16_766); //fp_param_set(K16_766); diff --git a/src/low/x64-asm-12l/macro.s b/src/low/x64-asm-12l/macro.s index 17cb0f874..851ddcc76 100644 --- a/src/low/x64-asm-12l/macro.s +++ b/src/low/x64-asm-12l/macro.s @@ -75,21 +75,7 @@ #define P11 0x3FFFB8002607F379 #define U0 0xF282381A203F6933 #elif FP_PRIME == 765 -/* AFG16-765 */ -#define P0 0x0000000000000001 -#define P1 0x00000000384F0100 -#define P2 0x7D00000000000000 -#define P3 0xFFFEE92F0199280F -#define P4 0xF10B013FFFFFFFFF -#define P5 0x4AC04FAC4912BADA -#define P6 0x6AC50E5A1A6AEAE4 -#define P7 0xEE9C1E7F21BD9E92 -#define P8 0x249F514A2A836FBF -#define P9 0x8866F5670199231B -#define P10 0xB2847B1232833CC3 -#define P11 0x16FAB993B0C96754 -#define U0 0xFFFFFFFFFFFFFFFF -/* FM16-765 +/* FM16-765 */ #define P0 0x1000EFC080000001 #define P1 0x0000000038223FF0 #define P2 0x0000000000000000 @@ -103,7 +89,6 @@ #define P10 0xBC5664C6F237BCB4 #define P11 0x166A30BEAF4CE221 #define U0 0xD000EFC07FFFFFFF -*/ #endif #if defined(__APPLE__) From 72daaa74ff2f83475221de528d9e48cec803f06d Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Thu, 14 Mar 2024 16:48:15 +0100 Subject: [PATCH 06/37] Fix compilation bug. --- src/ep/relic_ep_param.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ep/relic_ep_param.c b/src/ep/relic_ep_param.c index b41addc7d..253c47560 100644 --- a/src/ep/relic_ep_param.c +++ b/src/ep/relic_ep_param.c @@ -659,6 +659,7 @@ /** @} */ #endif +#if defined(EP_ENDOM) && FP_PRIME == 765 /** * Parameters for a 765-bit pairing-friendly prime curve. */ From 1d746d722b7e35903ba0a7f91b74497ce0daadf8 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Thu, 28 Mar 2024 23:04:25 +0100 Subject: [PATCH 07/37] Update presets. --- preset/gmp-pbc-bls12-1150.sh | 2 ++ preset/x64-pbc-afg16-765.sh | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) create mode 100755 preset/gmp-pbc-bls12-1150.sh delete mode 100755 preset/x64-pbc-afg16-765.sh diff --git a/preset/gmp-pbc-bls12-1150.sh b/preset/gmp-pbc-bls12-1150.sh new file mode 100755 index 000000000..a2c95863d --- /dev/null +++ b/preset/gmp-pbc-bls12-1150.sh @@ -0,0 +1,2 @@ +#!/bin/sh +cmake -DWSIZE=64 -DRAND=UDEV -DSHLIB=OFF -DSTBIN=ON -DTIMER=CYCLE -DCHECK=off -DVERBS=off -DARITH=gmp-sec -DFP_PRIME=1150 -DFP_METHD="INTEG;INTEG;INTEG;MONTY;JMPDS;JMPDS;SLIDE" -DCFLAGS="-O3 -funroll-loops -fomit-frame-pointer -finline-small-functions -march=native -mtune=native" -DFP_PMERS=off -DFP_QNRES=on -DFPX_METHD="INTEG;INTEG;LAZYR" -DEP_PLAIN=off -DEP_SUPER=off -DPP_METHD="LAZYR;OATEP" $1 diff --git a/preset/x64-pbc-afg16-765.sh b/preset/x64-pbc-afg16-765.sh deleted file mode 100755 index 4cc9fbcdb..000000000 --- a/preset/x64-pbc-afg16-765.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -cmake -DWSIZE=64 -DRAND=UDEV -DSHLIB=OFF -DSTBIN=ON -DTIMER=CYCLE -DCHECK=off -DVERBS=off -DARITH=x64-asm-12l -DBN_PRECI=3072 -DFP_PRIME=765 -DFP_METHD="INTEG;INTEG;INTEG;MONTY;JMPDS;JMPDS;SLIDE" -DCFLAGS="-O3 -funroll-loops -fomit-frame-pointer -march=native -mtune=native" -DFP_PMERS=off -DFP_QNRES=off -DFPX_METHD="INTEG;INTEG;LAZYR" -DEP_PLAIN=off -DEP_SUPER=off -DPP_METHD="LAZYR;OATEP" -DWITH="ALL" $1 From cf085802a34b8e2b21ca5d72db6fd8627f8103b6 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Fri, 29 Mar 2024 02:26:29 +0100 Subject: [PATCH 08/37] Big refactoring of the EP module. --- include/relic_core.h | 4 - include/relic_ep.h | 2 + include/relic_fp.h | 2 + src/ep/relic_ep_add.c | 617 +-------------- src/ep/relic_ep_curve.c | 27 - src/ep/relic_ep_dbl.c | 18 +- src/ep/relic_ep_map.c | 2 +- src/ep/relic_ep_param.c | 28 + src/epx/relic_ep2_add.c | 63 +- src/epx/relic_ep2_curve.c | 17 + src/epx/relic_ep2_map.c | 2 +- src/epx/relic_ep2_mul_cof.c | 1 - src/epx/relic_ep3_add.c | 71 +- src/epx/relic_ep3_mul_cof.c | 1 - src/epx/relic_ep4_add.c | 71 +- src/epx/relic_ep4_mul_cof.c | 1 - src/epx/relic_ep8_add.c | 71 +- src/epx/relic_ep8_mul_cof.c | 1 - src/fp/relic_fp_param.c | 18 + src/tmpl/relic_ep_add_tmpl.h | 706 ++++++++++++++++++ .../{relic_tmpl_map.h => relic_ep_map_tmpl.h} | 4 +- 21 files changed, 818 insertions(+), 909 deletions(-) create mode 100644 src/tmpl/relic_ep_add_tmpl.h rename src/tmpl/{relic_tmpl_map.h => relic_ep_map_tmpl.h} (98%) diff --git a/include/relic_core.h b/include/relic_core.h index ae3b08241..92bbbd580 100644 --- a/include/relic_core.h +++ b/include/relic_core.h @@ -267,8 +267,6 @@ typedef struct _ctx_t { fp_st ep_a; /** The b-coefficient of the elliptic curve. */ fp_st ep_b; - /** The value 3b used in elliptic curve arithmetic. */ - fp_st ep_b3; /** The generator of the elliptic curve. */ ep_st ep_g; /** The order of the group of points in the elliptic curve. */ @@ -292,8 +290,6 @@ typedef struct _ctx_t { int ep_opt_a; /** Optimization identifier for the b-coefficient. */ int ep_opt_b; - /** Optimization identifier for the b3 value. */ - int ep_opt_b3; /** Flag that stores if the prime curve has efficient endomorphisms. */ int ep_is_endom; /** Flag that stores if the prime curve is supersingular. */ diff --git a/include/relic_ep.h b/include/relic_ep.h index 5e5cdc5a7..6579f6ae9 100644 --- a/include/relic_ep.h +++ b/include/relic_ep.h @@ -183,6 +183,8 @@ enum { N16_P766, /* Fotiadis-Moartindale with embedding degree 18. */ FM18_P768, + /** Barreto-Lynn-Scott curve with embedding degree 12. */ + B12_P1150, /** 1536-bit supersingular curve. */ SS_P1536, /** 3072-bit supersingular curve. */ diff --git a/include/relic_fp.h b/include/relic_fp.h index 4bcff5b3b..061a885d0 100644 --- a/include/relic_fp.h +++ b/include/relic_fp.h @@ -166,6 +166,8 @@ enum { FM18_768, /** 1024-bit prime for CTIDH. */ CTIDH_1024, + /** 1150-bit prime for BLS curve with embedding degree 12. */ + B12_1150, /** 1536-bit prime for supersingular curve with embedding degree k = 2. */ SS_1536, /** 2048-bit prime for CTDIH. */ diff --git a/src/ep/relic_ep_add.c b/src/ep/relic_ep_add.c index e8694421c..dec994be7 100644 --- a/src/ep/relic_ep_add.c +++ b/src/ep/relic_ep_add.c @@ -30,6 +30,7 @@ */ #include "relic_core.h" +#include "relic_ep_add_tmpl.h" /*============================================================================*/ /* Private definitions */ @@ -46,70 +47,7 @@ * @param[in] p - the first point to add. * @param[in] q - the second point to add. */ -static void ep_add_basic_imp(ep_t r, fp_t s, const ep_t p, const ep_t q) { - fp_t t0, t1, t2; - - fp_null(t0); - fp_null(t1); - fp_null(t2); - - RLC_TRY { - fp_new(t0); - fp_new(t1); - fp_new(t2); - - /* t0 = x2 - x1. */ - fp_sub(t0, q->x, p->x); - /* t1 = y2 - y1. */ - fp_sub(t1, q->y, p->y); - - /* If t0 is zero. */ - if (fp_is_zero(t0)) { - if (fp_is_zero(t1)) { - /* If t1 is zero, q = p, should have doubled. */ - ep_dbl_basic(r, p); - } else { - /* If t1 is not zero and t0 is zero, q = -p and r = infinity. */ - ep_set_infty(r); - } - } else { - /* t2 = 1/(x2 - x1). */ - fp_inv(t2, t0); - /* t2 = lambda = (y2 - y1)/(x2 - x1). */ - fp_mul(t2, t1, t2); - - /* x3 = lambda^2 - x2 - x1. */ - fp_sqr(t1, t2); - fp_sub(t0, t1, p->x); - fp_sub(t0, t0, q->x); - - /* y3 = lambda * (x1 - x3) - y1. */ - fp_sub(t1, p->x, t0); - fp_mul(t1, t2, t1); - fp_sub(r->y, t1, p->y); - - fp_copy(r->x, t0); - fp_copy(r->z, p->z); - - if (s != NULL) { - fp_copy(s, t2); - } - - r->coord = BASIC; - } - fp_free(t0); - fp_free(t1); - fp_free(t2); - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp_free(t0); - fp_free(t1); - fp_free(t2); - } -} +TMPL_ADD_BASIC_IMP(ep, fp); #endif /* EP_ADD == BASIC */ @@ -117,165 +55,13 @@ static void ep_add_basic_imp(ep_t r, fp_t s, const ep_t p, const ep_t q) { /** * Adds a point represented in homogeneous coordinates to a point represented in - * projective coordinates. + * affine coordinates on an ordinary prime elliptic curve. * * @param[out] r - the result. * @param[in] p - the projective point. * @param[in] q - the affine point. */ -static void ep_add_projc_mix(ep_t r, const ep_t p, const ep_t q) { - fp_t t0, t1, t2, t3, t4, t5; - - fp_null(t0); - fp_null(t1); - fp_null(t2); - fp_null(t3); - fp_null(t4); - fp_null(t5); - - RLC_TRY { - fp_new(t0); - fp_new(t1); - fp_new(t2); - fp_new(t3); - fp_new(t4); - fp_new(t5); - - /* Formulas for mixed addition from - * "Complete addition formulas for prime order elliptic curves" - * by Joost Renes, Craig Costello, and Lejla Batina - * https://eprint.iacr.org/2015/1060.pdf - */ - - fp_mul(t0, p->x, q->x); - fp_mul(t1, p->y, q->y); - fp_add(t3, q->x, q->y); - fp_add(t4, p->x, p->y); - fp_mul(t3, t3, t4); - fp_add(t4, t0, t1); - fp_sub(t3, t3, t4); - - if (ep_curve_opt_a() == RLC_ZERO) { - /* Cost of 11M + 2m_3b + 13a. */ - if (p->coord == BASIC) { - /* Save 1M + 1m_3b if z1 = 1. */ - fp_add(t4, q->y, p->y); - fp_add(r->y, q->x, p->x); - fp_add(r->z, t1, ep_curve_get_b3()); - fp_sub(t1, t1, ep_curve_get_b3()); - } else { - fp_mul(t4, q->y, p->z); - fp_add(t4, t4, p->y); - fp_mul(r->y, q->x, p->z); - fp_add(r->y, r->y, p->x); - ep_curve_mul_b3(t2, p->z); - fp_add(r->z, t1, t2); - fp_sub(t1, t1, t2); - } - fp_dbl(r->x, t0); - fp_add(t0, t0, r->x); - ep_curve_mul_b3(r->y, r->y); - fp_mul(r->x, t4, r->y); - fp_mul(t2, t3, t1); - fp_sub(r->x, t2, r->x); - fp_mul(r->y, t0, r->y); - fp_mul(t1, t1, r->z); - fp_add(r->y, t1, r->y); - fp_mul(t0, t0, t3); - fp_mul(r->z, r->z, t4); - fp_add(r->z, r->z, t0); - } else if (ep_curve_opt_a() == RLC_MIN3) { - /* Cost of 11M + 2m_b + 23a. */ - if (p->coord == BASIC) { - /* Save 2M + 3a if z1 = 1. */ - fp_set_dig(t2, 3); - fp_add(t4, q->y, p->y); - fp_add(r->y, q->x, p->x); - fp_sub(r->x, r->y, ep_curve_get_b()); - } else { - fp_dbl(t2, p->z); - fp_add(t2, t2, p->z); - fp_mul(t4, q->y, p->z); - fp_add(t4, t4, p->y); - fp_mul(r->y, q->x, p->z); - fp_add(r->y, r->y, p->x); - ep_curve_mul_b(r->z, p->z); - fp_sub(r->x, r->y, r->z); - } - fp_dbl(r->z, r->x); - fp_add(r->x, r->x, r->z); - fp_sub(r->z, t1, r->x); - fp_add(r->x, t1, r->x); - ep_curve_mul_b(r->y, r->y); - fp_sub(r->y, r->y, t2); - fp_sub(r->y, r->y, t0); - fp_dbl(t1, r->y); - fp_add(r->y, t1, r->y); - fp_dbl(t1, t0); - fp_add(t0, t1, t0); - fp_sub(t0, t0, t2); - fp_mul(t1, t4, r->y); - fp_mul(t2, t0, r->y); - fp_mul(r->y, r->x, r->z); - fp_add(r->y, r->y, t2); - fp_mul(r->x, t3, r->x); - fp_sub(r->x, r->x, t1); - fp_mul(r->z, t4, r->z); - fp_mul(t1, t3, t0); - fp_add(r->z, r->z, t1); - } else { - /* Cost of 11M + 3m_a + 2m_3b + 17a. */ - if (p->coord == BASIC) { - /* Save 1M + 1m_a + 1m_3b if z1 = 1. */ - fp_copy(t2, ep_curve_get_a()); - fp_add(t4, q->x, p->x); - fp_add(t5, q->y, p->y); - ep_curve_mul_a(r->z, t4); - fp_add(r->z, r->z, ep_curve_get_b3()); - } else { - ep_curve_mul_a(t2, p->z); - fp_mul(t4, q->x, p->z); - fp_add(t4, t4, p->x); - fp_mul(t5, q->y, p->z); - fp_add(t5, t5, p->y); - ep_curve_mul_b3(r->x, p->z); - ep_curve_mul_a(r->z, t4); - fp_add(r->z, r->x, r->z); - } - fp_sub(r->x, t1, r->z); - fp_add(r->z, t1, r->z); - fp_mul(r->y, r->x, r->z); - fp_dbl(t1, t0); - fp_add(t1, t1, t0); - ep_curve_mul_b3(t4, t4); - fp_add(t1, t1, t2); - fp_sub(t2, t0, t2); - ep_curve_mul_a(t2, t2); - fp_add(t4, t4, t2); - fp_mul(t0, t1, t4); - fp_add(r->y, r->y, t0); - fp_mul(t0, t5, t4); - fp_mul(r->x, t3, r->x); - fp_sub(r->x, r->x, t0); - fp_mul(t0, t3, t1); - fp_mul(r->z, t5, r->z); - fp_add(r->z, r->z, t0); - } - - r->coord = PROJC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp_free(t0); - fp_free(t1); - fp_free(t2); - fp_free(t3); - fp_free(t4); - fp_free(t5); - } -} +TMPL_ADD_PROJC_MIX(ep, fp); /** * Adds two points represented in homogeneous coordinates on an ordinary prime @@ -285,164 +71,7 @@ static void ep_add_projc_mix(ep_t r, const ep_t p, const ep_t q) { * @param[in] p - the first point to add. * @param[in] q - the second point to add. */ -static void ep_add_projc_imp(ep_t r, const ep_t p, const ep_t q) { -#if defined(EP_MIXED) && defined(STRIP) - /* If code size is a problem, leave only the mixed version. */ - ep_add_projc_mix(r, p, q); -#else /* General addition. */ - -#if defined(EP_MIXED) || !defined(STRIP) - /* Test if z2 = 1 only if mixed coordinates are turned on. */ - if (q->coord == BASIC) { - ep_add_projc_mix(r, p, q); - return; - } -#endif - fp_t t0, t1, t2, t3, t4, t5; - - fp_null(t0); - fp_null(t1); - fp_null(t2); - fp_null(t3); - fp_null(t4); - fp_null(t5); - - RLC_TRY { - fp_new(t0); - fp_new(t1); - fp_new(t2); - fp_new(t3); - fp_new(t4); - fp_new(t5); - - /* Formulas for point addition from - * "Complete addition formulas for prime order elliptic curves" - * by Joost Renes, Craig Costello, and Lejla Batina - * https://eprint.iacr.org/2015/1060.pdf - */ - fp_mul(t0, p->x, q->x); - fp_mul(t1, p->y, q->y); - fp_mul(t2, p->z, q->z); - fp_add(t3, p->x, p->y); - fp_add(t4, q->x, q->y); - fp_mul(t3, t3, t4); - fp_add(t4, t0, t1); - fp_sub(t3, t3, t4); - if (ep_curve_opt_a() == RLC_ZERO) { - /* Cost of 12M + 2m_3b + 19a. */ - fp_add(t4, p->y, p->z); - fp_add(t5, q->y, q->z); - fp_mul(t4, t4, t5); - fp_add(t5, t1, t2); - fp_sub(t4, t4, t5); - fp_add(r->y, q->x, q->z); - fp_add(r->x, p->x, p->z); - fp_mul(r->x, r->x, r->y); - fp_add(r->y, t0, t2); - fp_sub(r->y, r->x, r->y); - fp_dbl(r->x, t0); - fp_add(t0, t0, r->x); - ep_curve_mul_b3(t2, t2); - fp_add(r->z, t1, t2); - fp_sub(t1, t1, t2); - ep_curve_mul_b3(r->y, r->y); - fp_mul(r->x, t4, r->y); - fp_mul(t2, t3, t1); - fp_sub(r->x, t2, r->x); - fp_mul(r->y, t0, r->y); - fp_mul(t1, t1, r->z); - fp_add(r->y, t1, r->y); - fp_mul(t0, t0, t3); - fp_mul(r->z, r->z, t4); - fp_add(r->z, r->z, t0); - } else if (ep_curve_opt_a() == RLC_MIN3) { - /* Cost of 12M + 2m_b + 29a. */ - fp_add(t4, p->y, p->z); - fp_add(t5, q->y, q->z); - fp_mul(t4, t4, t5); - fp_add(t5, t1, t2); - fp_sub(t4, t4, t5); - fp_add(r->x, p->x, p->z); - fp_add(r->y, q->x, q->z); - fp_mul(r->x, r->x, r->y); - fp_add(r->y, t0, t2); - fp_sub(r->y, r->x, r->y); - ep_curve_mul_b(r->z, t2); - fp_sub(r->x, r->y, r->z); - fp_dbl(r->z, r->x); - fp_add(r->x, r->x, r->z); - fp_sub(r->z, t1, r->x); - fp_add(r->x, t1, r->x); - ep_curve_mul_b(r->y, r->y); - fp_dbl(t1, t2); - fp_add(t2, t1, t2); - fp_sub(r->y, r->y, t2); - fp_sub(r->y, r->y, t0); - fp_dbl(t1, r->y); - fp_add(r->y, t1, r->y); - fp_dbl(t1, t0); - fp_add(t0, t1, t0); - fp_sub(t0, t0, t2); - fp_mul(t1, t4, r->y); - fp_mul(t2, t0, r->y); - fp_mul(r->y, r->x, r->z); - fp_add(r->y, r->y, t2); - fp_mul(r->x, t3, r->x); - fp_sub(r->x, r->x, t1); - fp_mul(r->z, t4, r->z); - fp_mul(t1, t3, t0); - fp_add(r->z, r->z, t1); - } else { - /* Cost of 12M + 3m_a + 2_m3b + 23a. */ - fp_add(t4, p->x, p->z); - fp_add(t5, q->x, q->z); - fp_mul(t4, t4, t5); - fp_add(t5, t0, t2); - fp_sub(t4, t4, t5); - fp_add(t5, p->y, p->z); - fp_add(r->x, q->y, q->z); - fp_mul(t5, t5, r->x); - fp_add(r->x, t1, t2); - fp_sub(t5, t5, r->x); - ep_curve_mul_a(r->z, t4); - ep_curve_mul_b3(r->x, t2); - fp_add(r->z, r->x, r->z); - fp_sub(r->x, t1, r->z); - fp_add(r->z, t1, r->z); - fp_mul(r->y, r->x, r->z); - fp_dbl(t1, t0); - fp_add(t1, t1, t0); - ep_curve_mul_a(t2, t2); - ep_curve_mul_b3(t4, t4); - fp_add(t1, t1, t2); - fp_sub(t2, t0, t2); - ep_curve_mul_a(t2, t2); - fp_add(t4, t4, t2); - fp_mul(t0, t1, t4); - fp_add(r->y, r->y, t0); - fp_mul(t0, t5, t4); - fp_mul(r->x, t3, r->x); - fp_sub(r->x, r->x, t0); - fp_mul(t0, t3, t1); - fp_mul(r->z, t5, r->z); - fp_add(r->z, r->z, t0); - } - - r->coord = PROJC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp_free(t0); - fp_free(t1); - fp_free(t2); - fp_free(t3); - fp_free(t4); - fp_free(t5); - } -#endif -} +TMPL_ADD_PROJC_IMP(ep, fp); #endif /* EP_ADD == PROJC */ @@ -450,123 +79,13 @@ static void ep_add_projc_imp(ep_t r, const ep_t p, const ep_t q) { /** * Adds a point represented in Jacobian coordinates to a point represented in - * projective coordinates. + * affine coordinates on an ordinary prime elliptic curve. * * @param[out] r - the result. * @param[in] p - the projective point. * @param[in] q - the affine point. */ -static void ep_add_jacob_mix(ep_t r, const ep_t p, const ep_t q) { - fp_t t0, t1, t2, t3, t4, t5, t6; - - fp_null(t0); - fp_null(t1); - fp_null(t2); - fp_null(t3); - fp_null(t4); - fp_null(t5); - fp_null(t6); - - RLC_TRY { - fp_new(t0); - fp_new(t1); - fp_new(t2); - fp_new(t3); - fp_new(t4); - fp_new(t5); - fp_new(t6); - - /* madd-2007-bl formulas: 7M + 4S + 9add + 1*4 + 3*2. */ - /* http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-madd-2007-bl */ - - if (p->coord != BASIC) { - /* t0 = z1^2. */ - fp_sqr(t0, p->z); - - /* t3 = U2 = x2 * z1^2. */ - fp_mul(t3, q->x, t0); - - /* t1 = S2 = y2 * z1^3. */ - fp_mul(t1, t0, p->z); - fp_mul(t1, t1, q->y); - - /* t3 = H = U2 - x1. */ - fp_sub(t3, t3, p->x); - - /* t1 = R = 2 * (S2 - y1). */ - fp_sub(t1, t1, p->y); - fp_dbl(t1, t1); - } else { - /* H = x2 - x1. */ - fp_sub(t3, q->x, p->x); - - /* t1 = R = 2 * (y2 - y1). */ - fp_sub(t1, q->y, p->y); - fp_dbl(t1, t1); - } - - /* t2 = HH = H^2. */ - fp_sqr(t2, t3); - - /* If H is zero. */ - if (fp_is_zero(t3)) { - if (fp_is_zero(t1)) { - /* If I is zero, p = q, should have doubled. */ - ep_dbl_jacob(r, p); - } else { - /* If I is not zero, q = -p, r = infinity. */ - ep_set_infty(r); - } - } else { - /* t4 = I = 4*HH. */ - fp_dbl(t4, t2); - fp_dbl(t4, t4); - - /* t5 = J = H * I. */ - fp_mul(t5, t3, t4); - - /* t4 = V = x1 * I. */ - fp_mul(t4, p->x, t4); - - /* x3 = R^2 - J - 2 * V. */ - fp_sqr(r->x, t1); - fp_sub(r->x, r->x, t5); - fp_dbl(t6, t4); - fp_sub(r->x, r->x, t6); - - /* y3 = R * (V - x3) - 2 * Y1 * J. */ - fp_sub(t4, t4, r->x); - fp_mul(t4, t4, t1); - fp_mul(t1, p->y, t5); - fp_dbl(t1, t1); - fp_sub(r->y, t4, t1); - - if (p->coord != BASIC) { - /* z3 = (z1 + H)^2 - z1^2 - HH. */ - fp_add(r->z, p->z, t3); - fp_sqr(r->z, r->z); - fp_sub(r->z, r->z, t0); - fp_sub(r->z, r->z, t2); - } else { - /* z3 = 2 * H. */ - fp_dbl(r->z, t3); - } - } - r->coord = JACOB; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp_free(t0); - fp_free(t1); - fp_free(t2); - fp_free(t3); - fp_free(t4); - fp_free(t5); - fp_free(t6); - } -} +TMPL_ADD_JACOB_MIX(ep, fp); /** * Adds two points represented in Jacobian coordinates on an ordinary prime @@ -576,127 +95,7 @@ static void ep_add_jacob_mix(ep_t r, const ep_t p, const ep_t q) { * @param[in] p - the first point to add. * @param[in] q - the second point to add. */ -static void ep_add_jacob_imp(ep_t r, const ep_t p, const ep_t q) { -#if defined(EP_MIXED) && defined(STRIP) - /* If code size is a problem, leave only the mixed version. */ - ep_add_jacob_mix(r, p, q); -#else /* General addition. */ - -#if defined(EP_MIXED) || !defined(STRIP) - /* Test if z2 = 1 only if mixed coordinates are turned on. */ - if (q->coord == BASIC) { - ep_add_jacob_mix(r, p, q); - return; - } -#endif - - fp_t t0, t1, t2, t3, t4, t5, t6; - - fp_null(t0); - fp_null(t1); - fp_null(t2); - fp_null(t3); - fp_null(t4); - fp_null(t5); - fp_null(t6); - - RLC_TRY { - fp_new(t0); - fp_new(t1); - fp_new(t2); - fp_new(t3); - fp_new(t4); - fp_new(t5); - fp_new(t6); - - /* add-2007-bl formulas: 11M + 5S + 9add + 4*2 */ - /* http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-2007-bl */ - - /* t0 = z1^2. */ - fp_sqr(t0, p->z); - - /* t1 = z2^2. */ - fp_sqr(t1, q->z); - - /* t2 = U1 = x1 * z2^2. */ - fp_mul(t2, p->x, t1); - - /* t3 = U2 = x2 * z1^2. */ - fp_mul(t3, q->x, t0); - - /* t6 = z1^2 + z2^2. */ - fp_add(t6, t0, t1); - - /* t0 = S2 = y2 * z1^3. */ - fp_mul(t0, t0, p->z); - fp_mul(t0, t0, q->y); - - /* t1 = S1 = y1 * z2^3. */ - fp_mul(t1, t1, q->z); - fp_mul(t1, t1, p->y); - - /* t3 = H = U2 - U1. */ - fp_sub(t3, t3, t2); - - /* t0 = R = 2 * (S2 - S1). */ - fp_sub(t0, t0, t1); - fp_dbl(t0, t0); - - /* If E is zero. */ - if (fp_is_zero(t3)) { - if (fp_is_zero(t0)) { - /* If I is zero, p = q, should have doubled. */ - ep_dbl_jacob(r, p); - } else { - /* If I is not zero, q = -p, r = infinity. */ - ep_set_infty(r); - } - } else { - /* t4 = I = (2*H)^2. */ - fp_dbl(t4, t3); - fp_sqr(t4, t4); - - /* t5 = J = H * I. */ - fp_mul(t5, t3, t4); - - /* t4 = V = U1 * I. */ - fp_mul(t4, t2, t4); - - /* x3 = R^2 - J - 2 * V. */ - fp_sqr(r->x, t0); - fp_sub(r->x, r->x, t5); - fp_dbl(t2, t4); - fp_sub(r->x, r->x, t2); - - /* y3 = R * (V - x3) - 2 * S1 * J. */ - fp_sub(t4, t4, r->x); - fp_mul(t4, t4, t0); - fp_mul(t1, t1, t5); - fp_dbl(t1, t1); - fp_sub(r->y, t4, t1); - - /* z3 = ((z1 + z2)^2 - z1^2 - z2^2) * H. */ - fp_add(r->z, p->z, q->z); - fp_sqr(r->z, r->z); - fp_sub(r->z, r->z, t6); - fp_mul(r->z, r->z, t3); - } - r->coord = JACOB; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp_free(t0); - fp_free(t1); - fp_free(t2); - fp_free(t3); - fp_free(t4); - fp_free(t5); - fp_free(t6); - } -#endif -} +TMPL_ADD_JACOB_IMP(ep, fp); #endif /* EP_ADD == JACOB */ diff --git a/src/ep/relic_ep_curve.c b/src/ep/relic_ep_curve.c index 6d2d9a2a3..aa7f4debb 100644 --- a/src/ep/relic_ep_curve.c +++ b/src/ep/relic_ep_curve.c @@ -203,12 +203,9 @@ static void ep_curve_set(const fp_t a, const fp_t b, const ep_t g, const bn_t r, fp_copy(ctx->ep_a, a); fp_copy(ctx->ep_b, b); - fp_dbl(ctx->ep_b3, b); - fp_add(ctx->ep_b3, ctx->ep_b3, b); detect_opt(&(ctx->ep_opt_a), ctx->ep_a); detect_opt(&(ctx->ep_opt_b), ctx->ep_b); - detect_opt(&(ctx->ep_opt_b3), ctx->ep_b3); ctx->ep_is_ctmap = ctmap; ep_curve_set_map(); @@ -266,10 +263,6 @@ dig_t *ep_curve_get_b(void) { return core_get()->ep_b; } -dig_t *ep_curve_get_b3(void) { - return core_get()->ep_b3; -} - #if defined(EP_ENDOM) && (EP_MUL == LWNAF || EP_FIX == COMBS || EP_FIX == LWNAF || EP_SIM == INTER || !defined(STRIP)) dig_t *ep_curve_get_beta(void) { @@ -340,26 +333,6 @@ void ep_curve_mul_b(fp_t c, const fp_t a) { } } -void ep_curve_mul_b3(fp_t c, const fp_t a) { - ctx_t *ctx = core_get(); - switch (ctx->ep_opt_b3) { - case RLC_ZERO: - fp_zero(c); - break; - case RLC_ONE: - fp_copy(c, a); - break; -#if FP_RDC != MONTY - case RLC_TINY: - fp_mul_dig(c, a, ctx->ep_b3[0]); - break; -#endif - default: - fp_mul(c, a, ctx->ep_b3); - break; - } -} - int ep_curve_is_endom(void) { return core_get()->ep_is_endom; } diff --git a/src/ep/relic_ep_dbl.c b/src/ep/relic_ep_dbl.c index 1a160d02c..b1a6ecafc 100644 --- a/src/ep/relic_ep_dbl.c +++ b/src/ep/relic_ep_dbl.c @@ -159,11 +159,14 @@ static void ep_dbl_projc_imp(ep_t r, const ep_t p) { if (p->coord == BASIC) { /* Save 1M + 1S + 1m_b3 if z1 = 1. */ fp_copy(t1, p->y); - fp_copy(t2, ep_curve_get_b3()); + fp_dbl(t2, ep_curve_get_b()); + fp_add(t2, t2, ep_curve_get_b()); } else { fp_mul(t1, p->y, p->z); fp_sqr(t2, p->z); - ep_curve_mul_b3(t2, t2); + fp_dbl(t5, t2); + fp_add(t5, t5, t2); + ep_curve_mul_b(t2, t5); } fp_dbl(r->z, t0); fp_dbl(r->z, r->z); @@ -218,11 +221,14 @@ static void ep_dbl_projc_imp(ep_t r, const ep_t p) { /* Common cost of 8M + 3S + 3m_a + 2m_3b + 15a. */ if (p->coord == BASIC) { /* Save 1S + 1m_b + 1m_a if z1 = 1. */ - fp_copy(r->y, ep_curve_get_b3()); + fp_dbl(r->y, ep_curve_get_b()); + fp_add(r->y, r->y, ep_curve_get_b()); fp_copy(t2, ep_curve_get_a()); } else { fp_sqr(t2, p->z); - ep_curve_mul_b3(r->y, t2); + fp_dbl(t5, t2); + fp_add(t5, t5, t2); + ep_curve_mul_b(r->y, t5); ep_curve_mul_a(t2, t2); } fp_mul(r->z, p->x, p->z); @@ -233,7 +239,9 @@ static void ep_dbl_projc_imp(ep_t r, const ep_t p) { fp_add(r->y, t1, r->y); fp_mul(r->y, r->x, r->y); fp_mul(r->x, t3, r->x); - ep_curve_mul_b3(r->z, r->z); + fp_dbl(t5, r->z); + fp_add(t5, t5, r->z); + ep_curve_mul_b(r->z, t5); fp_sub(t3, t0, t2); ep_curve_mul_a(t3, t3); fp_add(t3, t3, r->z); diff --git a/src/ep/relic_ep_map.c b/src/ep/relic_ep_map.c index 97cbaedfe..f07b6aac0 100644 --- a/src/ep/relic_ep_map.c +++ b/src/ep/relic_ep_map.c @@ -31,7 +31,7 @@ #include "relic_core.h" #include "relic_md.h" -#include "relic_tmpl_map.h" +#include "relic_ep_map_tmpl.h" /*============================================================================*/ /* Private definitions */ diff --git a/src/ep/relic_ep_param.c b/src/ep/relic_ep_param.c index 253c47560..e14a85302 100644 --- a/src/ep/relic_ep_param.c +++ b/src/ep/relic_ep_param.c @@ -714,6 +714,20 @@ /** @} */ #endif +#if defined(EP_ENDOM) && FP_PRIME == 1150 +/** + * Parameters for a 383-bit pairing-friendly prime curve. + */ +/** @{ */ +#define B12_P1150_A "0" +#define B12_P1150_B "1" +#define B12_P1150_X "1C9E4A85748AF56BFCDB28AA09E80CF55FFED5E25D92B882B3CAB4202EDA1DEAA62DA8D3B9C204FF9C0647A1BCC17EB60CB7F57D1FA5CFE131200DC511C6636B898515C2C714B1F07ADFB97874E7B9E22D3D7206B327792949E05EA8B5CAF91AA486D72C522A4BDD387B63E5EF12374F1FB766B86CE65ACAC5EF3E05826EC1AE3C18D2A14C1181915DA9A7C3760152D1" +#define B12_P1150_Y "3116D7FDB1130460DF71722020D3B32FBBBB01A6A77C999B13EE9C0C3DE4D6CA81E886AC80F933A31A78F0850DD9ED0A8DE122C179481FEF031A50910D7A726BB3F1BAE5E8AC186EDEE69C85A043169308C06B4277E65BDE0AD872F58938F84033ED21E9DC0A6DEE2957BA884AE9582CE5AD88C00CFA4D323E686FF864F872C2EEAD356A43E9BB4A59452C08F4E89E9A" +#define B12_P1150_R "C5C1000000000000001B30F00000000000003622EC6000034BC000057869AF00005703000575ED588100ABBDB4005D60C600000BA2C6103D98AD58000B730C000210975450006301E00100052800004000108000050000000000200000000001" +#define B12_P1150_H "4B00000000000000000528000000000000000A16B00000014000000058000000000B00000055555555556AAAAAAAAAAC" +/** @} */ +#endif + #if defined(EP_SUPER) && FP_PRIME == 1536 /** * Parameters for a 1536-bit supersingular elliptic curve. @@ -1189,6 +1203,13 @@ void ep_param_set(int param) { pairf = EP_FM18; break; #endif +#if defined(EP_ENDOM) && FP_PRIME == 1150 + case B12_P1150: + ASSIGN(B12_P1150, B12_1150); + endom = 1; + pairf = EP_B12; + break; +#endif #if defined(EP_SUPER) && FP_PRIME == 1536 case SS_P1536: ASSIGN(SS_P1536, SS_1536); @@ -1471,6 +1492,8 @@ int ep_param_set_any_endom(void) { ep_param_set(N16_P766); #elif FP_PRIME == 768 ep_param_set(FM18_P768); +#elif FP_PRIME == 1150 + ep_param_set(B12_P1150); #else r = RLC_ERR; #endif @@ -1597,6 +1620,10 @@ int ep_param_set_any_pairf(void) { ep_param_set(FM18_P768); type = RLC_EP_MTYPE; extension = 3; +#elif FP_PRIME == 1150 + ep_param_set(B12_P1150); + type = RLC_EP_MTYPE; + extension = 2; #elif FP_PRIME == 1536 ep_param_set(SS_P1536); extension = 1; @@ -1817,6 +1844,7 @@ int ep_param_level(void) { case B12_P455: return 140; case NIST_P384: + case B12_P1150: case FM16_P765: case K16_P766: case FM18_P768: diff --git a/src/epx/relic_ep2_add.c b/src/epx/relic_ep2_add.c index 2e4f563dc..f793de14a 100644 --- a/src/epx/relic_ep2_add.c +++ b/src/epx/relic_ep2_add.c @@ -31,6 +31,7 @@ */ #include "relic_core.h" +#include "relic_ep_add_tmpl.h" /*============================================================================*/ /* Private definitions */ @@ -47,67 +48,7 @@ * @param p - the first point to add. * @param q - the second point to add. */ -static void ep2_add_basic_imp(ep2_t r, fp2_t s, const ep2_t p, const ep2_t q) { - fp2_t t0, t1, t2; - - fp2_null(t0); - fp2_null(t1); - fp2_null(t2); - - RLC_TRY { - fp2_new(t0); - fp2_new(t1); - fp2_new(t2); - - /* t0 = x2 - x1. */ - fp2_sub(t0, q->x, p->x); - /* t1 = y2 - y1. */ - fp2_sub(t1, q->y, p->y); - - /* If t0 is zero. */ - if (fp2_is_zero(t0)) { - if (fp2_is_zero(t1)) { - /* If t1 is zero, q = p, should have doubled. */ - ep2_dbl_slp_basic(r, s, p); - } else { - /* If t1 is not zero and t0 is zero, q = -p and r = infty. */ - ep2_set_infty(r); - } - } else { - /* t2 = 1/(x2 - x1). */ - fp2_inv(t2, t0); - /* t2 = lambda = (y2 - y1)/(x2 - x1). */ - fp2_mul(t2, t1, t2); - - /* x3 = lambda^2 - x2 - x1. */ - fp2_sqr(t1, t2); - fp2_sub(t0, t1, p->x); - fp2_sub(t0, t0, q->x); - - /* y3 = lambda * (x1 - x3) - y1. */ - fp2_sub(t1, p->x, t0); - fp2_mul(t1, t2, t1); - fp2_sub(r->y, t1, p->y); - - fp2_copy(r->x, t0); - fp2_copy(r->z, p->z); - - if (s != NULL) { - fp2_copy(s, t2); - } - - r->coord = BASIC; - } - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp2_free(t0); - fp2_free(t1); - fp2_free(t2); - } -} +TMPL_ADD_BASIC_IMP(ep2, fp2); #endif /* EP_ADD == BASIC */ diff --git a/src/epx/relic_ep2_curve.c b/src/epx/relic_ep2_curve.c index 3ec781ba0..5af21b291 100644 --- a/src/epx/relic_ep2_curve.c +++ b/src/epx/relic_ep2_curve.c @@ -275,6 +275,23 @@ /** @} */ #endif +#if defined(EP_ENDOM) && FP_PRIME == 638 +/** @{ */ +#define B12_1150_A0 "0" +#define B12_1150_A1 "0" +#define B12_1150_B0 "4" +#define B12_1150_B1 "4" +#define B12_1150_X0 "3C57F8F05130D336804E30CAA7BB45E06244DFC0BA836056B036038703719449A42CCC7C34452B4EE2DCA3CBCE0B7637E14E9CA88BDEF105440FB3F84AA95C75DE0BA05686394492B8648BB71D5E7F39" +#define B12_1150_X1 "07B30040203566584002D6DBB49A3DA1D99ECA3CBCD113C07E0CF1FFB3FA4F87F034A034C86F56DB380F2810AC329ED8BD6FE0F4D5C1FA26949739AF82D3AAD4702D2186862B0293E16C5EDDDDA3C922" +#define B12_1150_Y0 "29ED1A1C4F3F5AFC64AB2BA97CFA4D17998061179331A1C34E024B7D82134C60A3569F644E4155753C48698C8A01C80C0C3CEC9E3BDE2E5E22D81BBB514FD24DE186FEBA69B82E88809BFCCE51A1840F" +#define B12_1150_Y1 "3795191221DB4917EEE4B7B85BC7D7CA0C60E82116064463FED0892BA82ACECF905E6DB8083C5F589F04DB80E3203C1B2BEB52ACDED6DF96FC515F36761E7152AEED13369A504FE38C4FF93860B89550" +#define B12_1150_R "50F94035FF4000FFFFFFFFFFF9406BFDC0040000000000000035FB801DFFBFFFFFFFFFFFFFFF401BFF80000000000000000000FFC01" +#define B12_1150_H "2D88688DBA18275F5801BFFD4DDE93725697788C46C7B4BC8050639BA17EA2158B6784CCACDDECE490643943E5376D29C71C96B894056CCCC13C3DC6AAAAAA0F89601DC2979B3721C71C71C8B38CB8AEFEEB9E1C71C71C71C4B9FED17AE51B8E38E38E38E38FC954E8C65" +#define B12_1150_MAPU0 "0" +#define B12_1150_MAPU1 "1" +/** @} */ +#endif + #if defined(EP_ENDOM) && FP_PRIME == 638 /** @{ */ #define B12_P638_A0 "0" diff --git a/src/epx/relic_ep2_map.c b/src/epx/relic_ep2_map.c index 229d5f349..80f01ac24 100644 --- a/src/epx/relic_ep2_map.c +++ b/src/epx/relic_ep2_map.c @@ -32,7 +32,7 @@ #include "relic_core.h" #include "relic_md.h" -#include "relic_tmpl_map.h" +#include "relic_ep_map_tmpl.h" /*============================================================================*/ /* Private definitions */ diff --git a/src/epx/relic_ep2_mul_cof.c b/src/epx/relic_ep2_mul_cof.c index ee8be325a..505e77860 100644 --- a/src/epx/relic_ep2_mul_cof.c +++ b/src/epx/relic_ep2_mul_cof.c @@ -32,7 +32,6 @@ #include "relic_core.h" #include "relic_md.h" -#include "relic_tmpl_map.h" /*============================================================================*/ /* Private definitions */ diff --git a/src/epx/relic_ep3_add.c b/src/epx/relic_ep3_add.c index 3c3626342..628b48bf4 100644 --- a/src/epx/relic_ep3_add.c +++ b/src/epx/relic_ep3_add.c @@ -31,6 +31,7 @@ */ #include "relic_core.h" +#include "relic_ep_add_tmpl.h" /*============================================================================*/ /* Private definitions */ @@ -42,72 +43,12 @@ * Adds two points represented in affine coordinates on an ordinary prime * elliptic curve. * - * @param r - the result. - * @param s - the resulting slope. - * @param p - the first point to add. - * @param q - the second point to add. + * @param[out] r - the result. + * @param[out] s - the slope. + * @param[in] p - the first point to add. + * @param[in] q - the second point to add. */ -static void ep3_add_basic_imp(ep3_t r, fp3_t s, const ep3_t p, const ep3_t q) { - fp3_t t0, t1, t2; - - fp3_null(t0); - fp3_null(t1); - fp3_null(t2); - - RLC_TRY { - fp3_new(t0); - fp3_new(t1); - fp3_new(t2); - - /* t0 = x2 - x1. */ - fp3_sub(t0, q->x, p->x); - /* t1 = y2 - y1. */ - fp3_sub(t1, q->y, p->y); - - /* If t0 is zero. */ - if (fp3_is_zero(t0)) { - if (fp3_is_zero(t1)) { - /* If t1 is zero, q = p, should have doubled. */ - ep3_dbl_slp_basic(r, s, p); - } else { - /* If t1 is not zero and t0 is zero, q = -p and r = infty. */ - ep3_set_infty(r); - } - } else { - /* t2 = 1/(x2 - x1). */ - fp3_inv(t2, t0); - /* t2 = lambda = (y2 - y1)/(x2 - x1). */ - fp3_mul(t2, t1, t2); - - /* x3 = lambda^2 - x2 - x1. */ - fp3_sqr(t1, t2); - fp3_sub(t0, t1, p->x); - fp3_sub(t0, t0, q->x); - - /* y3 = lambda * (x1 - x3) - y1. */ - fp3_sub(t1, p->x, t0); - fp3_mul(t1, t2, t1); - fp3_sub(r->y, t1, p->y); - - fp3_copy(r->x, t0); - fp3_copy(r->z, p->z); - - if (s != NULL) { - fp3_copy(s, t2); - } - - r->coord = BASIC; - } - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp3_free(t0); - fp3_free(t1); - fp3_free(t2); - } -} +TMPL_ADD_BASIC_IMP(ep3, fp3); #endif /* EP_ADD == BASIC */ diff --git a/src/epx/relic_ep3_mul_cof.c b/src/epx/relic_ep3_mul_cof.c index a0deca806..3c3711ca8 100644 --- a/src/epx/relic_ep3_mul_cof.c +++ b/src/epx/relic_ep3_mul_cof.c @@ -32,7 +32,6 @@ #include "relic_core.h" #include "relic_md.h" -#include "relic_tmpl_map.h" /*============================================================================*/ /* Private definitions */ diff --git a/src/epx/relic_ep4_add.c b/src/epx/relic_ep4_add.c index 57fda0cfc..3ff25328a 100644 --- a/src/epx/relic_ep4_add.c +++ b/src/epx/relic_ep4_add.c @@ -31,6 +31,7 @@ */ #include "relic_core.h" +#include "relic_ep_add_tmpl.h" /*============================================================================*/ /* Private definitions */ @@ -42,72 +43,12 @@ * Adds two points represented in affine coordinates on an ordinary prime * elliptic curve. * - * @param r - the result. - * @param s - the resulting slope. - * @param p - the first point to add. - * @param q - the second point to add. + * @param[out] r - the result. + * @param[out] s - the slope. + * @param[in] p - the first point to add. + * @param[in] q - the second point to add. */ -static void ep4_add_basic_imp(ep4_t r, fp4_t s, const ep4_t p, const ep4_t q) { - fp4_t t0, t1, t2; - - fp4_null(t0); - fp4_null(t1); - fp4_null(t2); - - RLC_TRY { - fp4_new(t0); - fp4_new(t1); - fp4_new(t2); - - /* t0 = x2 - x1. */ - fp4_sub(t0, q->x, p->x); - /* t1 = y2 - y1. */ - fp4_sub(t1, q->y, p->y); - - /* If t0 is zero. */ - if (fp4_is_zero(t0)) { - if (fp4_is_zero(t1)) { - /* If t1 is zero, q = p, should have doubled. */ - ep4_dbl_slp_basic(r, s, p); - } else { - /* If t1 is not zero and t0 is zero, q = -p and r = infty. */ - ep4_set_infty(r); - } - } else { - /* t2 = 1/(x2 - x1). */ - fp4_inv(t2, t0); - /* t2 = lambda = (y2 - y1)/(x2 - x1). */ - fp4_mul(t2, t1, t2); - - /* x3 = lambda^2 - x2 - x1. */ - fp4_sqr(t1, t2); - fp4_sub(t0, t1, p->x); - fp4_sub(t0, t0, q->x); - - /* y3 = lambda * (x1 - x3) - y1. */ - fp4_sub(t1, p->x, t0); - fp4_mul(t1, t2, t1); - fp4_sub(r->y, t1, p->y); - - fp4_copy(r->x, t0); - fp4_copy(r->z, p->z); - - if (s != NULL) { - fp4_copy(s, t2); - } - - r->coord = BASIC; - } - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp4_free(t0); - fp4_free(t1); - fp4_free(t2); - } -} +TMPL_ADD_BASIC_IMP(ep4, fp4); #endif /* EP_ADD == BASIC */ diff --git a/src/epx/relic_ep4_mul_cof.c b/src/epx/relic_ep4_mul_cof.c index 65d4f7f64..b79aa17b6 100644 --- a/src/epx/relic_ep4_mul_cof.c +++ b/src/epx/relic_ep4_mul_cof.c @@ -32,7 +32,6 @@ #include "relic_core.h" #include "relic_md.h" -#include "relic_tmpl_map.h" /*============================================================================*/ /* Public definitions */ diff --git a/src/epx/relic_ep8_add.c b/src/epx/relic_ep8_add.c index 15dde9000..e0316eaff 100644 --- a/src/epx/relic_ep8_add.c +++ b/src/epx/relic_ep8_add.c @@ -31,6 +31,7 @@ */ #include "relic_core.h" +#include "relic_ep_add_tmpl.h" /*============================================================================*/ /* Private definitions */ @@ -42,72 +43,12 @@ * Adds two points represented in affine coordinates on an ordinary prime * elliptic curve. * - * @param r - the result. - * @param s - the resulting slope. - * @param p - the first point to add. - * @param q - the second point to add. + * @param[out] r - the result. + * @param[out] s - the slope. + * @param[in] p - the first point to add. + * @param[in] q - the second point to add. */ -static void ep8_add_basic_imp(ep8_t r, fp8_t s, const ep8_t p, const ep8_t q) { - fp8_t t0, t1, t2; - - fp8_null(t0); - fp8_null(t1); - fp8_null(t2); - - RLC_TRY { - fp8_new(t0); - fp8_new(t1); - fp8_new(t2); - - /* t0 = x2 - x1. */ - fp8_sub(t0, q->x, p->x); - /* t1 = y2 - y1. */ - fp8_sub(t1, q->y, p->y); - - /* If t0 is zero. */ - if (fp8_is_zero(t0)) { - if (fp8_is_zero(t1)) { - /* If t1 is zero, q = p, should have doubled. */ - ep8_dbl_slp_basic(r, s, p); - } else { - /* If t1 is not zero and t0 is zero, q = -p and r = infty. */ - ep8_set_infty(r); - } - } else { - /* t2 = 1/(x2 - x1). */ - fp8_inv(t2, t0); - /* t2 = lambda = (y2 - y1)/(x2 - x1). */ - fp8_mul(t2, t1, t2); - - /* x3 = lambda^2 - x2 - x1. */ - fp8_sqr(t1, t2); - fp8_sub(t0, t1, p->x); - fp8_sub(t0, t0, q->x); - - /* y3 = lambda * (x1 - x3) - y1. */ - fp8_sub(t1, p->x, t0); - fp8_mul(t1, t2, t1); - fp8_sub(r->y, t1, p->y); - - fp8_copy(r->x, t0); - fp8_copy(r->z, p->z); - - if (s != NULL) { - fp8_copy(s, t2); - } - - r->coord = BASIC; - } - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp8_free(t0); - fp8_free(t1); - fp8_free(t2); - } -} +TMPL_ADD_BASIC_IMP(ep8, fp8); #endif /* EP_ADD == BASIC */ diff --git a/src/epx/relic_ep8_mul_cof.c b/src/epx/relic_ep8_mul_cof.c index 65f85a660..16b818a53 100644 --- a/src/epx/relic_ep8_mul_cof.c +++ b/src/epx/relic_ep8_mul_cof.c @@ -32,7 +32,6 @@ #include "relic_core.h" #include "relic_md.h" -#include "relic_tmpl_map.h" /*============================================================================*/ /* Public definitions */ diff --git a/src/fp/relic_fp_param.c b/src/fp/relic_fp_param.c index a3f545520..8ff841371 100644 --- a/src/fp/relic_fp_param.c +++ b/src/fp/relic_fp_param.c @@ -623,6 +623,22 @@ void fp_param_set(int param) { bn_read_str(p, STR_P1024, strlen(STR_P1024), 16); fp_prime_set_dense(p); break; +#elif FP_PRIME == 1150 + case B12_1150: + /* x = -(2^192 - 2^188 + 2^115 + 2^110 + 2^44 + 1 */ + bn_set_2b(t0, 192); + bn_set_2b(t1, 188); + bn_sub(t0, t0, t1); + bn_set_2b(t1, 115); + bn_add(t0, t0, t1); + bn_set_2b(t1, 110); + bn_add(t0, t0, t1); + bn_set_2b(t1, 44); + bn_add(t0, t0, t1); + bn_add_dig(t0, t0, 1); + bn_neg(t0, t0); + fp_prime_set_pairf(t0, EP_B12); + break; #elif FP_PRIME == 1536 case SS_1536: /* x = 2^255 + 2^41 + 1. */ @@ -817,6 +833,8 @@ int fp_param_set_any_tower(void) { //fp_param_set(K16_766); #elif FP_PRIME == 768 fp_param_set(FM18_768); +#elif FP_PRIME == 1150 + fp_param_set(B12_1150); #elif FP_PRIME == 1536 fp_param_set(SS_1536); #elif FP_PRIME == 3072 diff --git a/src/tmpl/relic_ep_add_tmpl.h b/src/tmpl/relic_ep_add_tmpl.h new file mode 100644 index 000000000..bc33bb852 --- /dev/null +++ b/src/tmpl/relic_ep_add_tmpl.h @@ -0,0 +1,706 @@ +/* + * RELIC is an Efficient LIbrary for Cryptography + * Copyright (c) 2024 RELIC Authors + * + * This file is part of RELIC. RELIC is legal property of its developers, + * whose names are not listed here. Please refer to the COPYRIGHT file + * for contact information. + * + * RELIC is free software; you can redistribute it and/or modify it under the + * terms of the version 2.1 (or later) of the GNU Lesser General Public License + * as published by the Free Software Foundation; or version 2.0 of the Apache + * License as published by the Apache Software Foundation. See the LICENSE files + * for more details. + * + * RELIC is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the LICENSE files for more details. + * + * You should have received a copy of the GNU Lesser General Public or the + * Apache License along with RELIC. If not, see + * or . + */ + +/** + * @file + * + * Template for point addition on prime elliptic curves. + * + * @ingroup tmpl + */ + +#include "relic_core.h" + +/*============================================================================*/ +/* Private definitions */ +/*============================================================================*/ + +/** + * Defines a template for point addition in affine coordinates. + * + * @param[in] C - the curve. + * @param[in] F - the field prefix. + */ +#define TMPL_ADD_BASIC_IMP(C, F) \ + static void C##_add_basic_imp(C##_t r, F##_t s, const C##_t p, \ + const C##_t q) { \ + F##_t t0, t1, t2; \ + \ + F##_null(t0); \ + F##_null(t1); \ + F##_null(t2); \ + \ + RLC_TRY { \ + F##_new(t0); \ + F##_new(t1); \ + F##_new(t2); \ + \ + /* t0 = x2 - x1. */ \ + F##_sub(t0, q->x, p->x); \ + /* t1 = y2 - y1. */ \ + F##_sub(t1, q->y, p->y); \ + \ + /* If t0 is zero. */ \ + if (F##_is_zero(t0)) { \ + if (F##_is_zero(t1)) { \ + /* If t1 is zero, q = p, should have doubled. */ \ + C##_dbl_basic(r, p); \ + } else { \ + /* If t1 != 0 and t0 == 0, q = -p and r = infinity. */ \ + C##_set_infty(r); \ + } \ + } else { \ + /* t2 = 1/(x2 - x1). */ \ + F##_inv(t2, t0); \ + /* t2 = lambda = (y2 - y1)/(x2 - x1). */ \ + F##_mul(t2, t1, t2); \ + \ + /* x3 = lambda^2 - x2 - x1. */ \ + F##_sqr(t1, t2); \ + F##_sub(t0, t1, p->x); \ + F##_sub(t0, t0, q->x); \ + \ + /* y3 = lambda * (x1 - x3) - y1. */ \ + F##_sub(t1, p->x, t0); \ + F##_mul(t1, t2, t1); \ + F##_sub(r->y, t1, p->y); \ + \ + F##_copy(r->x, t0); \ + F##_copy(r->z, p->z); \ + \ + if (s != NULL) { \ + F##_copy(s, t2); \ + } \ + \ + r->coord = BASIC; \ + } \ + F##_free(t0); \ + F##_free(t1); \ + F##_free(t2); \ + } RLC_CATCH_ANY { \ + RLC_THROW(ERR_CAUGHT); \ + } RLC_FINALLY { \ + F##_free(t0); \ + F##_free(t1); \ + F##_free(t2); \ + } \ + } \ + +/** + * Defines a template for mixed point addition in homogeneous projective + * coordinates. + * + * Formulas for mixed addition from + * "Complete addition formulas for prime order elliptic curves" + * by Joost Renes, Craig Costello, and Lejla Batina + * https://eprint.iacr.org/2015/1060.pdf + * + * @param[in] C - the curve. + * @param[in] F - the field prefix. + */ +#define TMPL_ADD_PROJC_MIX(C, F) \ + static void C##_add_projc_mix(C##_t r, const C##_t p, const C##_t q) { \ + F##_t t0, t1, t2, t3, t4, t5; \ + \ + F##_null(t0); \ + F##_null(t1); \ + F##_null(t2); \ + F##_null(t3); \ + F##_null(t4); \ + F##_null(t5); \ + \ + RLC_TRY { \ + F##_new(t0); \ + F##_new(t1); \ + F##_new(t2); \ + F##_new(t3); \ + F##_new(t4); \ + F##_new(t5); \ + \ + F##_mul(t0, p->x, q->x); \ + F##_mul(t1, p->y, q->y); \ + F##_add(t3, q->x, q->y); \ + F##_add(t4, p->x, p->y); \ + F##_mul(t3, t3, t4); \ + F##_add(t4, t0, t1); \ + F##_sub(t3, t3, t4); \ + \ + if (C##_curve_opt_a() == RLC_ZERO) { \ + /* Cost of 11M + 2m_3b + 13a. */ \ + if (p->coord == BASIC) { \ + /* Save 1M + 1m_3b if z1 = 1. */ \ + F##_add(t4, q->y, p->y); \ + F##_add(r->y, q->x, p->x); \ + F##_dbl(t5, C##_curve_get_b()); \ + F##_add(t5, t5, C##_curve_get_b()); \ + F##_add(r->z, t1, t5); \ + F##_sub(t1, t1, t5); \ + } else { \ + F##_mul(t4, q->y, p->z); \ + F##_add(t4, t4, p->y); \ + F##_mul(r->y, q->x, p->z); \ + F##_add(r->y, r->y, p->x); \ + C##_curve_mul_b(t2, p->z); \ + F##_dbl(t5, t2); \ + F##_add(t2, t2, t5); \ + F##_add(r->z, t1, t2); \ + F##_sub(t1, t1, t2); \ + } \ + F##_dbl(r->x, t0); \ + F##_add(t0, t0, r->x); \ + C##_curve_mul_b(r->y, r->y); \ + F##_dbl(t5, r->y); \ + F##_add(r->y, r->y, t5); \ + F##_mul(r->x, t4, r->y); \ + F##_mul(t2, t3, t1); \ + F##_sub(r->x, t2, r->x); \ + F##_mul(r->y, t0, r->y); \ + F##_mul(t1, t1, r->z); \ + F##_add(r->y, t1, r->y); \ + F##_mul(t0, t0, t3); \ + F##_mul(r->z, r->z, t4); \ + F##_add(r->z, r->z, t0); \ + } else if (C##_curve_opt_a() == RLC_MIN3) { \ + /* Cost of 11M + 2m_b + 23a. */ \ + if (p->coord == BASIC) { \ + /* Save 2M + 3a if z1 = 1. */ \ + F##_set_dig(t2, 3); \ + F##_add(t4, q->y, p->y); \ + F##_add(r->y, q->x, p->x); \ + F##_sub(r->x, r->y, C##_curve_get_b()); \ + } else { \ + F##_dbl(t2, p->z); \ + F##_add(t2, t2, p->z); \ + F##_mul(t4, q->y, p->z); \ + F##_add(t4, t4, p->y); \ + F##_mul(r->y, q->x, p->z); \ + F##_add(r->y, r->y, p->x); \ + C##_curve_mul_b(r->z, p->z); \ + F##_sub(r->x, r->y, r->z); \ + } \ + F##_dbl(r->z, r->x); \ + F##_add(r->x, r->x, r->z); \ + F##_sub(r->z, t1, r->x); \ + F##_add(r->x, t1, r->x); \ + C##_curve_mul_b(r->y, r->y); \ + F##_sub(r->y, r->y, t2); \ + F##_sub(r->y, r->y, t0); \ + F##_dbl(t1, r->y); \ + F##_add(r->y, t1, r->y); \ + F##_dbl(t1, t0); \ + F##_add(t0, t1, t0); \ + F##_sub(t0, t0, t2); \ + F##_mul(t1, t4, r->y); \ + F##_mul(t2, t0, r->y); \ + F##_mul(r->y, r->x, r->z); \ + F##_add(r->y, r->y, t2); \ + F##_mul(r->x, t3, r->x); \ + F##_sub(r->x, r->x, t1); \ + F##_mul(r->z, t4, r->z); \ + F##_mul(t1, t3, t0); \ + F##_add(r->z, r->z, t1); \ + } else { \ + /* Cost of 11M + 3m_a + 2m_3b + 17a. */ \ + if (p->coord == BASIC) { \ + /* Save 1M + 1m_a + 1m_3b if z1 = 1. */ \ + F##_copy(t2, C##_curve_get_a()); \ + F##_add(t4, q->x, p->x); \ + F##_add(t5, q->y, p->y); \ + F##_dbl(r->z, t4); \ + F##_add(r->z, r->z, t4); \ + C##_curve_mul_a(r->z, r->z); \ + C##_curve_mul_b(r->z, r->z); \ + } else { \ + C##_curve_mul_a(t2, p->z); \ + F##_mul(t4, q->x, p->z); \ + F##_add(t4, t4, p->x); \ + F##_mul(t5, q->y, p->z); \ + F##_add(t5, t5, p->y); \ + F##_dbl(r->x, p->z); \ + F##_add(r->x, r->x, p->z); \ + F##_dbl(r->y, r->x); \ + F##_add(r->x, r->x, r->y); \ + C##_curve_mul_b(r->x, r->x); \ + C##_curve_mul_a(r->z, t4); \ + F##_add(r->z, r->x, r->z); \ + } \ + F##_sub(r->x, t1, r->z); \ + F##_add(r->z, t1, r->z); \ + F##_mul(r->y, r->x, r->z); \ + F##_dbl(t1, t4); \ + F##_add(t1, t1, t4); \ + C##_curve_mul_b(t4, t1); \ + F##_dbl(t1, t0); \ + F##_add(t1, t1, t0); \ + F##_add(t1, t1, t2); \ + F##_sub(t2, t0, t2); \ + C##_curve_mul_a(t2, t2); \ + F##_add(t4, t4, t2); \ + F##_mul(t0, t1, t4); \ + F##_add(r->y, r->y, t0); \ + F##_mul(t0, t5, t4); \ + F##_mul(r->x, t3, r->x); \ + F##_sub(r->x, r->x, t0); \ + F##_mul(t0, t3, t1); \ + F##_mul(r->z, t5, r->z); \ + F##_add(r->z, r->z, t0); \ + } \ + \ + r->coord = PROJC; \ + } \ + RLC_CATCH_ANY { \ + RLC_THROW(ERR_CAUGHT); \ + } \ + RLC_FINALLY { \ + F##_free(t0); \ + F##_free(t1); \ + F##_free(t2); \ + F##_free(t3); \ + F##_free(t4); \ + F##_free(t5); \ + } \ + } \ + +/** + * Defines a template for point addition in homogeneous projective + * coordinates. + * + * Formulas for mixed addition from + * "Complete addition formulas for prime order elliptic curves" + * by Joost Renes, Craig Costello, and Lejla Batina + * https://eprint.iacr.org/2015/1060.pdf + * + * @param[in] C - the curve. + * @param[in] F - the field prefix. + */ +#if defined(EP_MIXED) && defined(STRIP) + +#define TMPL_ADD_PROJC_IMP(C, F) \ + static void C##_add_projc_imp(C##_t r, const C##_t p, const C##_t q) { \ + /* If code size is a problem, leave only the mixed version. */ \ + C##_add_projc_mix(r, p, q); \ + } \ + +#else + +#define TMPL_ADD_PROJC_IMP(C, F) \ + static void C##_add_projc_imp(C##_t r, const C##_t p, const C##_t q) { \ + F##_t t0, t1, t2, t3, t4, t5; \ + \ + if (q->coord == BASIC) { \ + C##_add_projc_mix(r, p, q); \ + return; \ + } \ + \ + F##_null(t0); \ + F##_null(t1); \ + F##_null(t2); \ + F##_null(t3); \ + F##_null(t4); \ + F##_null(t5); \ + \ + RLC_TRY { \ + F##_new(t0); \ + F##_new(t1); \ + F##_new(t2); \ + F##_new(t3); \ + F##_new(t4); \ + F##_new(t5); \ + \ + F##_mul(t0, p->x, q->x); \ + F##_mul(t1, p->y, q->y); \ + F##_mul(t2, p->z, q->z); \ + F##_add(t3, p->x, p->y); \ + F##_add(t4, q->x, q->y); \ + F##_mul(t3, t3, t4); \ + F##_add(t4, t0, t1); \ + F##_sub(t3, t3, t4); \ + if (C##_curve_opt_a() == RLC_ZERO) { \ + /* Cost of 12M + 2m_3b + 19a. */ \ + F##_add(t4, p->y, p->z); \ + F##_add(t5, q->y, q->z); \ + F##_mul(t4, t4, t5); \ + F##_add(t5, t1, t2); \ + F##_sub(t4, t4, t5); \ + F##_add(r->y, q->x, q->z); \ + F##_add(r->x, p->x, p->z); \ + F##_mul(r->x, r->x, r->y); \ + F##_add(r->y, t0, t2); \ + F##_sub(r->y, r->x, r->y); \ + F##_dbl(r->x, t0); \ + F##_add(t0, t0, r->x); \ + F##_dbl(t5, t2); \ + F##_add(t5, t5, t2); \ + C##_curve_mul_b(t2, t5); \ + F##_add(r->z, t1, t2); \ + F##_sub(t1, t1, t2); \ + F##_dbl(t5, r->y); \ + F##_add(t5, t5, r->y); \ + C##_curve_mul_b(r->y, t5); \ + F##_mul(r->x, t4, r->y); \ + F##_mul(t2, t3, t1); \ + F##_sub(r->x, t2, r->x); \ + F##_mul(r->y, t0, r->y); \ + F##_mul(t1, t1, r->z); \ + F##_add(r->y, t1, r->y); \ + F##_mul(t0, t0, t3); \ + F##_mul(r->z, r->z, t4); \ + F##_add(r->z, r->z, t0); \ + } else if (C##_curve_opt_a() == RLC_MIN3) { \ + /* Cost of 12M + 2m_b + 29a. */ \ + F##_add(t4, p->y, p->z); \ + F##_add(t5, q->y, q->z); \ + F##_mul(t4, t4, t5); \ + F##_add(t5, t1, t2); \ + F##_sub(t4, t4, t5); \ + F##_add(r->x, p->x, p->z); \ + F##_add(r->y, q->x, q->z); \ + F##_mul(r->x, r->x, r->y); \ + F##_add(r->y, t0, t2); \ + F##_sub(r->y, r->x, r->y); \ + C##_curve_mul_b(r->z, t2); \ + F##_sub(r->x, r->y, r->z); \ + F##_dbl(r->z, r->x); \ + F##_add(r->x, r->x, r->z); \ + F##_sub(r->z, t1, r->x); \ + F##_add(r->x, t1, r->x); \ + C##_curve_mul_b(r->y, r->y); \ + F##_dbl(t1, t2); \ + F##_add(t2, t1, t2); \ + F##_sub(r->y, r->y, t2); \ + F##_sub(r->y, r->y, t0); \ + F##_dbl(t1, r->y); \ + F##_add(r->y, t1, r->y); \ + F##_dbl(t1, t0); \ + F##_add(t0, t1, t0); \ + F##_sub(t0, t0, t2); \ + F##_mul(t1, t4, r->y); \ + F##_mul(t2, t0, r->y); \ + F##_mul(r->y, r->x, r->z); \ + F##_add(r->y, r->y, t2); \ + F##_mul(r->x, t3, r->x); \ + F##_sub(r->x, r->x, t1); \ + F##_mul(r->z, t4, r->z); \ + F##_mul(t1, t3, t0); \ + F##_add(r->z, r->z, t1); \ + } else { \ + /* Cost of 12M + 3m_a + 2_m3b + 23a. */ \ + F##_add(t4, p->x, p->z); \ + F##_add(t5, q->x, q->z); \ + F##_mul(t4, t4, t5); \ + F##_add(t5, t0, t2); \ + F##_sub(t4, t4, t5); \ + F##_add(t5, p->y, p->z); \ + F##_add(r->x, q->y, q->z); \ + F##_mul(t5, t5, r->x); \ + F##_add(r->x, t1, t2); \ + F##_sub(t5, t5, r->x); \ + C##_curve_mul_a(r->z, t4); \ + F##_dbl(r->x, t2); \ + F##_add(r->x, r->x, t2); \ + C##_curve_mul_b(r->x, r->x); \ + F##_add(r->z, r->x, r->z); \ + F##_sub(r->x, t1, r->z); \ + F##_add(r->z, t1, r->z); \ + F##_mul(r->y, r->x, r->z); \ + F##_dbl(t1, t4); \ + F##_add(t1, t1, t4); \ + C##_curve_mul_b(t4, t1); \ + F##_dbl(t1, t0); \ + F##_add(t1, t1, t0); \ + C##_curve_mul_a(t2, t2); \ + F##_add(t1, t1, t2); \ + F##_sub(t2, t0, t2); \ + C##_curve_mul_a(t2, t2); \ + F##_add(t4, t4, t2); \ + F##_mul(t0, t1, t4); \ + F##_add(r->y, r->y, t0); \ + F##_mul(t0, t5, t4); \ + F##_mul(r->x, t3, r->x); \ + F##_sub(r->x, r->x, t0); \ + F##_mul(t0, t3, t1); \ + F##_mul(r->z, t5, r->z); \ + F##_add(r->z, r->z, t0); \ + } \ + r->coord = PROJC; \ + } RLC_CATCH_ANY { \ + RLC_THROW(ERR_CAUGHT); \ + } RLC_FINALLY { \ + F##_free(t0); \ + F##_free(t1); \ + F##_free(t2); \ + F##_free(t3); \ + F##_free(t4); \ + F##_free(t5); \ + } \ + } \ + +#endif + +/** + * Defines a template for mixed point addition in Jacobian coordinates. + * + * madd-2007-bl formulas: 7M + 4S + 9add + 1*4 + 3*2. + * http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-madd-2007-bl + * + * @param[in] C - the curve. + * @param[in] F - the field prefix. + */ +#define TMPL_ADD_JACOB_MIX(C, F) \ + static void C##_add_jacob_mix(C##_t r, const C##_t p, const C##_t q) { \ + F##_t t0, t1, t2, t3, t4, t5, t6; \ + \ + F##_null(t0); \ + F##_null(t1); \ + F##_null(t2); \ + F##_null(t3); \ + F##_null(t4); \ + F##_null(t5); \ + F##_null(t6); \ + \ + RLC_TRY { \ + F##_new(t0); \ + F##_new(t1); \ + F##_new(t2); \ + F##_new(t3); \ + F##_new(t4); \ + F##_new(t5); \ + F##_new(t6); \ + \ + if (p->coord != BASIC) { \ + /* t0 = z1^2. */ \ + F##_sqr(t0, p->z); \ + \ + /* t3 = U2 = x2 * z1^2. */ \ + F##_mul(t3, q->x, t0); \ + \ + /* t1 = S2 = y2 * z1^3. */ \ + F##_mul(t1, t0, p->z); \ + F##_mul(t1, t1, q->y); \ + \ + /* t3 = H = U2 - x1. */ \ + F##_sub(t3, t3, p->x); \ + \ + /* t1 = R = 2 * (S2 - y1). */ \ + F##_sub(t1, t1, p->y); \ + F##_dbl(t1, t1); \ + } else { \ + /* H = x2 - x1. */ \ + F##_sub(t3, q->x, p->x); \ + \ + /* t1 = R = 2 * (y2 - y1). */ \ + F##_sub(t1, q->y, p->y); \ + F##_dbl(t1, t1); \ + } \ + \ + /* t2 = HH = H^2. */ \ + F##_sqr(t2, t3); \ + \ + /* If H is zero. */ \ + if (F##_is_zero(t3)) { \ + if (F##_is_zero(t1)) { \ + /* If I is zero, p = q, should have doubled. */ \ + C##_dbl_jacob(r, p); \ + } else { \ + /* If I is not zero, q = -p, r = infinity. */ \ + C##_set_infty(r); \ + } \ + } else { \ + /* t4 = I = 4*HH. */ \ + F##_dbl(t4, t2); \ + F##_dbl(t4, t4); \ + \ + /* t5 = J = H * I. */ \ + F##_mul(t5, t3, t4); \ + \ + /* t4 = V = x1 * I. */ \ + F##_mul(t4, p->x, t4); \ + \ + /* x3 = R^2 - J - 2 * V. */ \ + F##_sqr(r->x, t1); \ + F##_sub(r->x, r->x, t5); \ + F##_dbl(t6, t4); \ + F##_sub(r->x, r->x, t6); \ + \ + /* y3 = R * (V - x3) - 2 * Y1 * J. */ \ + F##_sub(t4, t4, r->x); \ + F##_mul(t4, t4, t1); \ + F##_mul(t1, p->y, t5); \ + F##_dbl(t1, t1); \ + F##_sub(r->y, t4, t1); \ + \ + if (p->coord != BASIC) { \ + /* z3 = (z1 + H)^2 - z1^2 - HH. */ \ + F##_add(r->z, p->z, t3); \ + F##_sqr(r->z, r->z); \ + F##_sub(r->z, r->z, t0); \ + F##_sub(r->z, r->z, t2); \ + } else { \ + /* z3 = 2 * H. */ \ + F##_dbl(r->z, t3); \ + } \ + } \ + r->coord = JACOB; \ + } RLC_CATCH_ANY { \ + RLC_THROW(ERR_CAUGHT); \ + } \ + RLC_FINALLY { \ + F##_free(t0); \ + F##_free(t1); \ + F##_free(t2); \ + F##_free(t3); \ + F##_free(t4); \ + F##_free(t5); \ + F##_free(t6); \ + } \ + } \ + +/** + * Defines a template for point addition in Jacobian coordinates. + * + * add-2007-bl formulas: 11M + 5S + 9add + 4*2 + * http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#addition-add-2007-bl + * + * @param[in] C - the curve. + * @param[in] F - the field prefix. + */ +#if defined(EP_MIXED) && defined(STRIP) + +#define TMPL_ADD_JACOB_IMP(C, F) \ + static void C##_add_jacob_imp(C##_t r, const C##_t p, const C##_t q) { \ + /* If code size is a problem, leave only the mixed version. */ \ + C##_add_jacob_mix(r, p, q); \ + } \ + +#else + +#define TMPL_ADD_JACOB_IMP(C, F) \ + static void C##_add_jacob_imp(C##_t r, const C##_t p, const C##_t q) { \ + F##_t t0, t1, t2, t3, t4, t5, t6; \ + \ + if (q->coord == BASIC) { \ + C##_add_jacob_mix(r, p, q); \ + return; \ + } \ + \ + F##_null(t0); \ + F##_null(t1); \ + F##_null(t2); \ + F##_null(t3); \ + F##_null(t4); \ + F##_null(t5); \ + F##_null(t6); \ + \ + RLC_TRY { \ + F##_new(t0); \ + F##_new(t1); \ + F##_new(t2); \ + F##_new(t3); \ + F##_new(t4); \ + F##_new(t5); \ + F##_new(t6); \ + \ + /* t0 = z1^2. */ \ + F##_sqr(t0, p->z); \ + \ + /* t1 = z2^2. */ \ + F##_sqr(t1, q->z); \ + \ + /* t2 = U1 = x1 * z2^2. */ \ + F##_mul(t2, p->x, t1); \ + \ + /* t3 = U2 = x2 * z1^2. */ \ + F##_mul(t3, q->x, t0); \ + \ + /* t6 = z1^2 + z2^2. */ \ + F##_add(t6, t0, t1); \ + \ + /* t0 = S2 = y2 * z1^3. */ \ + F##_mul(t0, t0, p->z); \ + F##_mul(t0, t0, q->y); \ + \ + /* t1 = S1 = y1 * z2^3. */ \ + F##_mul(t1, t1, q->z); \ + F##_mul(t1, t1, p->y); \ + \ + /* t3 = H = U2 - U1. */ \ + F##_sub(t3, t3, t2); \ + \ + /* t0 = R = 2 * (S2 - S1). */ \ + F##_sub(t0, t0, t1); \ + F##_dbl(t0, t0); \ + \ + /* If E is zero. */ \ + if (F##_is_zero(t3)) { \ + if (F##_is_zero(t0)) { \ + /* If I is zero, p = q, should have doubled. */ \ + C##_dbl_jacob(r, p); \ + } else { \ + /* If I is not zero, q = -p, r = infinity. */ \ + C##_set_infty(r); \ + } \ + } else { \ + /* t4 = I = (2*H)^2. */ \ + F##_dbl(t4, t3); \ + F##_sqr(t4, t4); \ + \ + /* t5 = J = H * I. */ \ + F##_mul(t5, t3, t4); \ + \ + /* t4 = V = U1 * I. */ \ + F##_mul(t4, t2, t4); \ + \ + /* x3 = R^2 - J - 2 * V. */ \ + F##_sqr(r->x, t0); \ + F##_sub(r->x, r->x, t5); \ + F##_dbl(t2, t4); \ + F##_sub(r->x, r->x, t2); \ + \ + /* y3 = R * (V - x3) - 2 * S1 * J. */ \ + F##_sub(t4, t4, r->x); \ + F##_mul(t4, t4, t0); \ + F##_mul(t1, t1, t5); \ + F##_dbl(t1, t1); \ + F##_sub(r->y, t4, t1); \ + \ + /* z3 = ((z1 + z2)^2 - z1^2 - z2^2) * H. */ \ + F##_add(r->z, p->z, q->z); \ + F##_sqr(r->z, r->z); \ + F##_sub(r->z, r->z, t6); \ + F##_mul(r->z, r->z, t3); \ + } \ + r->coord = JACOB; \ + } RLC_CATCH_ANY { \ + RLC_THROW(ERR_CAUGHT); \ + } RLC_FINALLY { \ + F##_free(t0); \ + F##_free(t1); \ + F##_free(t2); \ + F##_free(t3); \ + F##_free(t4); \ + F##_free(t5); \ + F##_free(t6); \ + } \ + } \ + + #endif \ No newline at end of file diff --git a/src/tmpl/relic_tmpl_map.h b/src/tmpl/relic_ep_map_tmpl.h similarity index 98% rename from src/tmpl/relic_tmpl_map.h rename to src/tmpl/relic_ep_map_tmpl.h index 2caaef1f7..d5d06b40e 100644 --- a/src/tmpl/relic_tmpl_map.h +++ b/src/tmpl/relic_ep_map_tmpl.h @@ -24,7 +24,7 @@ /** * @file * - * Templates for hashing to elliptic curves. + * Template for hashing to prime elliptic curves. * * @ingroup tmpl */ @@ -34,7 +34,7 @@ /*============================================================================*/ /** - * Evaluate a polynomial represented by its coefficients over a using Horner's + * Evaluates a polynomial represented by its coefficients over a using Horner's * rule. Might promove to an API if needed elsewhere in the future. */ #define TMPL_MAP_HORNER(PFX, IN) \ From 3db85f88cac5da9a4a7b0f26e7dd32dc0f9ecf8f Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Fri, 29 Mar 2024 08:24:09 +0100 Subject: [PATCH 09/37] Now refactor point doubling. --- include/relic_epx.h | 46 ++-- src/ep/relic_ep_curve.c | 3 + src/ep/relic_ep_dbl.c | 420 +--------------------------------- src/epx/relic_ep2_dbl.c | 62 +---- src/epx/relic_ep3_curve.c | 8 +- src/epx/relic_ep3_dbl.c | 69 +----- src/epx/relic_ep3_map.c | 8 +- src/epx/relic_ep3_util.c | 18 +- src/epx/relic_ep4_curve.c | 8 +- src/epx/relic_ep4_dbl.c | 69 +----- src/epx/relic_ep4_map.c | 18 +- src/epx/relic_ep4_util.c | 18 +- src/epx/relic_ep8_curve.c | 8 +- src/epx/relic_ep8_dbl.c | 69 +----- src/epx/relic_ep8_map.c | 8 +- src/epx/relic_ep8_util.c | 29 +-- src/pp/relic_pp_dbl_k18.c | 6 +- src/pp/relic_pp_dbl_k24.c | 3 +- src/pp/relic_pp_dbl_k48.c | 3 +- src/tmpl/relic_ep_add_tmpl.h | 2 +- src/tmpl/relic_ep_dbl_tmpl.h | 429 +++++++++++++++++++++++++++++++++++ 21 files changed, 534 insertions(+), 770 deletions(-) create mode 100644 src/tmpl/relic_ep_dbl_tmpl.h diff --git a/include/relic_epx.h b/include/relic_epx.h index 9cb652243..3d8a27573 100644 --- a/include/relic_epx.h +++ b/include/relic_epx.h @@ -33,7 +33,7 @@ * The scalar multiplication functions are only guaranteed to work * in the prime order subgroup used by pairings. If you need a generic scalar - * multiplication function, use \sa ep2_mul_big(). + * multiplication function, use epx_mul_big(). * * @ingroup epx */ @@ -459,6 +459,16 @@ typedef iso2_st *iso2_t; #define ep2_dbl(R, P) ep2_dbl_projc(R, P); #endif +/** + * Multiplies a point in an elliptic curve over a quadratic extension field by + * an unrestricted integer scalar. Computes R = [k]P. + * + * @param[out] R - the result. + * @param[in] P - the point to multiply. + * @param[in] K - the integer. + */ +#define ep2_mul_big(R, P, K) ep2_mul_basic(R, P, K) + /** * Multiplies a point in an elliptic curve over a quadratic extension field. * Computes R = [k]P. @@ -577,6 +587,16 @@ typedef iso2_st *iso2_t; #define ep3_dbl(R, P) ep3_dbl_projc(R, P); #endif +/** + * Multiplies a point in an elliptic curve over a cubic extension field by + * an unrestricted integer scalar. Computes R = [k]P. + * + * @param[out] R - the result. + * @param[in] P - the point to multiply. + * @param[in] K - the integer. + */ +#define ep3_mul_big(R, P, K) ep3_mul_basic(R, P, K) + /** * Multiplies a point in an elliptic curve over a cubic extension field. * Computes R = [k]P. @@ -653,7 +673,7 @@ typedef iso2_st *iso2_t; #endif /** - * Adds two points in an elliptic curve over a octic extension field. + * Adds two points in an elliptic curve over a quartic extension field. * Computes R = P + Q. * * @param[out] R - the result. @@ -667,7 +687,7 @@ typedef iso2_st *iso2_t; #endif /** - * Doubles a point in an elliptic curve over a octic extension field. + * Doubles a point in an elliptic curve over a quartic extension field. * Computes R = 2P. * * @param[out] R - the result. @@ -680,7 +700,7 @@ typedef iso2_st *iso2_t; #endif /** - * Multiplies a point in an elliptic curve over a octic extension field by + * Multiplies a point in an elliptic curve over a quartic extension field by * an unrestricted integer scalar. Computes R = [k]P. * * @param[out] R - the result. @@ -690,7 +710,7 @@ typedef iso2_st *iso2_t; #define ep4_mul_big(R, P, K) ep4_mul_basic(R, P, K) /** - * Multiplies a point in an elliptic curve over a octic extension field. + * Multiplies a point in an elliptic curve over a quartic extension field. * Computes R = [k]P. * * @param[out] R - the result. @@ -709,7 +729,7 @@ typedef iso2_st *iso2_t; /** * Builds a precomputation table for multiplying a fixed prime elliptic point - * over a octic extension. + * over a quartic extension. * * @param[out] T - the precomputation table. * @param[in] P - the point to multiply. @@ -726,7 +746,7 @@ typedef iso2_st *iso2_t; #endif /** - * Multiplies a fixed prime elliptic point over a octic extension using a + * Multiplies a fixed prime elliptic point over a quartic extension using a * precomputation table. Computes R = [k]P. * * @param[out] R - the result. @@ -1539,14 +1559,14 @@ void ep3_curve_clean(void); * * @return the 'a' coefficient of the elliptic curve. */ -void ep3_curve_get_a(fp3_t a); +fp_t *ep3_curve_get_a(void); /** * Returns the 'b' coefficient of the currently configured elliptic curve. * * @param[out] b - the 'b' coefficient of the elliptic curve. */ -void ep3_curve_get_b(fp3_t b); +fp_t *ep3_curve_get_b(void); /** * Returns a optimization identifier based on the 'a' coefficient of the curve. @@ -2148,14 +2168,14 @@ void ep4_curve_clean(void); * * @return the 'a' coefficient of the elliptic curve. */ -void ep4_curve_get_a(fp4_t a); +fp2_t *ep4_curve_get_a(void); /** * Returns the 'b' coefficient of the currently configured elliptic curve. * * @param[out] b - the 'b' coefficient of the elliptic curve. */ -void ep4_curve_get_b(fp4_t b); +fp2_t *ep4_curve_get_b(void); /** * Returns a optimization identifier based on the 'a' coefficient of the curve. @@ -2758,14 +2778,14 @@ void ep8_curve_clean(void); * * @return the 'a' coefficient of the elliptic curve. */ -void ep8_curve_get_a(fp8_t a); +fp4_t *ep8_curve_get_a(void); /** * Returns the 'b' coefficient of the currently configured elliptic curve. * * @param[out] b - the 'b' coefficient of the elliptic curve. */ -void ep8_curve_get_b(fp8_t b); +fp4_t *ep8_curve_get_b(void); /** * Returns a optimization identifier based on the 'a' coefficient of the curve. diff --git a/src/ep/relic_ep_curve.c b/src/ep/relic_ep_curve.c index aa7f4debb..6f55f5761 100644 --- a/src/ep/relic_ep_curve.c +++ b/src/ep/relic_ep_curve.c @@ -302,6 +302,9 @@ void ep_curve_mul_a(fp_t c, const fp_t a) { case RLC_ONE: fp_copy(c, a); break; + case RLC_TWO: + fp_dbl(c, a); + break; #if FP_RDC != MONTY case RLC_TINY: fp_mul_dig(c, a, ctx->ep_a[0]); diff --git a/src/ep/relic_ep_dbl.c b/src/ep/relic_ep_dbl.c index b1a6ecafc..f8548b6be 100644 --- a/src/ep/relic_ep_dbl.c +++ b/src/ep/relic_ep_dbl.c @@ -30,6 +30,7 @@ */ #include "relic_core.h" +#include "relic_ep_dbl_tmpl.h" /*============================================================================*/ /* Private definitions */ @@ -45,77 +46,7 @@ * @param[out] s - the slope. * @param[in] p - the point to double. */ -static void ep_dbl_basic_imp(ep_t r, fp_t s, const ep_t p) { - fp_t t0, t1, t2; - - fp_null(t0); - fp_null(t1); - fp_null(t2); - - RLC_TRY { - fp_new(t0); - fp_new(t1); - fp_new(t2); - - /* t0 = 1/2 * y1. */ - fp_dbl(t0, p->y); - fp_inv(t0, t0); - - /* t1 = 3 * x1^2 + a. */ - fp_sqr(t1, p->x); - fp_copy(t2, t1); - fp_dbl(t1, t1); - fp_add(t1, t1, t2); - - switch (ep_curve_opt_a()) { - case RLC_ZERO: - break; - case RLC_ONE: - fp_add_dig(t1, t1, (dig_t)1); - break; -#if FP_RDC != MONTY - case RLC_TINY: - fp_add_dig(t1, t1, ep_curve_get_a()[0]); - break; -#endif - default: - fp_add(t1, t1, ep_curve_get_a()); - break; - } - - /* t1 = (3 * x1^2 + a)/(2 * y1). */ - fp_mul(t1, t1, t0); - - if (s != NULL) { - fp_copy(s, t1); - } - - /* t2 = t1^2. */ - fp_sqr(t2, t1); - - /* x3 = t1^2 - 2 * x1. */ - fp_dbl(t0, p->x); - fp_sub(t0, t2, t0); - - /* y3 = t1 * (x1 - x3) - y1. */ - fp_sub(t2, p->x, t0); - fp_mul(t1, t1, t2); - fp_sub(r->y, t1, p->y); - - fp_copy(r->x, t0); - fp_copy(r->z, p->z); - - r->coord = BASIC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp_free(t0); - fp_free(t1); - fp_free(t2); - } -} +TMPL_DBL_BASIC_IMP(ep, fp); #endif /* EP_ADD == BASIC */ @@ -128,152 +59,7 @@ static void ep_dbl_basic_imp(ep_t r, fp_t s, const ep_t p) { * @param r - the result. * @param p - the point to double. */ -static void ep_dbl_projc_imp(ep_t r, const ep_t p) { - fp_t t0, t1, t2, t3, t4, t5; - - fp_null(t0); - fp_null(t1); - fp_null(t2); - fp_null(t3); - fp_null(t4); - fp_null(t5); - - RLC_TRY { - fp_new(t0); - fp_new(t1); - fp_new(t2); - fp_new(t3); - fp_new(t4); - fp_new(t5); - - /* Formulas for point doubling from - * "Complete addition formulas for prime order elliptic curves" - * by Joost Renes, Craig Costello, and Lejla Batina - * https://eprint.iacr.org/2015/1060.pdf - */ - if (ep_curve_opt_a() == RLC_ZERO) { - /* Cost of 6M + 2S + 1m_3b + 9a. */ - fp_sqr(t0, p->y); - fp_mul(t3, p->x, p->y); - - if (p->coord == BASIC) { - /* Save 1M + 1S + 1m_b3 if z1 = 1. */ - fp_copy(t1, p->y); - fp_dbl(t2, ep_curve_get_b()); - fp_add(t2, t2, ep_curve_get_b()); - } else { - fp_mul(t1, p->y, p->z); - fp_sqr(t2, p->z); - fp_dbl(t5, t2); - fp_add(t5, t5, t2); - ep_curve_mul_b(t2, t5); - } - fp_dbl(r->z, t0); - fp_dbl(r->z, r->z); - fp_dbl(r->z, r->z); - fp_mul(r->x, t2, r->z); - fp_add(r->y, t0, t2); - fp_mul(r->z, t1, r->z); - fp_dbl(t1, t2); - fp_add(t2, t1, t2); - fp_sub(t0, t0, t2); - fp_mul(r->y, t0, r->y); - fp_add(r->y, r->x, r->y); - fp_mul(r->x, t0, t3); - fp_dbl(r->x, r->x); - } else { - fp_sqr(t0, p->x); - fp_sqr(t1, p->y); - fp_mul(t3, p->x, p->y); - fp_dbl(t3, t3); - fp_mul(t4, p->y, p->z); - - if (ep_curve_opt_a() == RLC_MIN3) { - /* Cost of 8M + 3S + 2mb + 21a. */ - if (p->coord == BASIC) { - /* Save 1S + 1m_b + 2a if z1 = 1. */ - fp_set_dig(t2, 3); - fp_copy(r->y, ep_curve_get_b()); - } else { - fp_sqr(t2, p->z); - ep_curve_mul_b(r->y, t2); - fp_dbl(t5, t2); - fp_add(t2, t2, t5); - } - fp_mul(r->z, p->x, p->z); - fp_dbl(r->z, r->z); - fp_sub(r->y, r->y, r->z); - fp_dbl(r->x, r->y); - fp_add(r->y, r->x, r->y); - fp_sub(r->x, t1, r->y); - fp_add(r->y, t1, r->y); - fp_mul(r->y, r->x, r->y); - fp_mul(r->x, t3, r->x); - ep_curve_mul_b(r->z, r->z); - fp_sub(t3, r->z, t2); - fp_sub(t3, t3, t0); - fp_dbl(r->z, t3); - fp_add(t3, t3, r->z); - fp_dbl(r->z, t0); - fp_add(t0, t0, r->z); - fp_sub(t0, t0, t2); - } else { - /* Common cost of 8M + 3S + 3m_a + 2m_3b + 15a. */ - if (p->coord == BASIC) { - /* Save 1S + 1m_b + 1m_a if z1 = 1. */ - fp_dbl(r->y, ep_curve_get_b()); - fp_add(r->y, r->y, ep_curve_get_b()); - fp_copy(t2, ep_curve_get_a()); - } else { - fp_sqr(t2, p->z); - fp_dbl(t5, t2); - fp_add(t5, t5, t2); - ep_curve_mul_b(r->y, t5); - ep_curve_mul_a(t2, t2); - } - fp_mul(r->z, p->x, p->z); - fp_dbl(r->z, r->z); - ep_curve_mul_a(r->x, r->z); - fp_add(r->y, r->x, r->y); - fp_sub(r->x, t1, r->y); - fp_add(r->y, t1, r->y); - fp_mul(r->y, r->x, r->y); - fp_mul(r->x, t3, r->x); - fp_dbl(t5, r->z); - fp_add(t5, t5, r->z); - ep_curve_mul_b(r->z, t5); - fp_sub(t3, t0, t2); - ep_curve_mul_a(t3, t3); - fp_add(t3, t3, r->z); - fp_dbl(r->z, t0); - fp_add(t0, t0, r->z); - fp_add(t0, t0, t2); - } - /* Common part with renamed variables. */ - fp_mul(t0, t0, t3); - fp_add(r->y, r->y, t0); - fp_dbl(t2, t4); - fp_mul(t0, t2, t3); - fp_sub(r->x, r->x, t0); - fp_mul(r->z, t2, t1); - fp_dbl(r->z, r->z); - fp_dbl(r->z, r->z); - } - - r->coord = PROJC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp_free(t0); - fp_free(t1); - fp_free(t2); - fp_free(t3); - fp_free(t4); - fp_free(t5); - } -} +TMPL_DBL_PROJC_IMP(ep, fp); #endif /* EP_ADD == PROJC */ @@ -286,205 +72,7 @@ static void ep_dbl_projc_imp(ep_t r, const ep_t p) { * @param r - the result. * @param p - the point to double. */ -static void ep_dbl_jacob_imp(ep_t r, const ep_t p) { - fp_t t0, t1, t2, t3, t4, t5; - - fp_null(t1); - fp_null(t2); - fp_null(t3); - fp_null(t4); - fp_null(t5); - - RLC_TRY { - fp_new(t0); - fp_new(t1); - fp_new(t2); - fp_new(t3); - fp_new(t4); - fp_new(t5); - - if (p->coord != BASIC && ep_curve_opt_a() == RLC_MIN3) { - /* dbl-2001-b formulas: 3M + 5S + 8add + 1*4 + 2*8 + 1*3 */ - /* http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html#doubling-dbl-2001-b */ - - /* t0 = delta = z1^2. */ - fp_sqr(t0, p->z); - - /* t1 = gamma = y1^2. */ - fp_sqr(t1, p->y); - - /* t2 = beta = x1 * y1^2. */ - fp_mul(t2, p->x, t1); - - /* t3 = alpha = 3 * (x1 - z1^2) * (x1 + z1^2). */ - fp_sub(t3, p->x, t0); - fp_add(t4, p->x, t0); - fp_mul(t4, t3, t4); - fp_dbl(t3, t4); - fp_add(t3, t3, t4); - - /* x3 = alpha^2 - 8 * beta. */ - fp_dbl(t2, t2); - fp_dbl(t2, t2); - fp_dbl(t5, t2); - fp_sqr(r->x, t3); - fp_sub(r->x, r->x, t5); - - /* z3 = (y1 + z1)^2 - gamma - delta. */ - fp_add(r->z, p->y, p->z); - fp_sqr(r->z, r->z); - fp_sub(r->z, r->z, t1); - fp_sub(r->z, r->z, t0); - - /* y3 = alpha * (4 * beta - x3) - 8 * gamma^2. */ - fp_dbl(t1, t1); - fp_sqr(t1, t1); - fp_dbl(t1, t1); - fp_sub(r->y, t2, r->x); - fp_mul(r->y, r->y, t3); - fp_sub(r->y, r->y, t1); - } else if (ep_curve_opt_a() == RLC_ZERO) { - /* dbl-2009-l formulas: 2M + 5S + 6add + 1*8 + 3*2 + 1*3. */ - /* http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l */ - - /* A = X1^2 */ - fp_sqr(t0, p->x); - - /* B = Y1^2 */ - fp_sqr(t1, p->y); - - /* C = B^2 */ - fp_sqr(t2, t1); - - /* D = 2*((X1+B)^2-A-C) */ - fp_add(t1, t1, p->x); - fp_sqr(t1, t1); - fp_sub(t1, t1, t0); - fp_sub(t1, t1, t2); - fp_dbl(t1, t1); - - /* E = 3*A */ - fp_dbl(t3, t0); - fp_add(t0, t3, t0); - - /* F = E^2 */ - fp_sqr(t3, t0); - - /* Z3 = 2*Y1*Z1 */ - fp_mul(r->z, p->y, p->z); - fp_dbl(r->z, r->z); - - /* X3 = F-2*D */ - fp_sub(r->x, t3, t1); - fp_sub(r->x, r->x, t1); - - /* Y3 = E*(D-X3)-8*C */ - fp_sub(r->y, t1, r->x); - fp_mul(r->y, r->y, t0); - fp_dbl(t2, t2); - fp_dbl(t2, t2); - fp_dbl(t2, t2); - fp_sub(r->y, r->y, t2); - } else { - /* dbl-2007-bl formulas: 1M + 8S + 1*a + 10add + 1*8 + 2*2 + 1*3 */ - /* http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-2007-bl */ - - /* t0 = x1^2, t1 = y1^2, t2 = y1^4. */ - fp_sqr(t0, p->x); - fp_sqr(t1, p->y); - fp_sqr(t2, t1); - - if (p->coord != BASIC) { - /* t3 = z1^2. */ - fp_sqr(t3, p->z); - - if (ep_curve_opt_a() == RLC_ZERO) { - /* z3 = 2 * y1 * z1. */ - fp_mul(r->z, p->y, p->z); - fp_dbl(r->z, r->z); - } else { - /* z3 = (y1 + z1)^2 - y1^2 - z1^2. */ - fp_add(r->z, p->y, p->z); - fp_sqr(r->z, r->z); - fp_sub(r->z, r->z, t1); - fp_sub(r->z, r->z, t3); - } - } else { - /* z3 = 2 * y1. */ - fp_dbl(r->z, p->y); - } - - /* t4 = S = 2*((x1 + y1^2)^2 - x1^2 - y1^4). */ - fp_add(t4, p->x, t1); - fp_sqr(t4, t4); - fp_sub(t4, t4, t0); - fp_sub(t4, t4, t2); - fp_dbl(t4, t4); - - /* t5 = M = 3 * x1^2 + a * z1^4. */ - fp_dbl(t5, t0); - fp_add(t5, t5, t0); - if (p->coord != BASIC) { - fp_sqr(t3, t3); - switch (ep_curve_opt_a()) { - case RLC_ZERO: - break; - case RLC_ONE: - fp_add(t5, t5, t3); - break; - case RLC_TINY: - fp_mul_dig(t1, t3, ep_curve_get_a()[0]); - fp_add(t5, t5, t1); - break; - default: - fp_mul(t1, t3, ep_curve_get_a()); - fp_add(t5, t5, t1); - break; - } - } else { - switch (ep_curve_opt_a()) { - case RLC_ZERO: - break; - case RLC_ONE: - fp_add_dig(t5, t5, (dig_t)1); - break; - case RLC_TINY: - fp_add_dig(t5, t5, ep_curve_get_a()[0]); - break; - default: - fp_add(t5, t5, ep_curve_get_a()); - break; - } - } - - /* x3 = T = M^2 - 2 * S. */ - fp_sqr(r->x, t5); - fp_dbl(t1, t4); - fp_sub(r->x, r->x, t1); - - /* y3 = M * (S - T) - 8 * y1^4. */ - fp_dbl(t2, t2); - fp_dbl(t2, t2); - fp_dbl(t2, t2); - fp_sub(t4, t4, r->x); - fp_mul(t5, t5, t4); - fp_sub(r->y, t5, t2); - } - - r->coord = JACOB; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp_free(t0); - fp_free(t1); - fp_free(t2); - fp_free(t3); - fp_free(t4); - fp_free(t5); - } -} +TMPL_DBL_JACOB_IMP(ep, fp); #endif /* EP_ADD == JACOB */ diff --git a/src/epx/relic_ep2_dbl.c b/src/epx/relic_ep2_dbl.c index a1abb3d1b..0d2900d9d 100644 --- a/src/epx/relic_ep2_dbl.c +++ b/src/epx/relic_ep2_dbl.c @@ -31,6 +31,7 @@ */ #include "relic_core.h" +#include "relic_ep_dbl_tmpl.h" /*============================================================================*/ /* Private definitions */ @@ -46,64 +47,7 @@ * @param[out] s - the resulting slope. * @param[in] p - the point to double. */ -static void ep2_dbl_basic_imp(ep2_t r, fp2_t s, const ep2_t p) { - fp2_t t0, t1, t2; - - fp2_null(t0); - fp2_null(t1); - fp2_null(t2); - - RLC_TRY { - fp2_new(t0); - fp2_new(t1); - fp2_new(t2); - - /* t0 = 1/(2 * y1). */ - fp2_dbl(t0, p->y); - fp2_inv(t0, t0); - - /* t1 = 3 * x1^2 + a. */ - fp2_sqr(t1, p->x); - fp2_copy(t2, t1); - fp2_dbl(t1, t1); - fp2_add(t1, t1, t2); - - fp2_add(t1, t1, ep2_curve_get_a()); - - /* t1 = (3 * x1^2 + a)/(2 * y1). */ - fp2_mul(t1, t1, t0); - - if (s != NULL) { - fp2_copy(s, t1); - } - - /* t2 = t1^2. */ - fp2_sqr(t2, t1); - - /* x3 = t1^2 - 2 * x1. */ - fp2_dbl(t0, p->x); - fp2_sub(t0, t2, t0); - - /* y3 = t1 * (x1 - x3) - y1. */ - fp2_sub(t2, p->x, t0); - fp2_mul(t1, t1, t2); - - fp2_sub(r->y, t1, p->y); - - fp2_copy(r->x, t0); - fp2_copy(r->z, p->z); - - r->coord = BASIC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp2_free(t0); - fp2_free(t1); - fp2_free(t2); - } -} +TMPL_DBL_BASIC_IMP(ep2, fp2); #endif /* EP_ADD == BASIC */ @@ -168,7 +112,7 @@ static void ep2_dbl_projc_imp(ep2_t r, const ep2_t p) { /* t3 = z1^2. */ fp2_sqr(t3, p->z); - if (ep_curve_get_a() == RLC_ZERO) { + if (ep_curve_opt_a() == RLC_ZERO) { /* z3 = 2 * y1 * z1. */ fp2_mul(r->z, p->y, p->z); fp2_dbl(r->z, r->z); diff --git a/src/epx/relic_ep3_curve.c b/src/epx/relic_ep3_curve.c index 8bb97eff3..2d1788530 100644 --- a/src/epx/relic_ep3_curve.c +++ b/src/epx/relic_ep3_curve.c @@ -249,12 +249,12 @@ void ep3_curve_get_gen(ep3_t g) { ep3_copy(g, core_get()->ep3_g); } -void ep3_curve_get_a(fp3_t a) { - fp3_copy(a, core_get()->ep3_a); +fp_t *ep3_curve_get_a(void) { + return core_get()->ep3_a; } -void ep3_curve_get_b(fp3_t b) { - fp3_copy(b, core_get()->ep3_b); +fp_t *ep3_curve_get_b(void) { + return core_get()->ep3_b; } void ep3_curve_get_ord(bn_t n) { diff --git a/src/epx/relic_ep3_dbl.c b/src/epx/relic_ep3_dbl.c index 27b09b10c..28d535dc6 100644 --- a/src/epx/relic_ep3_dbl.c +++ b/src/epx/relic_ep3_dbl.c @@ -31,6 +31,7 @@ */ #include "relic_core.h" +#include "relic_ep_dbl_tmpl.h" /*============================================================================*/ /* Private definitions */ @@ -46,65 +47,7 @@ * @param[out] s - the resulting slope. * @param[in] p - the point to double. */ -static void ep3_dbl_basic_imp(ep3_t r, fp3_t s, const ep3_t p) { - fp3_t t0, t1, t2; - - fp3_null(t0); - fp3_null(t1); - fp3_null(t2); - - RLC_TRY { - fp3_new(t0); - fp3_new(t1); - fp3_new(t2); - - /* t0 = 1/(2 * y1). */ - fp3_dbl(t0, p->y); - fp3_inv(t0, t0); - - /* t1 = 3 * x1^2 + a. */ - fp3_sqr(t1, p->x); - fp3_copy(t2, t1); - fp3_dbl(t1, t1); - fp3_add(t1, t1, t2); - - ep3_curve_get_a(t2); - fp3_add(t1, t1, t2); - - /* t1 = (3 * x1^2 + a)/(2 * y1). */ - fp3_mul(t1, t1, t0); - - if (s != NULL) { - fp3_copy(s, t1); - } - - /* t2 = t1^2. */ - fp3_sqr(t2, t1); - - /* x3 = t1^2 - 2 * x1. */ - fp3_dbl(t0, p->x); - fp3_sub(t0, t2, t0); - - /* y3 = t1 * (x1 - x3) - y1. */ - fp3_sub(t2, p->x, t0); - fp3_mul(t1, t1, t2); - - fp3_sub(r->y, t1, p->y); - - fp3_copy(r->x, t0); - fp3_copy(r->z, p->z); - - r->coord = BASIC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp3_free(t0); - fp3_free(t1); - fp3_free(t2); - } -} +TMPL_DBL_BASIC_IMP(ep3, fp3); #endif /* EP_ADD == BASIC */ @@ -169,7 +112,7 @@ static void ep3_dbl_projc_imp(ep3_t r, const ep3_t p) { /* t3 = z1^2. */ fp3_sqr(t3, p->z); - if (ep_curve_get_a() == RLC_ZERO) { + if (ep_curve_opt_a() == RLC_ZERO) { /* z3 = 2 * y1 * z1. */ fp3_mul(r->z, p->y, p->z); fp3_dbl(r->z, r->z); @@ -197,12 +140,10 @@ static void ep3_dbl_projc_imp(ep3_t r, const ep3_t p) { fp3_add(t5, t5, t0); if (p->coord != BASIC) { fp3_sqr(t3, t3); - ep3_curve_get_a(t1); - fp3_mul(t1, t3, t1); + fp3_mul(t1, t3, ep3_curve_get_a()); fp3_add(t5, t5, t1); } else { - ep3_curve_get_a(t1); - fp3_add(t5, t5, t1); + fp3_add(t5, t5, ep3_curve_get_a()); } /* x3 = T = M^2 - 2 * S. */ diff --git a/src/epx/relic_ep3_map.c b/src/epx/relic_ep3_map.c index 8550ea179..757a5ad0b 100644 --- a/src/epx/relic_ep3_map.c +++ b/src/epx/relic_ep3_map.c @@ -115,19 +115,17 @@ void ep3_map(ep3_t p, const uint8_t *msg, size_t len) { fp3_sqr(z1, z1); fp3_add(z1, z1, u); - ep3_curve_get_b(w); - fp3_sqr(t, x1); fp3_mul(t, t, x1); - fp3_add(t, t, w); + fp3_add(t, t, ep3_curve_get_b()); fp3_sqr(u, y1); fp3_mul(u, u, y1); - fp3_add(u, u, w); + fp3_add(u, u, ep3_curve_get_b()); fp3_sqr(v, z1); fp3_mul(v, v, z1); - fp3_add(v, v, w); + fp3_add(v, v, ep3_curve_get_b()); c2 = fp3_is_sqr(u); c3 = fp3_is_sqr(v); diff --git a/src/epx/relic_ep3_util.c b/src/epx/relic_ep3_util.c index ec7129aeb..e75de6b2e 100644 --- a/src/epx/relic_ep3_util.c +++ b/src/epx/relic_ep3_util.c @@ -105,14 +105,12 @@ void ep3_blind(ep3_t r, const ep3_t p) { } void ep3_rhs(fp3_t rhs, const ep3_t p) { - fp3_t t0, t1; + fp3_t t0; fp3_null(t0); - fp3_null(t1); RLC_TRY { fp3_new(t0); - fp3_new(t1); fp3_sqr(t0, p->x); /* x1^2 */ @@ -130,13 +128,11 @@ void ep3_rhs(fp3_t rhs, const ep3_t p) { fp_add_dig(t0[0], t0[0], 2); break; case RLC_TINY: - ep3_curve_get_a(t1); - fp3_mul_dig(t0, t0, t1[0][0]); + fp3_mul_dig(t0, t0, ep3_curve_get_a()[0][0]); break; #endif default: - ep3_curve_get_a(t1); - fp3_add(t0, t0, t1); + fp3_add(t0, t0, ep3_curve_get_a()); break; } @@ -156,13 +152,11 @@ void ep3_rhs(fp3_t rhs, const ep3_t p) { fp3_add_dig(t0, t0, 2); break; case RLC_TINY: - ep3_curve_get_b(t1); - fp3_mul_dig(t0, t0, t1[0][0]); + fp3_mul_dig(t0, t0, ep3_curve_get_b()[0][0]); break; #endif default: - ep3_curve_get_b(t1); - fp3_add(t0, t0, t1); + fp3_add(t0, t0, ep3_curve_get_b()); break; } @@ -171,11 +165,9 @@ void ep3_rhs(fp3_t rhs, const ep3_t p) { RLC_THROW(ERR_CAUGHT); } RLC_FINALLY { fp3_free(t0); - fp3_free(t1); } } - int ep3_on_curve(const ep3_t p) { ep3_t t; int r = 0; diff --git a/src/epx/relic_ep4_curve.c b/src/epx/relic_ep4_curve.c index 140a6dad6..df4488451 100644 --- a/src/epx/relic_ep4_curve.c +++ b/src/epx/relic_ep4_curve.c @@ -343,12 +343,12 @@ void ep4_curve_get_gen(ep4_t g) { ep4_copy(g, core_get()->ep4_g); } -void ep4_curve_get_a(fp4_t a) { - fp4_copy(a, core_get()->ep4_a); +fp2_t *ep4_curve_get_a(void) { + return core_get()->ep4_a; } -void ep4_curve_get_b(fp4_t b) { - fp4_copy(b, core_get()->ep4_b); +fp2_t *ep4_curve_get_b(void) { + return core_get()->ep4_b; } void ep4_curve_get_ord(bn_t n) { diff --git a/src/epx/relic_ep4_dbl.c b/src/epx/relic_ep4_dbl.c index 6aa82b9e9..abe004128 100644 --- a/src/epx/relic_ep4_dbl.c +++ b/src/epx/relic_ep4_dbl.c @@ -31,6 +31,7 @@ */ #include "relic_core.h" +#include "relic_ep_dbl_tmpl.h" /*============================================================================*/ /* Private definitions */ @@ -46,65 +47,7 @@ * @param[out] s - the resulting slope. * @param[in] p - the point to double. */ -static void ep4_dbl_basic_imp(ep4_t r, fp4_t s, const ep4_t p) { - fp4_t t0, t1, t2; - - fp4_null(t0); - fp4_null(t1); - fp4_null(t2); - - RLC_TRY { - fp4_new(t0); - fp4_new(t1); - fp4_new(t2); - - /* t0 = 1/(2 * y1). */ - fp4_dbl(t0, p->y); - fp4_inv(t0, t0); - - /* t1 = 3 * x1^2 + a. */ - fp4_sqr(t1, p->x); - fp4_copy(t2, t1); - fp4_dbl(t1, t1); - fp4_add(t1, t1, t2); - - ep4_curve_get_a(t2); - fp4_add(t1, t1, t2); - - /* t1 = (3 * x1^2 + a)/(2 * y1). */ - fp4_mul(t1, t1, t0); - - if (s != NULL) { - fp4_copy(s, t1); - } - - /* t2 = t1^2. */ - fp4_sqr(t2, t1); - - /* x3 = t1^2 - 2 * x1. */ - fp4_dbl(t0, p->x); - fp4_sub(t0, t2, t0); - - /* y3 = t1 * (x1 - x3) - y1. */ - fp4_sub(t2, p->x, t0); - fp4_mul(t1, t1, t2); - - fp4_sub(r->y, t1, p->y); - - fp4_copy(r->x, t0); - fp4_copy(r->z, p->z); - - r->coord = BASIC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp4_free(t0); - fp4_free(t1); - fp4_free(t2); - } -} +TMPL_DBL_BASIC_IMP(ep4, fp4); #endif /* EP_ADD == BASIC */ @@ -169,7 +112,7 @@ static void ep4_dbl_projc_imp(ep4_t r, const ep4_t p) { /* t3 = z1^2. */ fp4_sqr(t3, p->z); - if (ep_curve_get_a() == RLC_ZERO) { + if (ep_curve_opt_a() == RLC_ZERO) { /* z3 = 2 * y1 * z1. */ fp4_mul(r->z, p->y, p->z); fp4_dbl(r->z, r->z); @@ -197,12 +140,10 @@ static void ep4_dbl_projc_imp(ep4_t r, const ep4_t p) { fp4_add(t5, t5, t0); if (p->coord != BASIC) { fp4_sqr(t3, t3); - ep4_curve_get_a(t1); - fp4_mul(t1, t3, t1); + fp4_mul(t1, t3, ep4_curve_get_a()); fp4_add(t5, t5, t1); } else { - ep4_curve_get_a(t1); - fp4_add(t5, t5, t1); + fp4_add(t5, t5, ep4_curve_get_a()); } /* x3 = T = M^2 - 2 * S. */ diff --git a/src/epx/relic_ep4_map.c b/src/epx/relic_ep4_map.c index c43083a42..2b11045c5 100644 --- a/src/epx/relic_ep4_map.c +++ b/src/epx/relic_ep4_map.c @@ -95,11 +95,10 @@ void ep4_map(ep4_t p, const uint8_t *msg, size_t len) { h -= 8*elm; if (ep_curve_opt_b() == RLC_ZERO) { - ep4_curve_get_a(p->y); fp4_sqr(a, u); fp4_sqr(b, a); fp4_mul(c, b, a); - fp4_dbl(p->y, p->y); + fp4_dbl(p->y, ep4_curve_get_a()); fp4_dbl(p->y, p->y); fp4_sqr(p->z, p->y); fp4_mul(p->z, p->z, p->y); @@ -178,15 +177,14 @@ void ep4_map(ep4_t p, const uint8_t *msg, size_t len) { fp4_mul(y1, y1, den[1]); fp4_mul(z1, z1, den[2]); - ep4_curve_get_a(p->y); fp4_sqr(t, x1); - fp4_add(t, t, p->y); + fp4_add(t, t, ep4_curve_get_a()); fp4_mul(t, t, x1); fp4_sqr(u, y1); - fp4_add(u, u, p->y); + fp4_add(u, u, ep4_curve_get_a()); fp4_mul(u, u, y1); fp4_sqr(v, z1); - fp4_add(v, v, p->y); + fp4_add(v, v, ep4_curve_get_a()); fp4_mul(v, v, z1); int c2 = fp4_is_sqr(u); @@ -261,19 +259,17 @@ void ep4_map(ep4_t p, const uint8_t *msg, size_t len) { fp4_sqr(z1, z1); fp4_add(z1, z1, u); - ep4_curve_get_b(w); - fp4_sqr(t, x1); fp4_mul(t, t, x1); - fp4_add(t, t, w); + fp4_add(t, t, ep4_curve_get_b()); fp4_sqr(u, y1); fp4_mul(u, u, y1); - fp4_add(u, u, w); + fp4_add(u, u, ep4_curve_get_b()); fp4_sqr(v, z1); fp4_mul(v, v, z1); - fp4_add(v, v, w); + fp4_add(v, v, ep4_curve_get_b()); dig_t c2 = fp4_is_sqr(u); dig_t c3 = fp4_is_sqr(v); diff --git a/src/epx/relic_ep4_util.c b/src/epx/relic_ep4_util.c index c41642956..9bbd4f4b9 100644 --- a/src/epx/relic_ep4_util.c +++ b/src/epx/relic_ep4_util.c @@ -105,14 +105,12 @@ void ep4_blind(ep4_t r, const ep4_t p) { } void ep4_rhs(fp4_t rhs, const ep4_t p) { - fp4_t t0, t1; + fp4_t t0; fp4_null(t0); - fp4_null(t1); RLC_TRY { fp4_new(t0); - fp4_new(t1); fp4_sqr(t0, p->x); /* x1^2 */ @@ -130,13 +128,11 @@ void ep4_rhs(fp4_t rhs, const ep4_t p) { fp_add_dig(t0[0][0], t0[0][0], 2); break; case RLC_TINY: - ep4_curve_get_a(t1); - fp4_mul_dig(t0, t0, t1[0][0][0]); + fp4_mul_dig(t0, t0, ep4_curve_get_a()[0][0][0]); break; #endif default: - ep4_curve_get_a(t1); - fp4_add(t0, t0, t1); + fp4_add(t0, t0, ep4_curve_get_a()); break; } @@ -156,13 +152,11 @@ void ep4_rhs(fp4_t rhs, const ep4_t p) { fp_add_dig(t0[0][0], t0[0][0], 2); break; case RLC_TINY: - ep4_curve_get_b(t1); - fp4_mul_dig(t0, t0, t1[0][0][0]); + fp4_mul_dig(t0, t0, ep4_curve_get_b()[0][0][0]); break; #endif default: - ep4_curve_get_b(t1); - fp4_add(t0, t0, t1); + fp4_add(t0, t0, ep4_curve_get_b()); break; } @@ -171,11 +165,9 @@ void ep4_rhs(fp4_t rhs, const ep4_t p) { RLC_THROW(ERR_CAUGHT); } RLC_FINALLY { fp4_free(t0); - fp4_free(t1); } } - int ep4_on_curve(const ep4_t p) { ep4_t t; int r = 0; diff --git a/src/epx/relic_ep8_curve.c b/src/epx/relic_ep8_curve.c index 90d130d07..c7c1dcda5 100644 --- a/src/epx/relic_ep8_curve.c +++ b/src/epx/relic_ep8_curve.c @@ -229,12 +229,12 @@ void ep8_curve_get_gen(ep8_t g) { ep8_copy(g, core_get()->ep8_g); } -void ep8_curve_get_a(fp8_t a) { - fp8_copy(a, core_get()->ep8_a); +fp4_t *ep8_curve_get_a(void) { + return core_get()->ep8_a; } -void ep8_curve_get_b(fp8_t b) { - fp8_copy(b, core_get()->ep8_b); +fp4_t *ep8_curve_get_b(void) { + return core_get()->ep8_b; } void ep8_curve_get_ord(bn_t n) { diff --git a/src/epx/relic_ep8_dbl.c b/src/epx/relic_ep8_dbl.c index 0139e9f8d..532f15a41 100644 --- a/src/epx/relic_ep8_dbl.c +++ b/src/epx/relic_ep8_dbl.c @@ -31,6 +31,7 @@ */ #include "relic_core.h" +#include "relic_ep_dbl_tmpl.h" /*============================================================================*/ /* Private definitions */ @@ -46,65 +47,7 @@ * @param[out] s - the resulting slope. * @param[in] p - the point to double. */ -static void ep8_dbl_basic_imp(ep8_t r, fp8_t s, const ep8_t p) { - fp8_t t0, t1, t2; - - fp8_null(t0); - fp8_null(t1); - fp8_null(t2); - - RLC_TRY { - fp8_new(t0); - fp8_new(t1); - fp8_new(t2); - - /* t0 = 1/(2 * y1). */ - fp8_dbl(t0, p->y); - fp8_inv(t0, t0); - - /* t1 = 3 * x1^2 + a. */ - fp8_sqr(t1, p->x); - fp8_copy(t2, t1); - fp8_dbl(t1, t1); - fp8_add(t1, t1, t2); - - ep8_curve_get_a(t2); - fp8_add(t1, t1, t2); - - /* t1 = (3 * x1^2 + a)/(2 * y1). */ - fp8_mul(t1, t1, t0); - - if (s != NULL) { - fp8_copy(s, t1); - } - - /* t2 = t1^2. */ - fp8_sqr(t2, t1); - - /* x3 = t1^2 - 2 * x1. */ - fp8_dbl(t0, p->x); - fp8_sub(t0, t2, t0); - - /* y3 = t1 * (x1 - x3) - y1. */ - fp8_sub(t2, p->x, t0); - fp8_mul(t1, t1, t2); - - fp8_sub(r->y, t1, p->y); - - fp8_copy(r->x, t0); - fp8_copy(r->z, p->z); - - r->coord = BASIC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp8_free(t0); - fp8_free(t1); - fp8_free(t2); - } -} +TMPL_DBL_BASIC_IMP(ep8, fp8); #endif /* EP_ADD == BASIC */ @@ -169,7 +112,7 @@ static void ep8_dbl_projc_imp(ep8_t r, const ep8_t p) { /* t3 = z1^2. */ fp8_sqr(t3, p->z); - if (ep_curve_get_a() == RLC_ZERO) { + if (ep_curve_opt_a() == RLC_ZERO) { /* z3 = 2 * y1 * z1. */ fp8_mul(r->z, p->y, p->z); fp8_dbl(r->z, r->z); @@ -197,12 +140,10 @@ static void ep8_dbl_projc_imp(ep8_t r, const ep8_t p) { fp8_add(t5, t5, t0); if (p->coord != BASIC) { fp8_sqr(t3, t3); - ep8_curve_get_a(t1); - fp8_mul(t1, t3, t1); + fp8_mul(t1, t3, ep8_curve_get_a()); fp8_add(t5, t5, t1); } else { - ep8_curve_get_a(t1); - fp8_add(t5, t5, t1); + fp8_add(t5, t5, ep8_curve_get_a()); } /* x3 = T = M^2 - 2 * S. */ diff --git a/src/epx/relic_ep8_map.c b/src/epx/relic_ep8_map.c index ce8420a74..421f17c61 100644 --- a/src/epx/relic_ep8_map.c +++ b/src/epx/relic_ep8_map.c @@ -124,19 +124,17 @@ void ep8_map(ep8_t p, const uint8_t *msg, size_t len) { fp8_sqr(z1, z1); fp8_add(z1, z1, u); - ep8_curve_get_b(w); - fp8_sqr(t, x1); fp8_mul(t, t, x1); - fp8_add(t, t, w); + fp8_add(t, t, ep8_curve_get_b()); fp8_sqr(u, y1); fp8_mul(u, u, y1); - fp8_add(u, u, w); + fp8_add(u, u, ep8_curve_get_b()); fp8_sqr(v, z1); fp8_mul(v, v, z1); - fp8_add(v, v, w); + fp8_add(v, v, ep8_curve_get_b()); c2 = fp8_is_sqr(u); c3 = fp8_is_sqr(v); diff --git a/src/epx/relic_ep8_util.c b/src/epx/relic_ep8_util.c index 3b5e737f0..3232d4870 100644 --- a/src/epx/relic_ep8_util.c +++ b/src/epx/relic_ep8_util.c @@ -105,14 +105,12 @@ void ep8_blind(ep8_t r, const ep8_t p) { } void ep8_rhs(fp8_t rhs, const ep8_t p) { - fp8_t t0, t1; + fp8_t t0; fp8_null(t0); - fp8_null(t1); RLC_TRY { fp8_new(t0); - fp8_new(t1); fp8_sqr(t0, p->x); /* x1^2 */ @@ -130,20 +128,12 @@ void ep8_rhs(fp8_t rhs, const ep8_t p) { fp_add_dig(t0[0][0][0], t0[0][0][0], 2); break; case RLC_TINY: - ep8_curve_get_a(t1); - fp_mul_dig(t0[0][0][0], t0[0][0][0], t1[0][0][0][0]); - fp_mul_dig(t0[0][0][1], t0[0][0][1], t1[0][0][0][0]); - fp_mul_dig(t0[0][1][0], t0[0][1][0], t1[0][0][0][0]); - fp_mul_dig(t0[0][1][1], t0[0][1][1], t1[0][0][0][0]); - fp_mul_dig(t0[1][0][0], t0[1][0][0], t1[0][0][0][0]); - fp_mul_dig(t0[1][0][1], t0[1][0][1], t1[0][0][0][0]); - fp_mul_dig(t0[1][1][0], t0[1][1][0], t1[0][0][0][0]); - fp_mul_dig(t0[1][1][1], t0[1][1][1], t1[0][0][0][0]); + fp_add_dig(t0[0][0][0], t0[0][0][0], + ep8_curve_get_a()[0][0][0][0]) break; #endif default: - ep8_curve_get_a(t1); - fp8_add(t0, t0, t1); + fp8_add(t0, t0, ep8_curve_get_a()); break; } @@ -164,15 +154,12 @@ void ep8_rhs(fp8_t rhs, const ep8_t p) { break; case RLC_TINY: ep8_curve_get_b(t1); - fp_mul_dig(t0[0][0][0], t0[0][0][0], t1[0][0][0][0]); - fp_mul_dig(t0[0][1][0], t0[0][1][0], t1[0][0][0][0]); - fp_mul_dig(t0[1][0][0], t0[0][0][0], t1[0][0][0][0]); - fp_mul_dig(t0[1][1][0], t0[1][1][0], t1[0][0][0][0]); + fp_add_dig(t0[0][0][0], t0[0][0][0], + ep8_curve_get_b()[0][0][0][0]); break; #endif default: - ep8_curve_get_b(t1); - fp8_add(t0, t0, t1); + fp8_add(t0, t0, ep8_curve_get_b()); break; } @@ -181,11 +168,9 @@ void ep8_rhs(fp8_t rhs, const ep8_t p) { RLC_THROW(ERR_CAUGHT); } RLC_FINALLY { fp8_free(t0); - fp8_free(t1); } } - int ep8_on_curve(const ep8_t p) { ep8_t t; int r = 0; diff --git a/src/pp/relic_pp_dbl_k18.c b/src/pp/relic_pp_dbl_k18.c index 2837eed99..a7e477c5a 100644 --- a/src/pp/relic_pp_dbl_k18.c +++ b/src/pp/relic_pp_dbl_k18.c @@ -117,8 +117,7 @@ void pp_dbl_k18_projc_basic(fp18_t l, ep3_t r, const ep3_t q, const ep_t p) { /* D = 3bC, general b. */ fp3_dbl(t3, t2); fp3_add(t3, t3, t2); - ep3_curve_get_b(t4); - fp3_mul(t3, t3, t4); + fp3_mul(t3, t3, ep3_curve_get_b()); /* E = (x1 + y1)^2 - A - B. */ fp3_add(t4, q->x, q->y); fp3_sqr(t4, t4); @@ -227,8 +226,7 @@ void pp_dbl_k18_projc_lazyr(fp18_t l, ep3_t r, const ep3_t q, const ep_t p) { /* D = 3bC, for general b. */ fp3_dbl(t3, t2); fp3_add(t3, t3, t2); - ep3_curve_get_b(t4); - fp3_mul(t3, t3, t4); + fp3_mul(t3, t3, ep3_curve_get_b()); /* E = (x1 + y1)^2 - A - B. */ fp3_add(t4, q->x, q->y); fp3_sqr(t4, t4); diff --git a/src/pp/relic_pp_dbl_k24.c b/src/pp/relic_pp_dbl_k24.c index c85e56303..c1c33a79d 100644 --- a/src/pp/relic_pp_dbl_k24.c +++ b/src/pp/relic_pp_dbl_k24.c @@ -121,8 +121,7 @@ void pp_dbl_k24_projc(fp24_t l, ep4_t r, const ep4_t q, const ep_t p) { /* D = 3bC, general b. */ fp4_dbl(t3, t2); fp4_add(t3, t3, t2); - ep4_curve_get_b(t4); - fp4_mul(t3, t3, t4); + fp4_mul(t3, t3, ep4_curve_get_b()); /* E = (x1 + y1)^2 - A - B. */ fp4_add(t4, q->x, q->y); diff --git a/src/pp/relic_pp_dbl_k48.c b/src/pp/relic_pp_dbl_k48.c index a58c9155f..f80fd4f55 100644 --- a/src/pp/relic_pp_dbl_k48.c +++ b/src/pp/relic_pp_dbl_k48.c @@ -109,8 +109,7 @@ void pp_dbl_k48_projc(fp48_t l, ep8_t r, const ep8_t q, const ep_t p) { /* D = 3bC, general b. */ fp8_dbl(t3, t2); fp8_add(t3, t3, t2); - ep8_curve_get_b(t4); - fp8_mul(t3, t3, t4); + fp8_mul(t3, t3, ep8_curve_get_b()); /* E = (x1 + y1)^2 - A - B. */ fp8_add(t4, q->x, q->y); diff --git a/src/tmpl/relic_ep_add_tmpl.h b/src/tmpl/relic_ep_add_tmpl.h index bc33bb852..dddc3b201 100644 --- a/src/tmpl/relic_ep_add_tmpl.h +++ b/src/tmpl/relic_ep_add_tmpl.h @@ -703,4 +703,4 @@ } \ } \ - #endif \ No newline at end of file +#endif \ No newline at end of file diff --git a/src/tmpl/relic_ep_dbl_tmpl.h b/src/tmpl/relic_ep_dbl_tmpl.h new file mode 100644 index 000000000..d07ccefcc --- /dev/null +++ b/src/tmpl/relic_ep_dbl_tmpl.h @@ -0,0 +1,429 @@ +/* + * RELIC is an Efficient LIbrary for Cryptography + * Copyright (c) 2024 RELIC Authors + * + * This file is part of RELIC. RELIC is legal property of its developers, + * whose names are not listed here. Please refer to the COPYRIGHT file + * for contact information. + * + * RELIC is free software; you can redistribute it and/or modify it under the + * terms of the version 2.1 (or later) of the GNU Lesser General Public License + * as published by the Free Software Foundation; or version 2.0 of the Apache + * License as published by the Apache Software Foundation. See the LICENSE files + * for more details. + * + * RELIC is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + * A PARTICULAR PURPOSE. See the LICENSE files for more details. + * + * You should have received a copy of the GNU Lesser General Public or the + * Apache License along with RELIC. If not, see + * or . + */ + +/** + * @file + * + * Template for point doubling on prime elliptic curves. + * + * @ingroup tmpl + */ + +#include "relic_core.h" + +/*============================================================================*/ +/* Private definitions */ +/*============================================================================*/ + +/** + * Defines a template for point addition in affine coordinates. + * + * @param[in] C - the curve. + * @param[in] F - the field prefix. + */ +#define TMPL_DBL_BASIC_IMP(C, F) \ + static void C##_dbl_basic_imp(C##_t r, F##_t s, const C##_t p) { \ + F##_t t0, t1, t2; \ + \ + F##_null(t0); \ + F##_null(t1); \ + F##_null(t2); \ + \ + RLC_TRY { \ + F##_new(t0); \ + F##_new(t1); \ + F##_new(t2); \ + \ + /* t0 = 1/2 * y1. */ \ + F##_dbl(t0, p->y); \ + F##_inv(t0, t0); \ + \ + /* t1 = 3 * x1^2 + a. */ \ + F##_sqr(t1, p->x); \ + F##_copy(t2, t1); \ + F##_dbl(t1, t1); \ + F##_add(t1, t1, t2); \ + F##_add(t1, t1, C##_curve_get_a()); \ + \ + /* t1 = (3 * x1^2 + a)/(2 * y1). */ \ + F##_mul(t1, t1, t0); \ + \ + if (s != NULL) { \ + F##_copy(s, t1); \ + } \ + \ + /* t2 = t1^2. */ \ + F##_sqr(t2, t1); \ + \ + /* x3 = t1^2 - 2 * x1. */ \ + F##_dbl(t0, p->x); \ + F##_sub(t0, t2, t0); \ + \ + /* y3 = t1 * (x1 - x3) - y1. */ \ + F##_sub(t2, p->x, t0); \ + F##_mul(t1, t1, t2); \ + F##_sub(r->y, t1, p->y); \ + \ + F##_copy(r->x, t0); \ + F##_copy(r->z, p->z); \ + \ + r->coord = BASIC; \ + } RLC_CATCH_ANY { \ + RLC_THROW(ERR_CAUGHT); \ + } RLC_FINALLY { \ + F##_free(t0); \ + F##_free(t1); \ + F##_free(t2); \ + } \ + } \ + +/** + * Defines a template for point addition in affine coordinates. + * + * Formulas for point doubling from + * "Complete addition formulas for prime order elliptic curves" + * by Joost Renes, Craig Costello, and Lejla Batina + * https://eprint.iacr.org/2015/1060.pdf + * + * @param[in] C - the curve. + * @param[in] F - the field prefix. + */ +#define TMPL_DBL_PROJC_IMP(C, F) \ + static void C##_dbl_projc_imp(C##_t r, const C##_t p) { \ + F##_t t0, t1, t2, t3, t4, t5; \ + \ + F##_null(t0); \ + F##_null(t1); \ + F##_null(t2); \ + F##_null(t3); \ + F##_null(t4); \ + F##_null(t5); \ + \ + RLC_TRY { \ + F##_new(t0); \ + F##_new(t1); \ + F##_new(t2); \ + F##_new(t3); \ + F##_new(t4); \ + F##_new(t5); \ + \ + if (C##_curve_opt_a() == RLC_ZERO) { \ + /* Cost of 6M + 2S + 1m_3b + 9a. */ \ + F##_sqr(t0, p->y); \ + F##_mul(t3, p->x, p->y); \ + \ + if (p->coord == BASIC) { \ + /* Save 1M + 1S + 1m_b3 if z1 = 1. */ \ + F##_copy(t1, p->y); \ + F##_dbl(t2, C##_curve_get_b()); \ + F##_add(t2, t2, C##_curve_get_b()); \ + } else { \ + F##_mul(t1, p->y, p->z); \ + F##_sqr(t2, p->z); \ + F##_dbl(t5, t2); \ + F##_add(t5, t5, t2); \ + C##_curve_mul_b(t2, t5); \ + } \ + F##_dbl(r->z, t0); \ + F##_dbl(r->z, r->z); \ + F##_dbl(r->z, r->z); \ + F##_mul(r->x, t2, r->z); \ + F##_add(r->y, t0, t2); \ + F##_mul(r->z, t1, r->z); \ + F##_dbl(t1, t2); \ + F##_add(t2, t1, t2); \ + F##_sub(t0, t0, t2); \ + F##_mul(r->y, t0, r->y); \ + F##_add(r->y, r->x, r->y); \ + F##_mul(r->x, t0, t3); \ + F##_dbl(r->x, r->x); \ + } else { \ + F##_sqr(t0, p->x); \ + F##_sqr(t1, p->y); \ + F##_mul(t3, p->x, p->y); \ + F##_dbl(t3, t3); \ + F##_mul(t4, p->y, p->z); \ + \ + if (C##_curve_opt_a() == RLC_MIN3) { \ + /* Cost of 8M + 3S + 2mb + 21a. */ \ + if (p->coord == BASIC) { \ + /* Save 1S + 1m_b + 2a if z1 = 1. */ \ + F##_set_dig(t2, 3); \ + F##_copy(r->y, C##_curve_get_b()); \ + } else { \ + F##_sqr(t2, p->z); \ + C##_curve_mul_b(r->y, t2); \ + F##_dbl(t5, t2); \ + F##_add(t2, t2, t5); \ + } \ + F##_mul(r->z, p->x, p->z); \ + F##_dbl(r->z, r->z); \ + F##_sub(r->y, r->y, r->z); \ + F##_dbl(r->x, r->y); \ + F##_add(r->y, r->x, r->y); \ + F##_sub(r->x, t1, r->y); \ + F##_add(r->y, t1, r->y); \ + F##_mul(r->y, r->x, r->y); \ + F##_mul(r->x, t3, r->x); \ + C##_curve_mul_b(r->z, r->z); \ + F##_sub(t3, r->z, t2); \ + F##_sub(t3, t3, t0); \ + F##_dbl(r->z, t3); \ + F##_add(t3, t3, r->z); \ + F##_dbl(r->z, t0); \ + F##_add(t0, t0, r->z); \ + F##_sub(t0, t0, t2); \ + } else { \ + /* Common cost of 8M + 3S + 3m_a + 2m_3b + 15a. */ \ + if (p->coord == BASIC) { \ + /* Save 1S + 1m_b + 1m_a if z1 = 1. */ \ + F##_dbl(r->y, C##_curve_get_b()); \ + F##_add(r->y, r->y, C##_curve_get_b()); \ + F##_copy(t2, C##_curve_get_a()); \ + } else { \ + F##_sqr(t2, p->z); \ + F##_dbl(t5, t2); \ + F##_add(t5, t5, t2); \ + C##_curve_mul_b(r->y, t5); \ + C##_curve_mul_a(t2, t2); \ + } \ + F##_mul(r->z, p->x, p->z); \ + F##_dbl(r->z, r->z); \ + C##_curve_mul_a(r->x, r->z); \ + F##_add(r->y, r->x, r->y); \ + F##_sub(r->x, t1, r->y); \ + F##_add(r->y, t1, r->y); \ + F##_mul(r->y, r->x, r->y); \ + F##_mul(r->x, t3, r->x); \ + F##_dbl(t5, r->z); \ + F##_add(t5, t5, r->z); \ + C##_curve_mul_b(r->z, t5); \ + F##_sub(t3, t0, t2); \ + C##_curve_mul_a(t3, t3); \ + F##_add(t3, t3, r->z); \ + F##_dbl(r->z, t0); \ + F##_add(t0, t0, r->z); \ + F##_add(t0, t0, t2); \ + } \ + /* Common part with renamed variables. */ \ + F##_mul(t0, t0, t3); \ + F##_add(r->y, r->y, t0); \ + F##_dbl(t2, t4); \ + F##_mul(t0, t2, t3); \ + F##_sub(r->x, r->x, t0); \ + F##_mul(r->z, t2, t1); \ + F##_dbl(r->z, r->z); \ + F##_dbl(r->z, r->z); \ + } \ + r->coord = PROJC; \ + } RLC_CATCH_ANY { \ + RLC_THROW(ERR_CAUGHT); \ + } RLC_FINALLY { \ + F##_free(t0); \ + F##_free(t1); \ + F##_free(t2); \ + F##_free(t3); \ + F##_free(t4); \ + F##_free(t5); \ + } \ + } \ + +/** + * Defines a template for point addition in Jacobian coordinates. + * + * Formulas from http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-3.html + * + * @param[in] C - the curve. + * @param[in] F - the field prefix. + */ +#define TMPL_DBL_JACOB_IMP(C, F) \ + static void C##_dbl_jacob_imp(C##_t r, const C##_t p) { \ + F##_t t0, t1, t2, t3, t4, t5; \ + \ + F##_null(t1); \ + F##_null(t2); \ + F##_null(t3); \ + F##_null(t4); \ + F##_null(t5); \ + \ + RLC_TRY { \ + F##_new(t0); \ + F##_new(t1); \ + F##_new(t2); \ + F##_new(t3); \ + F##_new(t4); \ + F##_new(t5); \ + \ + if (p->coord != BASIC && C##_curve_opt_a() == RLC_MIN3) { \ + /* dbl-2001-b formulas: 3M + 5S + 8add + 1*4 + 2*8 + 1*3 */ \ + \ + /* t0 = delta = z1^2. */ \ + F##_sqr(t0, p->z); \ + \ + /* t1 = gamma = y1^2. */ \ + F##_sqr(t1, p->y); \ + \ + /* t2 = beta = x1 * y1^2. */ \ + F##_mul(t2, p->x, t1); \ + \ + /* t3 = alpha = 3 * (x1 - z1^2) * (x1 + z1^2). */ \ + F##_sub(t3, p->x, t0); \ + F##_add(t4, p->x, t0); \ + F##_mul(t4, t3, t4); \ + F##_dbl(t3, t4); \ + F##_add(t3, t3, t4); \ + \ + /* x3 = alpha^2 - 8 * beta. */ \ + F##_dbl(t2, t2); \ + F##_dbl(t2, t2); \ + F##_dbl(t5, t2); \ + F##_sqr(r->x, t3); \ + F##_sub(r->x, r->x, t5); \ + \ + /* z3 = (y1 + z1)^2 - gamma - delta. */ \ + F##_add(r->z, p->y, p->z); \ + F##_sqr(r->z, r->z); \ + F##_sub(r->z, r->z, t1); \ + F##_sub(r->z, r->z, t0); \ + \ + /* y3 = alpha * (4 * beta - x3) - 8 * gamma^2. */ \ + F##_dbl(t1, t1); \ + F##_sqr(t1, t1); \ + F##_dbl(t1, t1); \ + F##_sub(r->y, t2, r->x); \ + F##_mul(r->y, r->y, t3); \ + F##_sub(r->y, r->y, t1); \ + } else if (C##_curve_opt_a() == RLC_ZERO) { \ + /* dbl-2009-l formulas: 2M + 5S + 6add + 1*8 + 3*2 + 1*3.*/ \ + \ + /* A = X1^2 */ \ + F##_sqr(t0, p->x); \ + \ + /* B = Y1^2 */ \ + F##_sqr(t1, p->y); \ + \ + /* C = B^2 */ \ + F##_sqr(t2, t1); \ + \ + /* D = 2*((X1+B)^2-A-C) */ \ + F##_add(t1, t1, p->x); \ + F##_sqr(t1, t1); \ + F##_sub(t1, t1, t0); \ + F##_sub(t1, t1, t2); \ + F##_dbl(t1, t1); \ + \ + /* E = 3*A */ \ + F##_dbl(t3, t0); \ + F##_add(t0, t3, t0); \ + \ + /* F = E^2 */ \ + F##_sqr(t3, t0); \ + \ + /* Z3 = 2*Y1*Z1 */ \ + F##_mul(r->z, p->y, p->z); \ + F##_dbl(r->z, r->z); \ + \ + /* X3 = F-2*D */ \ + F##_sub(r->x, t3, t1); \ + F##_sub(r->x, r->x, t1); \ + \ + /* Y3 = E*(D-X3)-8*C */ \ + F##_sub(r->y, t1, r->x); \ + F##_mul(r->y, r->y, t0); \ + F##_dbl(t2, t2); \ + F##_dbl(t2, t2); \ + F##_dbl(t2, t2); \ + F##_sub(r->y, r->y, t2); \ + } else { \ + /* dbl-2007-bl: 1M + 8S + 1*a + 10add + 1*8 + 2*2 + 1*3 */ \ + \ + /* t0 = x1^2, t1 = y1^2, t2 = y1^4. */ \ + F##_sqr(t0, p->x); \ + F##_sqr(t1, p->y); \ + F##_sqr(t2, t1); \ + \ + if (p->coord != BASIC) { \ + /* t3 = z1^2. */ \ + F##_sqr(t3, p->z); \ + \ + if (C##_curve_opt_a() == RLC_ZERO) { \ + /* z3 = 2 * y1 * z1. */ \ + F##_mul(r->z, p->y, p->z); \ + F##_dbl(r->z, r->z); \ + } else { \ + /* z3 = (y1 + z1)^2 - y1^2 - z1^2. */ \ + F##_add(r->z, p->y, p->z); \ + F##_sqr(r->z, r->z); \ + F##_sub(r->z, r->z, t1); \ + F##_sub(r->z, r->z, t3); \ + } \ + } else { \ + /* z3 = 2 * y1. */ \ + F##_dbl(r->z, p->y); \ + } \ + \ + /* t4 = S = 2*((x1 + y1^2)^2 - x1^2 - y1^4). */ \ + F##_add(t4, p->x, t1); \ + F##_sqr(t4, t4); \ + F##_sub(t4, t4, t0); \ + F##_sub(t4, t4, t2); \ + F##_dbl(t4, t4); \ + \ + /* t5 = M = 3 * x1^2 + a * z1^4. */ \ + F##_dbl(t5, t0); \ + F##_add(t5, t5, t0); \ + if (p->coord != BASIC) { \ + C##_curve_mul_a(t1, t3); \ + F##_add(t5, t5, t1); \ + } else { \ + F##_add(t5, t5, C##_curve_get_a()); \ + } \ + /* x3 = T = M^2 - 2 * S. */ \ + F##_sqr(r->x, t5); \ + F##_dbl(t1, t4); \ + F##_sub(r->x, r->x, t1); \ + \ + /* y3 = M * (S - T) - 8 * y1^4. */ \ + F##_dbl(t2, t2); \ + F##_dbl(t2, t2); \ + F##_dbl(t2, t2); \ + F##_sub(t4, t4, r->x); \ + F##_mul(t5, t5, t4); \ + F##_sub(r->y, t5, t2); \ + } \ + \ + r->coord = JACOB; \ + } \ + RLC_CATCH_ANY { \ + RLC_THROW(ERR_CAUGHT); \ + } \ + RLC_FINALLY { \ + F##_free(t0); \ + F##_free(t1); \ + F##_free(t2); \ + F##_free(t3); \ + F##_free(t4); \ + F##_free(t5); \ + } \ + } \ + From 1ffffb0b63826d3278edc6373592bb032a9f78e8 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Fri, 29 Mar 2024 19:39:21 +0100 Subject: [PATCH 10/37] Fix bugs in conversion to template. --- src/tmpl/relic_ep_add_tmpl.h | 10 ++++------ src/tmpl/relic_ep_dbl_tmpl.h | 1 + 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/tmpl/relic_ep_add_tmpl.h b/src/tmpl/relic_ep_add_tmpl.h index dddc3b201..218ff53cd 100644 --- a/src/tmpl/relic_ep_add_tmpl.h +++ b/src/tmpl/relic_ep_add_tmpl.h @@ -226,10 +226,10 @@ F##_copy(t2, C##_curve_get_a()); \ F##_add(t4, q->x, p->x); \ F##_add(t5, q->y, p->y); \ - F##_dbl(r->z, t4); \ - F##_add(r->z, r->z, t4); \ - C##_curve_mul_a(r->z, r->z); \ - C##_curve_mul_b(r->z, r->z); \ + C##_curve_mul_a(r->z, t4); \ + F##_dbl(r->y, C##_curve_get_b()); \ + F##_add(r->y, r->y, C##_curve_get_b()); \ + F##_add(r->z, r->z, r->y); \ } else { \ C##_curve_mul_a(t2, p->z); \ F##_mul(t4, q->x, p->z); \ @@ -238,8 +238,6 @@ F##_add(t5, t5, p->y); \ F##_dbl(r->x, p->z); \ F##_add(r->x, r->x, p->z); \ - F##_dbl(r->y, r->x); \ - F##_add(r->x, r->x, r->y); \ C##_curve_mul_b(r->x, r->x); \ C##_curve_mul_a(r->z, t4); \ F##_add(r->z, r->x, r->z); \ diff --git a/src/tmpl/relic_ep_dbl_tmpl.h b/src/tmpl/relic_ep_dbl_tmpl.h index d07ccefcc..12918e277 100644 --- a/src/tmpl/relic_ep_dbl_tmpl.h +++ b/src/tmpl/relic_ep_dbl_tmpl.h @@ -393,6 +393,7 @@ F##_dbl(t5, t0); \ F##_add(t5, t5, t0); \ if (p->coord != BASIC) { \ + F##_sqr(t3, t3); \ C##_curve_mul_a(t1, t3); \ F##_add(t5, t5, t1); \ } else { \ From b5e5a7c4c7baa6dc04a4b6f7a73c68218039fab5 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Fri, 29 Mar 2024 21:41:21 +0100 Subject: [PATCH 11/37] Another big refactoring to include more coords for ep2. --- bench/bench_epx.c | 67 ++++++-- include/relic_ep.h | 24 ++- include/relic_epx.h | 155 ++++++++++++------- src/ep/relic_ep_mul.c | 2 + src/epx/relic_ep2_add.c | 287 +++++++---------------------------- src/epx/relic_ep2_cmp.c | 87 +++++++---- src/epx/relic_ep2_curve.c | 43 ++++++ src/epx/relic_ep2_dbl.c | 156 +++++-------------- src/epx/relic_ep2_mul.c | 216 +++++++++++++++++++++++++- src/epx/relic_ep2_norm.c | 57 ++++--- src/tmpl/relic_ep_add_tmpl.h | 16 +- test/test_epx.c | 74 ++++++++- 12 files changed, 679 insertions(+), 505 deletions(-) diff --git a/bench/bench_epx.c b/bench/bench_epx.c index 3380f9801..5896e9822 100644 --- a/bench/bench_epx.c +++ b/bench/bench_epx.c @@ -240,8 +240,7 @@ static void arith2(void) { ep2_rand(p); ep2_add_projc(q, q, p); BENCH_ADD(ep2_add_projc(r, p, q)); - } - BENCH_END; + } BENCH_END; BENCH_RUN("ep2_add_projc (z2 = 1)") { ep2_rand(p); @@ -250,8 +249,7 @@ static void arith2(void) { ep2_rand(q); ep2_norm(q, q); BENCH_ADD(ep2_add_projc(r, p, q)); - } - BENCH_END; + } BENCH_END; BENCH_RUN("ep2_add_projc (z1,z2 = 1)") { ep2_rand(p); @@ -259,8 +257,36 @@ static void arith2(void) { ep2_rand(q); ep2_norm(q, q); BENCH_ADD(ep2_add_projc(r, p, q)); - } - BENCH_END; + } BENCH_END; +#endif + +#if EP_ADD == JACOB || !defined(STRIP) + BENCH_RUN("ep2_add_jacob") { + ep2_rand(p); + ep2_rand(q); + ep2_add_jacob(p, p, q); + ep2_rand(q); + ep2_rand(p); + ep2_add_jacob(q, q, p); + BENCH_ADD(ep2_add_jacob(r, p, q)); + } BENCH_END; + + BENCH_RUN("ep2_add_jacob (z2 = 1)") { + ep2_rand(p); + ep2_rand(q); + ep2_add_jacob(p, p, q); + ep2_rand(q); + ep2_norm(q, q); + BENCH_ADD(ep2_add_jacob(r, p, q)); + } BENCH_END; + + BENCH_RUN("ep2_add_jacob (z1,z2 = 1)") { + ep2_rand(p); + ep2_norm(p, p); + ep2_rand(q); + ep2_norm(q, q); + BENCH_ADD(ep2_add_jacob(r, p, q)); + } BENCH_END; #endif BENCH_RUN("ep2_sub") { @@ -302,15 +328,28 @@ static void arith2(void) { ep2_rand(q); ep2_add_projc(p, p, q); BENCH_ADD(ep2_dbl_projc(r, p)); - } - BENCH_END; + } BENCH_END; BENCH_RUN("ep2_dbl_projc (z1 = 1)") { ep2_rand(p); ep2_norm(p, p); BENCH_ADD(ep2_dbl_projc(r, p)); - } - BENCH_END; + } BENCH_END; +#endif + +#if EP_ADD == JACOB || !defined(STRIP) + BENCH_RUN("ep2_dbl_jacob") { + ep2_rand(p); + ep2_rand(q); + ep2_add_jacob(p, p, q); + BENCH_ADD(ep2_dbl_jacob(r, p)); + } BENCH_END; + + BENCH_RUN("ep2_dbl_jacob (z1 = 1)") { + ep2_rand(p); + ep2_norm(p, p); + BENCH_ADD(ep2_dbl_jacob(r, p)); + } BENCH_END; #endif BENCH_RUN("ep2_neg") { @@ -357,6 +396,14 @@ static void arith2(void) { } BENCH_END; #endif +#if EP_MUL == LWREG || !defined(STRIP) + BENCH_RUN("ep2_mul_lwreg") { + bn_rand_mod(k, n); + ep2_rand(p); + BENCH_ADD(ep2_mul_lwreg(q, p, k)); + } BENCH_END; +#endif + BENCH_RUN("ep2_mul_gen") { bn_rand_mod(k, n); BENCH_ADD(ep2_mul_gen(q, k)); diff --git a/include/relic_ep.h b/include/relic_ep.h index 6579f6ae9..a276afc30 100644 --- a/include/relic_ep.h +++ b/include/relic_ep.h @@ -467,6 +467,16 @@ typedef iso_st *iso_t; #define ep_mul_sim(R, P, K, Q, M) ep_mul_sim_joint(R, P, K, Q, M) #endif +/** + * Multiplies a point in an elliptic curve over by an unrestricted scalar. + * Computes R = [k]P. + * + * @param[out] R - the result. + * @param[in] P - the point to multiply. + * @param[in] K - the integer. + */ +#define ep_mul_big(R, P, K) ep_mul_basic(R, P, K) + /** * Hashes a byte string to a prime elliptic point or the right order. * Computes R = H(s). @@ -547,13 +557,6 @@ int ep_curve_opt_a(void); */ int ep_curve_opt_b(void); -/** - * Returns a optimization identifier based on the b-coefficient of the curve. - * - * @return the optimization identifier. - */ -int ep_curve_opt_b3(void); - /** * Multiplies a field element by the a-coefficient of the curve. * @@ -570,13 +573,6 @@ void ep_curve_mul_a(fp_t c, const fp_t a); */ void ep_curve_mul_b(fp_t c, const fp_t a); -/** - * Multiplies a field element by the b3 value of the curve. - * - * @param[out] c - the result. - * @param[in] a - the field element to multiply. - */ -void ep_curve_mul_b3(fp_t c, const fp_t a); /** * Tests if the configured prime elliptic curve is a Koblitz curve. * diff --git a/include/relic_epx.h b/include/relic_epx.h index 3d8a27573..56359c175 100644 --- a/include/relic_epx.h +++ b/include/relic_epx.h @@ -441,9 +441,11 @@ typedef iso2_st *iso2_t; * @param[in] Q - the second point to add. */ #if EP_ADD == BASIC -#define ep2_add(R, P, Q) ep2_add_basic(R, P, Q); -#elif EP_ADD == PROJC || EP_ADD == JACOB -#define ep2_add(R, P, Q) ep2_add_projc(R, P, Q); +#define ep2_add(R, P, Q) ep2_add_basic(R, P, Q) +#elif EP_ADD == PROJC +#define ep2_add(R, P, Q) ep2_add_projc(R, P, Q) +#elif EP_ADD == JACOB +#define ep2_add(R, P, Q) ep2_add_jacob(R, P, Q) #endif /** @@ -454,21 +456,13 @@ typedef iso2_st *iso2_t; * @param[in] P - the point to double. */ #if EP_ADD == BASIC -#define ep2_dbl(R, P) ep2_dbl_basic(R, P); -#elif EP_ADD == PROJC || EP_ADD == JACOB -#define ep2_dbl(R, P) ep2_dbl_projc(R, P); +#define ep2_dbl(R, P) ep2_dbl_basic(R, P) +#elif EP_ADD == PROJC +#define ep2_dbl(R, P) ep2_dbl_projc(R, P) +#elif EP_ADD == JACOB +#define ep2_dbl(R, P) ep2_dbl_jacob(R, P) #endif -/** - * Multiplies a point in an elliptic curve over a quadratic extension field by - * an unrestricted integer scalar. Computes R = [k]P. - * - * @param[out] R - the result. - * @param[in] P - the point to multiply. - * @param[in] K - the integer. - */ -#define ep2_mul_big(R, P, K) ep2_mul_basic(R, P, K) - /** * Multiplies a point in an elliptic curve over a quadratic extension field. * Computes R = [k]P. @@ -544,6 +538,16 @@ typedef iso2_st *iso2_t; #define ep2_mul_sim(R, P, K, Q, M) ep2_mul_sim_joint(R, P, K, Q, M) #endif +/** + * Multiplies a point in an elliptic curve over a quadratic extension field by + * an unrestricted integer scalar. Computes R = [k]P. + * + * @param[out] R - the result. + * @param[in] P - the point to multiply. + * @param[in] K - the integer. + */ +#define ep2_mul_big(R, P, K) ep2_mul_basic(R, P, K) + /** * Hashes a byte string to a prime elliptic point or the right order. * Computes R = H(s). @@ -587,16 +591,6 @@ typedef iso2_st *iso2_t; #define ep3_dbl(R, P) ep3_dbl_projc(R, P); #endif -/** - * Multiplies a point in an elliptic curve over a cubic extension field by - * an unrestricted integer scalar. Computes R = [k]P. - * - * @param[out] R - the result. - * @param[in] P - the point to multiply. - * @param[in] K - the integer. - */ -#define ep3_mul_big(R, P, K) ep3_mul_basic(R, P, K) - /** * Multiplies a point in an elliptic curve over a cubic extension field. * Computes R = [k]P. @@ -672,6 +666,16 @@ typedef iso2_st *iso2_t; #define ep3_mul_sim(R, P, K, Q, M) ep3_mul_sim_joint(R, P, K, Q, M) #endif +/** + * Multiplies a point in an elliptic curve over a cubic extension field by + * an unrestricted integer scalar. Computes R = [k]P. + * + * @param[out] R - the result. + * @param[in] P - the point to multiply. + * @param[in] K - the integer. + */ +#define ep3_mul_big(R, P, K) ep3_mul_basic(R, P, K) + /** * Adds two points in an elliptic curve over a quartic extension field. * Computes R = P + Q. @@ -699,16 +703,6 @@ typedef iso2_st *iso2_t; #define ep4_dbl(R, P) ep4_dbl_projc(R, P); #endif -/** - * Multiplies a point in an elliptic curve over a quartic extension field by - * an unrestricted integer scalar. Computes R = [k]P. - * - * @param[out] R - the result. - * @param[in] P - the point to multiply. - * @param[in] K - the integer. - */ -#define ep4_mul_big(R, P, K) ep4_mul_basic(R, P, K) - /** * Multiplies a point in an elliptic curve over a quartic extension field. * Computes R = [k]P. @@ -784,6 +778,16 @@ typedef iso2_st *iso2_t; #define ep4_mul_sim(R, P, K, Q, M) ep4_mul_sim_joint(R, P, K, Q, M) #endif +/** + * Multiplies a point in an elliptic curve over a quartic extension field by + * an unrestricted integer scalar. Computes R = [k]P. + * + * @param[out] R - the result. + * @param[in] P - the point to multiply. + * @param[in] K - the integer. + */ +#define ep4_mul_big(R, P, K) ep4_mul_basic(R, P, K) + /** * Adds two points in an elliptic curve over a octic extension field. * Computes R = P + Q. @@ -811,16 +815,6 @@ typedef iso2_st *iso2_t; #define ep8_dbl(R, P) ep8_dbl_projc(R, P); #endif -/** - * Multiplies a point in an elliptic curve over a octic extension field by - * an unrestricted integer scalar. Computes R = [k]P. - * - * @param[out] R - the result. - * @param[in] P - the point to multiply. - * @param[in] K - the integer. - */ -#define ep8_mul_big(R, P, K) ep8_mul_basic(R, P, K) - /** * Multiplies a point in an elliptic curve over a octic extension field. * Computes R = [k]P. @@ -896,6 +890,16 @@ typedef iso2_st *iso2_t; #define ep8_mul_sim(R, P, K, Q, M) ep8_mul_sim_joint(R, P, K, Q, M) #endif +/** + * Multiplies a point in an elliptic curve over a octic extension field by + * an unrestricted integer scalar. Computes R = [k]P. + * + * @param[out] R - the result. + * @param[in] P - the point to multiply. + * @param[in] K - the integer. + */ +#define ep8_mul_big(R, P, K) ep8_mul_basic(R, P, K) + /*============================================================================*/ /* Function prototypes */ /*============================================================================*/ @@ -938,6 +942,22 @@ int ep2_curve_opt_a(void); */ int ep2_curve_opt_b(void); +/** + * Multiplies a field element by the a-coefficient of the curve. + * + * @param[out] c - the result. + * @param[in] a - the field element to multiply. + */ +void ep2_curve_mul_a(fp2_t c, const fp2_t a); + +/** + * Multiplies a field element by the b-coefficient of the curve. + * + * @param[out] c - the result. + * @param[in] a - the field element to multiply. + */ +void ep2_curve_mul_b(fp2_t c, const fp2_t a); + /** * Tests if the configured elliptic curve is a twist. * @@ -1157,6 +1177,16 @@ void ep2_add_slp_basic(ep2_t r, fp2_t s, const ep2_t p, const ep2_t q); */ void ep2_add_projc(ep2_t r, const ep2_t p, const ep2_t q); +/** + * Adds two points represented in Jacobian coordinates in an elliptic curve + * over a quadratic extension. + * + * @param[out] r - the result. + * @param[in] p - the first point to add. + * @param[in] q - the second point to add. + */ +void ep2_add_jacob(ep2_t r, const ep2_t p, const ep2_t q); + /** * Subtracts a point in an elliptic curve over a quadratic extension from * another. @@ -1168,7 +1198,7 @@ void ep2_add_projc(ep2_t r, const ep2_t p, const ep2_t q); void ep2_sub(ep2_t r, const ep2_t p, const ep2_t q); /** - * Doubles a points represented in affine coordinates in an elliptic curve over + * Doubles a point represented in affine coordinates in an elliptic curve over * a quadratic extension. * * @param[out] r - the result. @@ -1177,7 +1207,7 @@ void ep2_sub(ep2_t r, const ep2_t p, const ep2_t q); void ep2_dbl_basic(ep2_t r, const ep2_t p); /** - * Doubles a points represented in affine coordinates in an elliptic curve over + * Doubles a point represented in affine coordinates in an elliptic curve over * a quadratic extension and returns the computed slope. * * @param[out] r - the result. @@ -1187,7 +1217,7 @@ void ep2_dbl_basic(ep2_t r, const ep2_t p); void ep2_dbl_slp_basic(ep2_t r, fp2_t s, const ep2_t p); /** - * Doubles a points represented in projective coordinates in an elliptic curve + * Doubles a point represented in projective coordinates in an elliptic curve * over a quadratic extension. * * @param[out] r - the result. @@ -1195,6 +1225,15 @@ void ep2_dbl_slp_basic(ep2_t r, fp2_t s, const ep2_t p); */ void ep2_dbl_projc(ep2_t r, const ep2_t p); +/** + * Doubles a point represented in Jacobian coordinates in an elliptic curve + * over a quadratic extension. + * + * @param[out] r - the result. + * @param[in] p - the point to double. + */ +void ep2_dbl_jacob(ep2_t r, const ep2_t p); + /** * Multiplies a prime elliptic point by an integer using the binary method. * @@ -1800,7 +1839,7 @@ void ep3_add_projc(ep3_t r, const ep3_t p, const ep3_t q); void ep3_sub(ep3_t r, const ep3_t p, const ep3_t q); /** - * Doubles a points represented in affine coordinates in an elliptic curve over + * Doubles a point represented in affine coordinates in an elliptic curve over * a octic extension. * * @param[out] r - the result. @@ -1809,7 +1848,7 @@ void ep3_sub(ep3_t r, const ep3_t p, const ep3_t q); void ep3_dbl_basic(ep3_t r, const ep3_t p); /** - * Doubles a points represented in affine coordinates in an elliptic curve over + * Doubles a point represented in affine coordinates in an elliptic curve over * a octic extension and returns the computed slope. * * @param[out] r - the result. @@ -1819,7 +1858,7 @@ void ep3_dbl_basic(ep3_t r, const ep3_t p); void ep3_dbl_slp_basic(ep3_t r, fp3_t s, const ep3_t p); /** - * Doubles a points represented in projective coordinates in an elliptic curve + * Doubles a point represented in projective coordinates in an elliptic curve * over a octic extension. * * @param[out] r - the result. @@ -2409,7 +2448,7 @@ void ep4_add_projc(ep4_t r, const ep4_t p, const ep4_t q); void ep4_sub(ep4_t r, const ep4_t p, const ep4_t q); /** - * Doubles a points represented in affine coordinates in an elliptic curve over + * Doubles a point represented in affine coordinates in an elliptic curve over * a octic extension. * * @param[out] r - the result. @@ -2418,7 +2457,7 @@ void ep4_sub(ep4_t r, const ep4_t p, const ep4_t q); void ep4_dbl_basic(ep4_t r, const ep4_t p); /** - * Doubles a points represented in affine coordinates in an elliptic curve over + * Doubles a point represented in affine coordinates in an elliptic curve over * a octic extension and returns the computed slope. * * @param[out] r - the result. @@ -2428,7 +2467,7 @@ void ep4_dbl_basic(ep4_t r, const ep4_t p); void ep4_dbl_slp_basic(ep4_t r, fp4_t s, const ep4_t p); /** - * Doubles a points represented in projective coordinates in an elliptic curve + * Doubles a point represented in projective coordinates in an elliptic curve * over a octic extension. * * @param[out] r - the result. @@ -3019,7 +3058,7 @@ void ep8_add_projc(ep8_t r, const ep8_t p, const ep8_t q); void ep8_sub(ep8_t r, const ep8_t p, const ep8_t q); /** - * Doubles a points represented in affine coordinates in an elliptic curve over + * Doubles a point represented in affine coordinates in an elliptic curve over * a octic extension. * * @param[out] r - the result. @@ -3028,7 +3067,7 @@ void ep8_sub(ep8_t r, const ep8_t p, const ep8_t q); void ep8_dbl_basic(ep8_t r, const ep8_t p); /** - * Doubles a points represented in affine coordinates in an elliptic curve over + * Doubles a point represented in affine coordinates in an elliptic curve over * a octic extension and returns the computed slope. * * @param[out] r - the result. @@ -3038,7 +3077,7 @@ void ep8_dbl_basic(ep8_t r, const ep8_t p); void ep8_dbl_slp_basic(ep8_t r, fp8_t s, const ep8_t p); /** - * Doubles a points represented in projective coordinates in an elliptic curve + * Doubles a point represented in projective coordinates in an elliptic curve * over a octic extension. * * @param[out] r - the result. diff --git a/src/ep/relic_ep_mul.c b/src/ep/relic_ep_mul.c index db0fda139..1e308d340 100644 --- a/src/ep/relic_ep_mul.c +++ b/src/ep/relic_ep_mul.c @@ -208,6 +208,7 @@ static void ep_mul_naf_imp(ep_t r, const ep_t p, const bn_t k) { #endif /* EP_PLAIN || EP_SUPER */ #endif /* EP_MUL == LWNAF */ +#if EP_MUL == LWREG || !defined(STRIP) #if defined(EP_ENDOM) static void ep_mul_reg_glv(ep_t r, const ep_t p, const bn_t k) { @@ -443,6 +444,7 @@ static void ep_mul_reg_imp(ep_t r, const ep_t p, const bn_t k) { } #endif /* EP_PLAIN || EP_SUPER */ +#endif /* EP_MUL == LWREG */ /*============================================================================*/ /* Public definitions */ diff --git a/src/epx/relic_ep2_add.c b/src/epx/relic_ep2_add.c index f793de14a..bf5034ffe 100644 --- a/src/epx/relic_ep2_add.c +++ b/src/epx/relic_ep2_add.c @@ -43,252 +43,62 @@ * Adds two points represented in affine coordinates on an ordinary prime * elliptic curve. * - * @param r - the result. - * @param s - the resulting slope. - * @param p - the first point to add. - * @param q - the second point to add. + * @param[out] r - the result. + * @param[out] s - the slope. + * @param[in] p - the first point to add. + * @param[in] q - the second point to add. */ TMPL_ADD_BASIC_IMP(ep2, fp2); #endif /* EP_ADD == BASIC */ -#if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) +#if EP_ADD == PROJC || !defined(STRIP) -#if defined(EP_MIXED) || !defined(STRIP) +/** + * Adds a point represented in homogeneous coordinates to a point represented in + * affine coordinates on an ordinary prime elliptic curve. + * + * @param[out] r - the result. + * @param[in] p - the projective point. + * @param[in] q - the affine point. + */ +TMPL_ADD_PROJC_MIX(ep2, fp2); /** - * Adds a point represented in affine coordinates to a point represented in - * projective coordinates. + * Adds two points represented in homogeneous coordinates on an ordinary prime + * elliptic curve. * - * @param r - the result. - * @param s - the slope. - * @param p - the affine point. - * @param q - the projective point. + * @param[out] r - the result. + * @param[in] p - the first point to add. + * @param[in] q - the second point to add. */ -static void ep2_add_projc_mix(ep2_t r, const ep2_t p, const ep2_t q) { - fp2_t t0, t1, t2, t3, t4, t5, t6; +TMPL_ADD_PROJC_IMP(ep2, fp2); - fp2_null(t0); - fp2_null(t1); - fp2_null(t2); - fp2_null(t3); - fp2_null(t4); - fp2_null(t5); - fp2_null(t6); +#endif /* EP_ADD == PROJC */ - RLC_TRY { - fp2_new(t0); - fp2_new(t1); - fp2_new(t2); - fp2_new(t3); - fp2_new(t4); - fp2_new(t5); - fp2_new(t6); - - if (p->coord != BASIC) { - /* t0 = z1^2. */ - fp2_sqr(t0, p->z); - - /* t3 = U2 = x2 * z1^2. */ - fp2_mul(t3, q->x, t0); - - /* t1 = S2 = y2 * z1^3. */ - fp2_mul(t1, t0, p->z); - fp2_mul(t1, t1, q->y); - - /* t3 = H = U2 - x1. */ - fp2_sub(t3, t3, p->x); - - /* t1 = R = 2 * (S2 - y1). */ - fp2_sub(t1, t1, p->y); - } else { - /* H = x2 - x1. */ - fp2_sub(t3, q->x, p->x); - - /* t1 = R = 2 * (y2 - y1). */ - fp2_sub(t1, q->y, p->y); - } - - /* t2 = HH = H^2. */ - fp2_sqr(t2, t3); - - /* If E is zero. */ - if (fp2_is_zero(t3)) { - if (fp2_is_zero(t1)) { - /* If I is zero, p = q, should have doubled. */ - ep2_dbl_projc(r, p); - } else { - /* If I is not zero, q = -p, r = infinity. */ - ep2_set_infty(r); - } - } else { - /* t5 = J = H * HH. */ - fp2_mul(t5, t3, t2); - - /* t4 = V = x1 * HH. */ - fp2_mul(t4, p->x, t2); - - /* x3 = R^2 - J - 2 * V. */ - fp2_sqr(r->x, t1); - fp2_sub(r->x, r->x, t5); - fp2_dbl(t6, t4); - fp2_sub(r->x, r->x, t6); - - /* y3 = R * (V - x3) - Y1 * J. */ - fp2_sub(t4, t4, r->x); - fp2_mul(t4, t4, t1); - fp2_mul(t1, p->y, t5); - fp2_sub(r->y, t4, t1); - - if (p->coord != BASIC) { - /* z3 = z1 * H. */ - fp2_mul(r->z, p->z, t3); - } else { - /* z3 = H. */ - fp2_copy(r->z, t3); - } - } - r->coord = PROJC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp2_free(t0); - fp2_free(t1); - fp2_free(t2); - fp2_free(t3); - fp2_free(t4); - fp2_free(t5); - fp2_free(t6); - } -} +#if EP_ADD == JACOB || !defined(STRIP) -#endif +/** + * Adds a point represented in Jacobian coordinates to a point represented in + * affine coordinates on an ordinary prime elliptic curve. + * + * @param[out] r - the result. + * @param[in] p - the projective point. + * @param[in] q - the affine point. + */ +TMPL_ADD_JACOB_MIX(ep2, fp2); /** - * Adds two points represented in projective coordinates on an ordinary prime + * Adds two points represented in Jacobian coordinates on an ordinary prime * elliptic curve. * - * @param r - the result. - * @param p - the first point to add. - * @param q - the second point to add. + * @param[out] r - the result. + * @param[in] p - the first point to add. + * @param[in] q - the second point to add. */ -static void ep2_add_projc_imp(ep2_t r, const ep2_t p, const ep2_t q) { -#if defined(EP_MIXED) && defined(STRIP) - ep2_add_projc_mix(r, p, q); -#else /* General addition. */ - fp2_t t0, t1, t2, t3, t4, t5, t6; - - fp2_null(t0); - fp2_null(t1); - fp2_null(t2); - fp2_null(t3); - fp2_null(t4); - fp2_null(t5); - fp2_null(t6); - - RLC_TRY { - fp2_new(t0); - fp2_new(t1); - fp2_new(t2); - fp2_new(t3); - fp2_new(t4); - fp2_new(t5); - fp2_new(t6); - - if (q->coord == BASIC) { - ep2_add_projc_mix(r, p, q); - } else { - /* t0 = z1^2. */ - fp2_sqr(t0, p->z); - - /* t1 = z2^2. */ - fp2_sqr(t1, q->z); - - /* t2 = U1 = x1 * z2^2. */ - fp2_mul(t2, p->x, t1); - - /* t3 = U2 = x2 * z1^2. */ - fp2_mul(t3, q->x, t0); - - /* t6 = z1^2 + z2^2. */ - fp2_add(t6, t0, t1); - - /* t0 = S2 = y2 * z1^3. */ - fp2_mul(t0, t0, p->z); - fp2_mul(t0, t0, q->y); - - /* t1 = S1 = y1 * z2^3. */ - fp2_mul(t1, t1, q->z); - fp2_mul(t1, t1, p->y); - - /* t3 = H = U2 - U1. */ - fp2_sub(t3, t3, t2); - - /* t0 = R = 2 * (S2 - S1). */ - fp2_sub(t0, t0, t1); - - fp2_dbl(t0, t0); - - /* If E is zero. */ - if (fp2_is_zero(t3)) { - if (fp2_is_zero(t0)) { - /* If I is zero, p = q, should have doubled. */ - ep2_dbl_projc(r, p); - } else { - /* If I is not zero, q = -p, r = infinity. */ - ep2_set_infty(r); - } - } else { - /* t4 = I = (2*H)^2. */ - fp2_dbl(t4, t3); - fp2_sqr(t4, t4); - - /* t5 = J = H * I. */ - fp2_mul(t5, t3, t4); - - /* t4 = V = U1 * I. */ - fp2_mul(t4, t2, t4); - - /* x3 = R^2 - J - 2 * V. */ - fp2_sqr(r->x, t0); - fp2_sub(r->x, r->x, t5); - fp2_dbl(t2, t4); - fp2_sub(r->x, r->x, t2); - - /* y3 = R * (V - x3) - 2 * S1 * J. */ - fp2_sub(t4, t4, r->x); - fp2_mul(t4, t4, t0); - fp2_mul(t1, t1, t5); - fp2_dbl(t1, t1); - fp2_sub(r->y, t4, t1); - - /* z3 = ((z1 + z2)^2 - z1^2 - z2^2) * H. */ - fp2_add(r->z, p->z, q->z); - fp2_sqr(r->z, r->z); - fp2_sub(r->z, r->z, t6); - fp2_mul(r->z, r->z, t3); - } - } - r->coord = PROJC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp2_free(t0); - fp2_free(t1); - fp2_free(t2); - fp2_free(t3); - fp2_free(t4); - fp2_free(t5); - fp2_free(t6); - } -#endif -} +TMPL_ADD_JACOB_IMP(ep2, fp2); -#endif /* EP_ADD == PROJC */ +#endif /* EP_ADD == JACOB */ /*============================================================================*/ /* Public definitions */ @@ -326,7 +136,7 @@ void ep2_add_slp_basic(ep2_t r, fp2_t s, const ep2_t p, const ep2_t q) { #endif -#if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) +#if EP_ADD == PROJC || !defined(STRIP) void ep2_add_projc(ep2_t r, const ep2_t p, const ep2_t q) { if (ep2_is_infty(p)) { @@ -339,13 +149,25 @@ void ep2_add_projc(ep2_t r, const ep2_t p, const ep2_t q) { return; } - if (p == q) { - /* TODO: This is a quick hack. Should we fix this? */ - ep2_dbl(r, p); + ep2_add_projc_imp(r, p, q); +} + +#endif + +#if EP_ADD == JACOB || !defined(STRIP) + +void ep2_add_jacob(ep2_t r, const ep2_t p, const ep2_t q) { + if (ep2_is_infty(p)) { + ep2_copy(r, q); return; } - ep2_add_projc_imp(r, p, q); + if (ep2_is_infty(q)) { + ep2_copy(r, p); + return; + } + + ep2_add_jacob_imp(r, p, q); } #endif @@ -362,7 +184,6 @@ void ep2_sub(ep2_t r, const ep2_t p, const ep2_t q) { RLC_TRY { ep2_new(t); - ep2_neg(t, q); ep2_add(r, p, t); } diff --git a/src/epx/relic_ep2_cmp.c b/src/epx/relic_ep2_cmp.c index 8c44a9eba..157e34bd9 100644 --- a/src/epx/relic_ep2_cmp.c +++ b/src/epx/relic_ep2_cmp.c @@ -37,46 +37,69 @@ /*============================================================================*/ int ep2_cmp(const ep2_t p, const ep2_t q) { - ep2_t r, s; - int result = RLC_NE; + ep2_t r, s; + int result = RLC_NE; if (ep2_is_infty(p) && ep2_is_infty(q)) { return RLC_EQ; } - ep2_null(r); - ep2_null(s); + ep2_null(r); + ep2_null(s); - RLC_TRY { - ep2_new(r); - ep2_new(s); + RLC_TRY { + ep2_new(r); + ep2_new(s); - if ((p->coord != BASIC) && (q->coord != BASIC)) { - /* If the two points are not normalized, it is faster to compare - * x1 * z2^2 == x2 * z1^2 and y1 * z2^3 == y2 * z1^3. */ - fp2_sqr(r->z, p->z); - fp2_sqr(s->z, q->z); - fp2_mul(r->x, p->x, s->z); - fp2_mul(s->x, q->x, r->z); - fp2_mul(r->z, r->z, p->z); - fp2_mul(s->z, s->z, q->z); - fp2_mul(r->y, p->y, s->z); - fp2_mul(s->y, q->y, r->z); - } else { - ep2_norm(r, p); - ep2_norm(s, q); - } + switch (q->coord) { + case PROJC: + /* If q is in homogeneous projective coordinates, compute + * x1 * z2 and y1 * z2. */ + fp2_mul(r->x, p->x, q->z); + fp2_mul(r->y, p->y, q->z); + break; + case JACOB: + /* If q is in Jacobian projective coordinates, compute + * x2 * z1^2 and y2 * z1^3. */ + fp2_sqr(r->z, q->z); + fp2_mul(r->x, p->x, r->z); + fp2_mul(r->z, r->z, q->z); + fp2_mul(r->y, p->y, r->z); + break; + default: + ep2_copy(r, p); + break; + } - if ((fp2_cmp(r->x, s->x) == RLC_EQ) && + switch (p->coord) { + /* Now do the same for the other point. */ + case PROJC: + fp2_mul(s->x, q->x, p->z); + fp2_mul(s->y, q->y, p->z); + break; + case JACOB: + fp2_sqr(s->z, p->z); + fp2_mul(s->x, q->x, s->z); + fp2_mul(s->z, s->z, p->z); + fp2_mul(s->y, q->y, s->z); + break; + default: + ep2_copy(s, q); + break; + } + + if ((fp2_cmp(r->x, s->x) == RLC_EQ) && (fp2_cmp(r->y, s->y) == RLC_EQ)) { - result = RLC_EQ; - } - } RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } RLC_FINALLY { - ep2_free(r); - ep2_free(s); - } + result = RLC_EQ; + } + } + RLC_CATCH_ANY { + RLC_THROW(ERR_CAUGHT); + } + RLC_FINALLY { + ep2_free(r); + ep2_free(s); + } - return result; + return result; } diff --git a/src/epx/relic_ep2_curve.c b/src/epx/relic_ep2_curve.c index 5af21b291..9168e7341 100644 --- a/src/epx/relic_ep2_curve.c +++ b/src/epx/relic_ep2_curve.c @@ -657,6 +657,49 @@ int ep2_curve_opt_b(void) { return core_get()->ep2_opt_b; } +void ep2_curve_mul_a(fp2_t c, const fp2_t a) { + ctx_t *ctx = core_get(); + switch (ctx->ep2_opt_a) { + case RLC_ZERO: + fp2_zero(c); + break; + case RLC_ONE: + fp2_copy(c, a); + break; + case RLC_TWO: + fp2_dbl(c, a); + break; +#if FP_RDC != MONTY + case RLC_TINY: + fp2_mul_dig(c, a, ctx->ep2_a[0]); + break; +#endif + default: + fp2_mul(c, a, ctx->ep2_a); + break; + } +} + +void ep2_curve_mul_b(fp2_t c, const fp2_t a) { + ctx_t *ctx = core_get(); + switch (ctx->ep2_opt_b) { + case RLC_ZERO: + fp2_zero(c); + break; + case RLC_ONE: + fp2_copy(c, a); + break; +#if FP_RDC != MONTY + case RLC_TINY: + fp2_mul_dig(c, a, ctx->ep2_b[0]); + break; +#endif + default: + fp2_mul(c, a, ctx->ep2_b); + break; + } +} + int ep2_curve_is_twist(void) { return core_get()->ep2_is_twist; } diff --git a/src/epx/relic_ep2_dbl.c b/src/epx/relic_ep2_dbl.c index 0d2900d9d..ca5bec8db 100644 --- a/src/epx/relic_ep2_dbl.c +++ b/src/epx/relic_ep2_dbl.c @@ -44,141 +44,41 @@ * elliptic curve. * * @param[out] r - the result. - * @param[out] s - the resulting slope. + * @param[out] s - the slope. * @param[in] p - the point to double. */ TMPL_DBL_BASIC_IMP(ep2, fp2); #endif /* EP_ADD == BASIC */ -#if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) +#if EP_ADD == PROJC || !defined(STRIP) /** - * Doubles a point represented in affine coordinates on an ordinary prime + * Doubles a point represented in projective coordinates on an ordinary prime * elliptic curve. * - * @param[out] r - the result. - * @param[in] p - the point to double. + * @param r - the result. + * @param p - the point to double. */ -static void ep2_dbl_projc_imp(ep2_t r, const ep2_t p) { - fp2_t t0, t1, t2, t3, t4, t5; - - fp2_null(t0); - fp2_null(t1); - fp2_null(t2); - fp2_null(t3); - fp2_null(t4); - fp2_null(t5); - - RLC_TRY { - fp2_new(t0); - fp2_new(t1); - fp2_new(t2); - fp2_new(t3); - fp2_new(t4); - fp2_new(t5); - - if (ep_curve_opt_a() == RLC_ZERO) { - fp2_sqr(t0, p->x); - fp2_add(t2, t0, t0); - fp2_add(t0, t2, t0); - - fp2_sqr(t3, p->y); - fp2_mul(t1, t3, p->x); - fp2_add(t1, t1, t1); - fp2_add(t1, t1, t1); - fp2_sqr(r->x, t0); - fp2_add(t2, t1, t1); - fp2_sub(r->x, r->x, t2); - fp2_mul(r->z, p->z, p->y); - fp2_add(r->z, r->z, r->z); - fp2_add(t3, t3, t3); - - fp2_sqr(t3, t3); - fp2_add(t3, t3, t3); - fp2_sub(t1, t1, r->x); - fp2_mul(r->y, t0, t1); - fp2_sub(r->y, r->y, t3); - } else { - /* dbl-2007-bl formulas: 1M + 8S + 1*a + 10add + 1*8 + 2*2 + 1*3 */ - /* http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-2007-bl */ - - /* t0 = x1^2, t1 = y1^2, t2 = y1^4. */ - fp2_sqr(t0, p->x); - fp2_sqr(t1, p->y); - fp2_sqr(t2, t1); - - if (p->coord != BASIC) { - /* t3 = z1^2. */ - fp2_sqr(t3, p->z); - - if (ep_curve_opt_a() == RLC_ZERO) { - /* z3 = 2 * y1 * z1. */ - fp2_mul(r->z, p->y, p->z); - fp2_dbl(r->z, r->z); - } else { - /* z3 = (y1 + z1)^2 - y1^2 - z1^2. */ - fp2_add(r->z, p->y, p->z); - fp2_sqr(r->z, r->z); - fp2_sub(r->z, r->z, t1); - fp2_sub(r->z, r->z, t3); - } - } else { - /* z3 = 2 * y1. */ - fp2_dbl(r->z, p->y); - } - - /* t4 = S = 2*((x1 + y1^2)^2 - x1^2 - y1^4). */ - fp2_add(t4, p->x, t1); - fp2_sqr(t4, t4); - fp2_sub(t4, t4, t0); - fp2_sub(t4, t4, t2); - fp2_dbl(t4, t4); - - /* t5 = M = 3 * x1^2 + a * z1^4. */ - fp2_dbl(t5, t0); - fp2_add(t5, t5, t0); - if (p->coord != BASIC) { - fp2_sqr(t3, t3); - fp2_mul(t1, t3, ep2_curve_get_a()); - fp2_add(t5, t5, t1); - } else { - fp2_add(t5, t5, ep2_curve_get_a()); - } - - /* x3 = T = M^2 - 2 * S. */ - fp2_sqr(r->x, t5); - fp2_dbl(t1, t4); - fp2_sub(r->x, r->x, t1); - - /* y3 = M * (S - T) - 8 * y1^4. */ - fp2_dbl(t2, t2); - fp2_dbl(t2, t2); - fp2_dbl(t2, t2); - fp2_sub(t4, t4, r->x); - fp2_mul(t5, t5, t4); - fp2_sub(r->y, t5, t2); - } - - r->coord = PROJC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp2_free(t0); - fp2_free(t1); - fp2_free(t2); - fp2_free(t3); - fp2_free(t4); - fp2_free(t5); - } -} +TMPL_DBL_PROJC_IMP(ep2, fp2); #endif /* EP_ADD == PROJC */ +#if EP_ADD == JACOB || !defined(STRIP) + +/** + * Doubles a point represented in Jacobian coordinates on an ordinary prime + * elliptic curve. + * + * @param r - the result. + * @param p - the point to double. + */ +TMPL_DBL_JACOB_IMP(ep2, fp2); + +#endif /* EP_ADD == JACOB */ + /*============================================================================*/ - /* Public definitions */ +/* Public definitions */ /*============================================================================*/ #if EP_ADD == BASIC || !defined(STRIP) @@ -188,7 +88,6 @@ void ep2_dbl_basic(ep2_t r, const ep2_t p) { ep2_set_infty(r); return; } - ep2_dbl_basic_imp(r, NULL, p); } @@ -203,7 +102,7 @@ void ep2_dbl_slp_basic(ep2_t r, fp2_t s, const ep2_t p) { #endif -#if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) +#if EP_ADD == PROJC || !defined(STRIP) void ep2_dbl_projc(ep2_t r, const ep2_t p) { if (ep2_is_infty(p)) { @@ -215,3 +114,16 @@ void ep2_dbl_projc(ep2_t r, const ep2_t p) { } #endif + +#if EP_ADD == JACOB || !defined(STRIP) + +void ep2_dbl_jacob(ep2_t r, const ep2_t p) { + if (ep2_is_infty(p)) { + ep2_set_infty(r); + return; + } + + ep2_dbl_jacob_imp(r, p); +} + +#endif diff --git a/src/epx/relic_ep2_mul.c b/src/epx/relic_ep2_mul.c index 698f026b6..188430626 100644 --- a/src/epx/relic_ep2_mul.c +++ b/src/epx/relic_ep2_mul.c @@ -40,7 +40,7 @@ #if defined(EP_ENDOM) -static void ep2_mul_glv_imp(ep2_t r, const ep2_t p, const bn_t k) { +static void ep2_mul_gls_imp(ep2_t r, const ep2_t p, const bn_t k) { size_t l, _l[4]; bn_t n, _k[4], u; int8_t naf[4][RLC_FP_BITS + 1]; @@ -164,6 +164,196 @@ static void ep2_mul_naf_imp(ep2_t r, const ep2_t p, const bn_t k) { #endif /* EP_PLAIN || EP_SUPER */ #endif /* EP_MUL == LWNAF */ +#if EP_MUL == LWREG || !defined(STRIP) + +#if defined(EP_ENDOM) + +static void ep2_mul_reg_gls(ep2_t r, const ep2_t p, const bn_t k) { + size_t l, _l[4]; + bn_t n, _k[4], u; + int8_t reg[4][RLC_FP_BITS + 1], b[4], s[4], _s0, _s1; + ep2_t q[4], t; + + bn_null(n); + bn_null(u); + ep2_null(t); + + RLC_TRY { + bn_new(n); + bn_new(u); + for (int i = 0; i < 4; i++) { + bn_null(_k[i]); + ep2_null(q[i]); + bn_new(_k[i]); + ep2_new(q[i]); + } + ep2_new(t); + + 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 (int i = 0; i < 4; i++) { + s[i] = bn_sign(_k[i]); + bn_abs(_k[i], _k[i]); + b[i] = bn_is_even(_k[i]); + _k[i]->dp[0] |= b[i]; + + _l[i] = RLC_FP_BITS + 1; + bn_rec_reg(reg[i], &_l[i], _k[i], bn_bits(u), 2); + l = RLC_MAX(l, _l[i]); + } + + ep2_set_infty(r); + for (int j = l - 1; j >= 0; j--) { + ep2_dbl(r, r); + + for (int i = 0; i < 4; i++) { + _s0 = reg[i][j] > 0; + _s1 = s[i] == RLC_POS; + + ep2_neg(t, q[i]); + dv_copy_cond(t->y[0], q[i]->y[0], RLC_FP_DIGS, _s0 == _s1); + dv_copy_cond(t->y[1], q[i]->y[1], RLC_FP_DIGS, _s0 == _s1); + ep2_add(r, r, t); + } + } + + for (int i = 0; i < 4; i++) { + ep2_neg(t, q[i]); + dv_copy_cond(t->y[0], q[i]->y[0], RLC_FP_DIGS, s[i] == RLC_NEG); + dv_copy_cond(t->y[1], q[i]->y[1], RLC_FP_DIGS, s[i] == RLC_NEG); + ep2_add(t, r, t); + dv_copy_cond(r->x[0], t->x[0], RLC_FP_DIGS, b[i]); + dv_copy_cond(r->x[1], t->x[1], RLC_FP_DIGS, b[i]); + dv_copy_cond(r->y[0], t->y[0], RLC_FP_DIGS, b[i]); + dv_copy_cond(r->y[1], t->y[1], RLC_FP_DIGS, b[i]); + dv_copy_cond(r->z[0], t->z[0], RLC_FP_DIGS, b[i]); + dv_copy_cond(r->z[1], t->z[1], RLC_FP_DIGS, b[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 (int i = 0; i < 4; i++) { + bn_free(_k[i]); + ep2_free(q[i]); + } + ep2_free(t); + } +} + +#endif /* EP_ENDOM */ + +#if defined(EP_PLAIN) || defined(EP_SUPER) + +static void ep2_mul_reg_imp(ep2_t r, const ep2_t p, const bn_t k) { + bn_t _k; + int i, j, n; + int8_t s, reg[1 + RLC_CEIL(RLC_FP_BITS + 1, RLC_WIDTH - 1)]; + ep2_t t[1 << (RLC_WIDTH - 2)], u, v; + size_t l; + + bn_null(_k); + + RLC_TRY { + bn_new(_k); + ep2_new(u); + ep2_new(v); + /* Prepare the precomputation table. */ + for (i = 0; i < (1 << (RLC_WIDTH - 2)); i++) { + ep2_null(t[i]); + ep2_new(t[i]); + } + /* Compute the precomputation table. */ + ep2_tab(t, p, RLC_WIDTH); + + ep2_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, RLC_WIDTH - 1) + 1; + bn_rec_reg(reg, &l, _k, n, RLC_WIDTH); + +#if defined(EP_MIXED) + fp_set_dig(u->z, 1); + u->coord = BASIC; +#else + u->coord = EP_ADD; +#endif + ep2_set_infty(r); + for (i = l - 1; i >= 0; i--) { + for (j = 0; j < RLC_WIDTH - 1; j++) { + ep2_dbl(r, r); + } + + n = reg[i]; + s = (n >> 7); + n = ((n ^ s) - s) >> 1; + + for (j = 0; j < (1 << (RLC_WIDTH - 2)); j++) { + dv_copy_cond(u->x[0], t[j]->x[0], RLC_FP_DIGS, j == n); + dv_copy_cond(u->x[1], t[j]->x[1], RLC_FP_DIGS, j == n); + dv_copy_cond(u->y[0], t[j]->y[0], RLC_FP_DIGS, j == n); + dv_copy_cond(u->y[1], t[j]->y[1], RLC_FP_DIGS, j == n); +#if !defined(EP_MIXED) + dv_copy_cond(u->z[0], t[j]->z[0], RLC_FP_DIGS, j == n); + dv_copy_cond(u->z[1], t[j]->z[1], RLC_FP_DIGS, j == n); +#endif + } + ep2_neg(v, u); + dv_copy_cond(u->y[0], v->y[0], RLC_FP_DIGS, s != 0); + dv_copy_cond(u->y[1], v->y[1], RLC_FP_DIGS, s != 0); + ep2_add(r, r, u); + } + /* t[0] has an unmodified copy of p. */ + ep2_sub(u, r, t[0]); + dv_copy_cond(r->x[0], u->x[0], RLC_FP_DIGS, bn_is_even(k)); + dv_copy_cond(r->x[1], u->x[1], RLC_FP_DIGS, bn_is_even(k)); + dv_copy_cond(r->y[0], u->y[0], RLC_FP_DIGS, bn_is_even(k)); + dv_copy_cond(r->y[1], u->y[1], RLC_FP_DIGS, bn_is_even(k)); + dv_copy_cond(r->z[0], u->z[0], RLC_FP_DIGS, bn_is_even(k)); + dv_copy_cond(r->z[1], u->z[1], RLC_FP_DIGS, bn_is_even(k)); + /* Convert r to affine coordinates. */ + ep2_norm(r, r); + ep2_neg(u, r); + dv_copy_cond(r->y[0], u->y[0], RLC_FP_DIGS, bn_sign(k) == RLC_NEG); + dv_copy_cond(r->y[1], u->y[1], 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 << (RLC_WIDTH - 2)); i++) { + ep2_free(t[i]); + } + bn_free(_k); + ep2_free(u); + ep2_free(v); + } +} + +#endif /* EP_PLAIN || EP_SUPER */ +#endif /* EP_MUL == LWREG */ + /*============================================================================*/ /* Public definitions */ /*============================================================================*/ @@ -380,7 +570,7 @@ void ep2_mul_lwnaf(ep2_t r, const ep2_t p, const bn_t k) { #if defined(EP_ENDOM) if (ep_curve_is_endom()) { - ep2_mul_glv_imp(r, p, k); + ep2_mul_gls_imp(r, p, k); return; } #endif @@ -392,6 +582,28 @@ void ep2_mul_lwnaf(ep2_t r, const ep2_t p, const bn_t k) { #endif +#if EP_MUL == LWREG || !defined(STRIP) + +void ep2_mul_lwreg(ep2_t r, const ep2_t p, const bn_t k) { + if (bn_is_zero(k) || ep2_is_infty(p)) { + ep2_set_infty(r); + return; + } + +#if defined(EP_ENDOM) + if (ep_curve_is_endom()) { + ep2_mul_reg_gls(r, p, k); + return; + } +#endif + +#if defined(EP_PLAIN) || defined(EP_SUPER) + ep2_mul_reg_imp(r, p, k); +#endif +} + +#endif + void ep2_mul_gen(ep2_t r, const bn_t k) { if (bn_is_zero(k)) { ep2_set_infty(r); diff --git a/src/epx/relic_ep2_norm.c b/src/epx/relic_ep2_norm.c index 3276649d3..d623c8c35 100644 --- a/src/epx/relic_ep2_norm.c +++ b/src/epx/relic_ep2_norm.c @@ -43,36 +43,45 @@ * * @param r - the result. * @param p - the point to normalize. + * @param inv - the flag to indicate if z is already inverted. */ -static void ep2_norm_imp(ep2_t r, const ep2_t p, int inverted) { +static void ep2_norm_imp(ep2_t r, const ep2_t p, int inv) { if (p->coord != BASIC) { - fp2_t t0, t1; + fp2_t t; - fp2_null(t0); - fp2_null(t1); + fp2_null(t); RLC_TRY { + fp2_new(t); - fp2_new(t0); - fp2_new(t1); - - if (inverted) { - fp2_copy(t1, p->z); + if (inv) { + fp2_copy(r->z, p->z); } else { - fp2_inv(t1, p->z); + fp2_inv(r->z, p->z); + } + + switch (p->coord) { + case PROJC: + fp2_mul(r->x, p->x, r->z); + fp2_mul(r->y, p->y, r->z); + break; + case JACOB: + fp2_sqr(t, r->z); + fp2_mul(r->x, p->x, t); + fp2_mul(t, t, r->z); + fp2_mul(r->y, p->y, t); + break; + default: + ep2_copy(r, p); + break; } - fp2_sqr(t0, t1); - fp2_mul(r->x, p->x, t0); - fp2_mul(t0, t0, t1); - fp2_mul(r->y, p->y, t0); fp2_set_dig(r->z, 1); } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); } RLC_FINALLY { - fp2_free(t0); - fp2_free(t1); + fp2_free(t); } } @@ -92,17 +101,18 @@ void ep2_norm(ep2_t r, const ep2_t p) { } if (p->coord == BASIC) { - /* If the point is represented in affine coordinates, we just copy it. */ + /* If the point is represented in affine coordinates, just copy it. */ ep2_copy(r, p); + return; } #if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) ep2_norm_imp(r, p, 0); -#endif +#endif /* EP_ADD == PROJC */ } void ep2_norm_sim(ep2_t *r, const ep2_t *t, int n) { int i; - fp2_t *a = RLC_ALLOCA(fp2_t, n); + fp2_t* a = RLC_ALLOCA(fp2_t, n); RLC_TRY { if (a == NULL) { @@ -114,19 +124,20 @@ void ep2_norm_sim(ep2_t *r, const ep2_t *t, int n) { fp2_copy(a[i], t[i]->z); } - fp2_inv_sim(a, a, n); + fp2_inv_sim(a, (const fp2_t *)a, n); for (i = 0; i < n; i++) { fp2_copy(r[i]->x, t[i]->x); fp2_copy(r[i]->y, t[i]->y); - fp2_copy(r[i]->z, a[i]); + if (!ep2_is_infty(t[i])) { + fp2_copy(r[i]->z, a[i]); + } } - #if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) for (i = 0; i < n; i++) { ep2_norm_imp(r[i], r[i], 1); } -#endif +#endif /* EP_ADD == PROJC */ } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); diff --git a/src/tmpl/relic_ep_add_tmpl.h b/src/tmpl/relic_ep_add_tmpl.h index 218ff53cd..c847eb554 100644 --- a/src/tmpl/relic_ep_add_tmpl.h +++ b/src/tmpl/relic_ep_add_tmpl.h @@ -160,17 +160,17 @@ F##_add(t4, t4, p->y); \ F##_mul(r->y, q->x, p->z); \ F##_add(r->y, r->y, p->x); \ - C##_curve_mul_b(t2, p->z); \ - F##_dbl(t5, t2); \ - F##_add(t2, t2, t5); \ + F##_dbl(t2, p->z); \ + F##_add(t2, t2, p->z); \ + C##_curve_mul_b(t2, t2); \ F##_add(r->z, t1, t2); \ F##_sub(t1, t1, t2); \ } \ F##_dbl(r->x, t0); \ F##_add(t0, t0, r->x); \ - C##_curve_mul_b(r->y, r->y); \ F##_dbl(t5, r->y); \ F##_add(r->y, r->y, t5); \ + C##_curve_mul_b(r->y, r->y); \ F##_mul(r->x, t4, r->y); \ F##_mul(t2, t3, t1); \ F##_sub(r->x, t2, r->x); \ @@ -348,13 +348,13 @@ F##_dbl(r->x, t0); \ F##_add(t0, t0, r->x); \ F##_dbl(t5, t2); \ - F##_add(t5, t5, t2); \ - C##_curve_mul_b(t2, t5); \ + F##_add(t2, t2, t5); \ + C##_curve_mul_b(t2, t2); \ F##_add(r->z, t1, t2); \ F##_sub(t1, t1, t2); \ F##_dbl(t5, r->y); \ - F##_add(t5, t5, r->y); \ - C##_curve_mul_b(r->y, t5); \ + F##_add(r->y, r->y, t5); \ + C##_curve_mul_b(r->y, r->y); \ F##_mul(r->x, t4, r->y); \ F##_mul(t2, t3, t1); \ F##_sub(r->x, t2, r->x); \ diff --git a/test/test_epx.c b/test/test_epx.c index d975bfdfc..612e484ad 100644 --- a/test/test_epx.c +++ b/test/test_epx.c @@ -219,9 +219,9 @@ static int addition2(void) { ep2_rand(a); ep2_set_infty(d); ep2_add(e, a, d); - TEST_ASSERT(ep2_cmp(e, a) == RLC_EQ, end); + TEST_ASSERT(ep2_cmp(a, e) == RLC_EQ, end); ep2_add(e, d, a); - TEST_ASSERT(ep2_cmp(e, a) == RLC_EQ, end); + TEST_ASSERT(ep2_cmp(a, e) == RLC_EQ, end); } TEST_END; TEST_CASE("point addition has inverse") { @@ -237,7 +237,7 @@ static int addition2(void) { ep2_rand(b); ep2_add(d, a, b); ep2_add_basic(e, a, b); - TEST_ASSERT(ep2_cmp(e, d) == RLC_EQ, end); + TEST_ASSERT(ep2_cmp(d, e) == RLC_EQ, end); } TEST_END; #endif @@ -281,6 +281,46 @@ static int addition2(void) { } TEST_END; #endif +#if EP_ADD == JACOB || !defined(STRIP) +#if !defined(EP_MIXED) || !defined(STRIP) + TEST_CASE("point addition in jacobian coordinates is correct") { + ep2_rand(a); + ep2_rand(b); + ep2_rand(c); + ep2_add_jacob(a, a, b); + ep2_add_jacob(b, b, c); + /* a and b in projective coordinates. */ + ep2_add_jacob(d, a, b); + ep2_norm(a, a); + ep2_norm(b, b); + ep2_add(e, a, b); + TEST_ASSERT(ep2_cmp(d, e) == RLC_EQ, end); + } TEST_END; +#endif + + TEST_CASE("point addition in mixed coordinates (z2 = 1) is correct") { + ep2_rand(a); + ep2_rand(b); + /* a in projective, b in affine coordinates. */ + ep2_add_jacob(a, a, b); + ep2_add_jacob(d, a, b); + /* a in affine coordinates. */ + ep2_norm(a, a); + ep2_add(e, a, b); + TEST_ASSERT(ep2_cmp(d, e) == RLC_EQ, end); + } TEST_END; + + TEST_CASE("point addition in mixed coordinates (z1,z2 = 1) is correct") { + ep2_rand(a); + ep2_rand(b); + ep2_norm(a, a); + ep2_norm(b, b); + /* a and b in affine coordinates. */ + ep2_add(d, a, b); + ep2_add_jacob(e, a, b); + TEST_ASSERT(ep2_cmp(d, e) == RLC_EQ, end); + } TEST_END; +#endif } RLC_CATCH_ANY { RLC_ERROR(end); @@ -567,6 +607,34 @@ static int multiplication2(void) { TEST_END; #endif +#if EP_MUL == LWREG || !defined(STRIP) + TEST_CASE("left-to-right regular point multiplication is correct") { + bn_zero(k); + ep2_mul_lwreg(r, p, k); + TEST_ASSERT(ep2_is_infty(r), end); + bn_set_dig(k, 1); + ep2_mul_lwreg(r, p, k); + TEST_ASSERT(ep2_cmp(p, r) == RLC_EQ, end); + ep2_rand(p); + ep2_mul_lwreg(r, p, n); + TEST_ASSERT(ep2_is_infty(r), end); + bn_rand_mod(k, n); + ep2_mul(q, p, k); + ep2_mul_lwreg(r, p, k); + TEST_ASSERT(ep2_cmp(q, r) == RLC_EQ, end); + bn_neg(k, k); + ep2_mul_lwreg(r, p, k); + ep2_neg(r, r); + TEST_ASSERT(ep2_cmp(q, r) == RLC_EQ, end); + bn_rand_mod(k, n); + ep2_mul_lwreg(q, p, k); + bn_add(k, k, n); + ep2_mul_lwreg(r, p, k); + TEST_ASSERT(ep2_cmp(q, r) == RLC_EQ, end); + } + TEST_END; +#endif + TEST_CASE("point multiplication by digit is correct") { ep2_mul_dig(r, p, 0); TEST_ASSERT(ep2_is_infty(r), end); From 088185279c2c6c5521bddd3a4018316d09bc5538 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Fri, 29 Mar 2024 21:45:56 +0100 Subject: [PATCH 12/37] Fix for more coords. --- src/epx/relic_ep2_util.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/epx/relic_ep2_util.c b/src/epx/relic_ep2_util.c index b61668ace..b49fdd4bc 100644 --- a/src/epx/relic_ep2_util.c +++ b/src/epx/relic_ep2_util.c @@ -89,13 +89,18 @@ void ep2_blind(ep2_t r, const ep2_t p) { #if EP_ADD == BASIC (void)rand; ep2_copy(r, p); -#else +#elif EP_ADD == PROJC + fp2_mul(r->x, p->x, rand); + fp2_mul(r->y, p->y, rand); + fp2_mul(r->z, p->z, rand); + r->coord = PROJC; +#elif EP_ADD == JACOB fp2_mul(r->z, p->z, rand); fp2_mul(r->y, p->y, rand); fp2_sqr(rand, rand); fp2_mul(r->x, r->x, rand); fp2_mul(r->y, r->y, rand); - r->coord = EP_ADD; + r->coord = JACOB; #endif } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); From 308412eda45d53c614b11c934e78efe72f17b7f7 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sat, 30 Mar 2024 11:24:09 +0100 Subject: [PATCH 13/37] Fix bug in BN curves. --- src/ep/relic_ep_mul.c | 4 ++-- src/epx/relic_ep2_mul.c | 16 +++++++++------- src/tmpl/relic_ep_map_tmpl.h | 37 ++++++++++++++++-------------------- 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/ep/relic_ep_mul.c b/src/ep/relic_ep_mul.c index 1e308d340..504c9492a 100644 --- a/src/ep/relic_ep_mul.c +++ b/src/ep/relic_ep_mul.c @@ -270,9 +270,9 @@ static void ep_mul_reg_glv(ep_t r, const ep_t p, const bn_t k) { ep_tab(t, q, RLC_WIDTH); l = RLC_FP_BITS + 1; - bn_rec_reg(reg0, &l, k0, bn_bits(n)/2, RLC_WIDTH); + bn_rec_reg(reg0, &l, k0, bn_bits(n) >> 1, RLC_WIDTH); l = RLC_FP_BITS + 1; - bn_rec_reg(reg1, &l, k1, bn_bits(n)/2, RLC_WIDTH); + bn_rec_reg(reg1, &l, k1, bn_bits(n) >> 1, RLC_WIDTH); #if defined(EP_MIXED) fp_set_dig(u->z, 1); diff --git a/src/epx/relic_ep2_mul.c b/src/epx/relic_ep2_mul.c index 188430626..327f06cdf 100644 --- a/src/epx/relic_ep2_mul.c +++ b/src/epx/relic_ep2_mul.c @@ -171,7 +171,7 @@ static void ep2_mul_naf_imp(ep2_t r, const ep2_t p, const bn_t k) { static void ep2_mul_reg_gls(ep2_t r, const ep2_t p, const bn_t k) { size_t l, _l[4]; bn_t n, _k[4], u; - int8_t reg[4][RLC_FP_BITS + 1], b[4], s[4], _s0, _s1; + int8_t reg[4][RLC_FP_BITS + 1], b[4], s[4], c0, c1; ep2_t q[4], t; bn_null(n); @@ -206,8 +206,10 @@ static void ep2_mul_reg_gls(ep2_t r, const ep2_t p, const bn_t k) { b[i] = bn_is_even(_k[i]); _k[i]->dp[0] |= b[i]; + /* Make some extra room for BN curves that grow subscalars by 1. */ + l = bn_bits(u) + (ep_curve_is_pairf() == EP_BN); _l[i] = RLC_FP_BITS + 1; - bn_rec_reg(reg[i], &_l[i], _k[i], bn_bits(u), 2); + bn_rec_reg(reg[i], &_l[i], _k[i], l, 2); l = RLC_MAX(l, _l[i]); } @@ -216,12 +218,12 @@ static void ep2_mul_reg_gls(ep2_t r, const ep2_t p, const bn_t k) { ep2_dbl(r, r); for (int i = 0; i < 4; i++) { - _s0 = reg[i][j] > 0; - _s1 = s[i] == RLC_POS; + c0 = reg[i][j] > 0; + c1 = s[i] == RLC_POS; ep2_neg(t, q[i]); - dv_copy_cond(t->y[0], q[i]->y[0], RLC_FP_DIGS, _s0 == _s1); - dv_copy_cond(t->y[1], q[i]->y[1], RLC_FP_DIGS, _s0 == _s1); + dv_copy_cond(t->y[0], q[i]->y[0], RLC_FP_DIGS, c0 == c1); + dv_copy_cond(t->y[1], q[i]->y[1], RLC_FP_DIGS, c0 == c1); ep2_add(r, r, t); } } @@ -293,7 +295,7 @@ static void ep2_mul_reg_imp(ep2_t r, const ep2_t p, const bn_t k) { bn_rec_reg(reg, &l, _k, n, RLC_WIDTH); #if defined(EP_MIXED) - fp_set_dig(u->z, 1); + fp2_set_dig(u->z, 1); u->coord = BASIC; #else u->coord = EP_ADD; diff --git a/src/tmpl/relic_ep_map_tmpl.h b/src/tmpl/relic_ep_map_tmpl.h index d5d06b40e..0a514c5aa 100644 --- a/src/tmpl/relic_ep_map_tmpl.h +++ b/src/tmpl/relic_ep_map_tmpl.h @@ -46,9 +46,9 @@ } \ } -/* TODO: remove the ugly hack due to lack of support for JACOB in ep2. */ /* conditionally normalize result of isogeny map when not using projective coords */ #if EP_ADD == JACOB + #define TMPL_MAP_ISOMAP_NORM(PFX) \ do { \ /* Y = Ny * Dx * Z^2. */ \ @@ -62,22 +62,11 @@ PFX##_mul(q->x, t0, t2); \ PFX##_mul(q->x, q->x, q->z); \ q->coord = JACOB; \ - } while (0) + } while (0) \ + #elif EP_ADD == PROJC + #define TMPL_MAP_ISOMAP_NORM(PFX) \ - if (#PFX[2] == '2') { \ - /* Y = Ny * Dx * Z^2. */ \ - PFX##_mul(q->y, p->y, t1); \ - PFX##_mul(q->y, q->y, t3); \ - /* Z = Dx * Dy, t1 = Z^2. */ \ - PFX##_mul(q->z, t2, t3); \ - PFX##_sqr(t1, q->z); \ - PFX##_mul(q->y, q->y, t1); \ - /* X = Nx * Dy * Z. */ \ - PFX##_mul(q->x, t0, t2); \ - PFX##_mul(q->x, q->x, q->z); \ - q->coord = PROJC; \ - } else { \ /* Z = Dx * Dy. */ \ PFX##_mul(q->z, t2, t3); \ /* X = Nx * Dy. */ \ @@ -86,8 +75,9 @@ PFX##_mul(q->y, p->y, t1); \ PFX##_mul(q->y, q->y, t3); \ q->coord = PROJC; \ - } + #else + #define TMPL_MAP_ISOMAP_NORM(PFX) \ do { \ /* when working with affine coordinates, clear denominator */ \ @@ -103,7 +93,8 @@ /* z coord == 1 */ \ PFX##_set_dig(q->z, 1); \ q->coord = BASIC; \ - } while (0) + } while (0) \ + #endif /** @@ -153,7 +144,7 @@ PFX##_free(t2); \ PFX##_free(t3); \ } \ - } + } \ /* Conditionally call isogeny mapping function depending on whether EP_CTMAP is defined */ #ifdef EP_CTMAP @@ -162,9 +153,12 @@ if (CUR##_curve_is_ctmap()) { \ CUR##_iso(PT, PT); \ } \ - } while (0) + } while (0) \ + #else + #define TMPL_MAP_CALL_ISOMAP(CUR, PT) /* No isogeny map call in this case. */ + #endif /** @@ -241,7 +235,7 @@ PFX##_free(t2); \ PFX##_free(t3); \ } \ - } + } \ /** * Shallue--van de Woestijne map, based on the definition from @@ -326,4 +320,5 @@ PFX##_free(t3); \ PFX##_free(t4); \ } \ - } + } \ + From eb8d511db101d4aaa9028cafa786866e27f40c66 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sat, 30 Mar 2024 12:34:22 +0100 Subject: [PATCH 14/37] Simplify ep_mul_lwreg. --- src/ep/relic_ep_mul.c | 107 +++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 59 deletions(-) diff --git a/src/ep/relic_ep_mul.c b/src/ep/relic_ep_mul.c index 504c9492a..7f8fa0612 100644 --- a/src/ep/relic_ep_mul.c +++ b/src/ep/relic_ep_mul.c @@ -212,36 +212,31 @@ static void ep_mul_naf_imp(ep_t r, const ep_t p, const bn_t k) { #if defined(EP_ENDOM) static void ep_mul_reg_glv(ep_t r, const ep_t p, const bn_t k) { - int i, j, n0, n1, s0, s1, b0, b1; - int8_t _s0, _s1, reg0[RLC_FP_BITS + 1], reg1[RLC_FP_BITS + 1]; - bn_t n, _k, k0, k1, v1[3], v2[3]; - ep_t q, t[1 << (RLC_WIDTH - 2)], u, v, w; + int8_t reg[2][RLC_FP_BITS + 1], s[2], b[2], c0, c1, n0, n1; + bn_t n, _k[2], v1[3], v2[3]; + ep_t q, t[1 << (RLC_WIDTH - 2)], u, w; size_t l; bn_null(n); - bn_null(_k); - bn_null(k0); - bn_null(k1); + bn_null(_k[0]); + bn_null(_k[1]); ep_null(q); ep_null(u); - ep_null(v); ep_null(w); RLC_TRY { bn_new(n); - bn_new(_k); - bn_new(k0); - bn_new(k1); + bn_new(_k[0]); + bn_new(_k[1]); ep_new(q); ep_new(u); - ep_new(v); ep_new(w); - for (i = 0; i < (1 << (RLC_WIDTH - 2)); i++) { + for (size_t i = 0; i < (1 << (RLC_WIDTH - 2)); i++) { ep_null(t[i]); ep_new(t[i]); } - for (i = 0; i < 3; i++) { + for (size_t i = 0; i < 3; i++) { bn_null(v1[i]); bn_null(v2[i]); bn_new(v1[i]); @@ -252,27 +247,25 @@ static void ep_mul_reg_glv(ep_t r, const ep_t p, const bn_t k) { ep_curve_get_v1(v1); ep_curve_get_v2(v2); - bn_mod(_k, k, n); + bn_mod(_k[0], 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); - b0 = bn_is_even(k0); - b1 = bn_is_even(k1); - k0->dp[0] |= b0; - k1->dp[0] |= b1; - - ep_copy(q, p); - ep_neg(t[0], p); - dv_copy_cond(q->y, t[0]->y, RLC_FP_DIGS, s0 != RLC_POS); + bn_rec_glv(_k[0], _k[1], _k[0], n, (const bn_t *)v1, (const bn_t *)v2); + for (size_t i = 0; i < 2; i++) { + s[i] = bn_sign(_k[i]); + bn_abs(_k[i], _k[i]); + b[i] = bn_is_even(_k[i]); + _k[i]->dp[0] |= b[i]; + } + + ep_norm(t[0], p); + ep_neg(q, t[0]); + dv_copy_cond(q->y, t[0]->y, RLC_FP_DIGS, s[0] == RLC_POS); ep_tab(t, q, RLC_WIDTH); l = RLC_FP_BITS + 1; - bn_rec_reg(reg0, &l, k0, bn_bits(n) >> 1, RLC_WIDTH); + bn_rec_reg(reg[0], &l, _k[0], bn_bits(n) >> 1, RLC_WIDTH); l = RLC_FP_BITS + 1; - bn_rec_reg(reg1, &l, k1, bn_bits(n) >> 1, RLC_WIDTH); + bn_rec_reg(reg[1], &l, _k[1], bn_bits(n) >> 1, RLC_WIDTH); #if defined(EP_MIXED) fp_set_dig(u->z, 1); @@ -282,19 +275,19 @@ static void ep_mul_reg_glv(ep_t r, const ep_t p, const bn_t k) { u->coord = w->coord = EP_ADD; #endif ep_set_infty(r); - for (i = l - 1; i >= 0; i--) { - for (j = 0; j < RLC_WIDTH - 1; j++) { + for (int i = l - 1; i >= 0; i--) { + for (size_t j = 0; j < RLC_WIDTH - 1; j++) { ep_dbl(r, r); } - n0 = reg0[i]; - _s0 = (n0 >> 7); - n0 = ((n0 ^ _s0) - _s0) >> 1; - n1 = reg1[i]; - _s1 = (n1 >> 7); - n1 = ((n1 ^ _s1) - _s1) >> 1; + n0 = reg[0][i]; + c0 = (n0 >> 7); + n0 = ((n0 ^ c0) - c0) >> 1; + n1 = reg[1][i]; + c1 = (n1 >> 7); + n1 = ((n1 ^ c1) - c1) >> 1; - for (j = 0; j < (1 << (RLC_WIDTH - 2)); j++) { + for (size_t j = 0; j < (1 << (RLC_WIDTH - 2)); j++) { dv_copy_cond(u->x, t[j]->x, RLC_FP_DIGS, j == n0); dv_copy_cond(w->x, t[j]->x, RLC_FP_DIGS, j == n1); dv_copy_cond(u->y, t[j]->y, RLC_FP_DIGS, j == n0); @@ -304,31 +297,29 @@ static void ep_mul_reg_glv(ep_t r, const ep_t p, const bn_t k) { dv_copy_cond(w->z, t[j]->z, RLC_FP_DIGS, j == n1); #endif } - ep_neg(v, u); - dv_copy_cond(u->y, v->y, RLC_FP_DIGS, _s0 != 0); - ep_add(r, r, u); + ep_neg(q, u); + dv_copy_cond(q->y, u->y, RLC_FP_DIGS, c0 == 0); + ep_add(r, r, q); ep_psi(w, w); ep_neg(q, w); - dv_copy_cond(w->y, q->y, RLC_FP_DIGS, s0 != s1); - ep_neg(q, w); - dv_copy_cond(w->y, q->y, RLC_FP_DIGS, _s1 != 0); + dv_copy_cond(w->y, q->y, RLC_FP_DIGS, (c1 != 0) ^ (s[0] != s[1])); ep_add(r, r, w); } /* t[0] has an unmodified copy of p. */ ep_sub(u, r, t[0]); - dv_copy_cond(r->x, u->x, RLC_FP_DIGS, b0); - dv_copy_cond(r->y, u->y, RLC_FP_DIGS, b0); - dv_copy_cond(r->z, u->z, RLC_FP_DIGS, b0); + dv_copy_cond(r->x, u->x, RLC_FP_DIGS, b[0]); + dv_copy_cond(r->y, u->y, RLC_FP_DIGS, b[0]); + dv_copy_cond(r->z, u->z, RLC_FP_DIGS, b[0]); ep_psi(w, t[0]); ep_neg(q, w); - dv_copy_cond(w->y, q->y, RLC_FP_DIGS, s0 != s1); - ep_sub(u, r, w); - dv_copy_cond(r->x, u->x, RLC_FP_DIGS, b1); - dv_copy_cond(r->y, u->y, RLC_FP_DIGS, b1); - dv_copy_cond(r->z, u->z, RLC_FP_DIGS, b1); + dv_copy_cond(q->y, w->y, RLC_FP_DIGS, s[0] == s[1]); + ep_sub(u, r, q); + dv_copy_cond(r->x, u->x, RLC_FP_DIGS, b[1]); + dv_copy_cond(r->y, u->y, RLC_FP_DIGS, b[1]); + dv_copy_cond(r->z, u->z, RLC_FP_DIGS, b[1]); /* Convert r to affine coordinates. */ ep_norm(r, r); @@ -338,18 +329,16 @@ static void ep_mul_reg_glv(ep_t r, const ep_t p, const bn_t k) { } RLC_FINALLY { bn_free(n); - bn_free(_k); - bn_free(k0); - bn_free(k1); + bn_free(_k[0]); + bn_free(_k[1]); bn_free(n); ep_free(q); ep_free(u); - ep_free(v); ep_free(w); - for (i = 0; i < 1 << (RLC_WIDTH - 2); i++) { + for (size_t i = 0; i < 1 << (RLC_WIDTH - 2); i++) { ep_free(t[i]); } - for (i = 0; i < 3; i++) { + for (size_t i = 0; i < 3; i++) { bn_free(v1[i]); bn_free(v2[i]); } From 69d7dc6193f99e78a4550ffa26891a9e7b976cad Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sat, 30 Mar 2024 13:11:58 +0100 Subject: [PATCH 15/37] Make LWREG use table. --- include/relic_epx.h | 4 +- src/epx/relic_ep2_mul.c | 108 ++++++++++++++++++++++++++-------------- 2 files changed, 74 insertions(+), 38 deletions(-) diff --git a/include/relic_epx.h b/include/relic_epx.h index 56359c175..3ce5d9ac0 100644 --- a/include/relic_epx.h +++ b/include/relic_epx.h @@ -477,7 +477,9 @@ typedef iso2_st *iso2_t; #define ep2_mul(R, P, K) ep2_mul_slide(R, P, K) #elif EP_MUL == MONTY #define ep2_mul(R, P, K) ep2_mul_monty(R, P, K) -#elif EP_MUL == LWNAF || EP_MUL == LWREG +#elif EP_MUL == LWNAF +#define ep2_mul(R, P, K) ep2_mul_lwnaf(R, P, K) +#elif EP_MUL == LWREG #define ep2_mul(R, P, K) ep2_mul_lwnaf(R, P, K) #endif diff --git a/src/epx/relic_ep2_mul.c b/src/epx/relic_ep2_mul.c index 327f06cdf..bd86cf790 100644 --- a/src/epx/relic_ep2_mul.c +++ b/src/epx/relic_ep2_mul.c @@ -169,76 +169,107 @@ static void ep2_mul_naf_imp(ep2_t r, const ep2_t p, const bn_t k) { #if defined(EP_ENDOM) static void ep2_mul_reg_gls(ep2_t r, const ep2_t p, const bn_t k) { - size_t l, _l[4]; + int8_t reg[4][RLC_FP_BITS + 1], b[4], s[4], c0, n0; bn_t n, _k[4], u; - int8_t reg[4][RLC_FP_BITS + 1], b[4], s[4], c0, c1; - ep2_t q[4], t; + ep2_t q, w, t[4][1 << (RLC_WIDTH - 2)]; + size_t l, len, _l[4]; bn_null(n); bn_null(u); - ep2_null(t); + ep2_null(q); + ep2_null(w); RLC_TRY { bn_new(n); bn_new(u); - for (int i = 0; i < 4; i++) { + ep2_new(q); + ep2_new(w); + for (size_t i = 0; i < 4; i++) { bn_null(_k[i]); - ep2_null(q[i]); bn_new(_k[i]); - ep2_new(q[i]); + for (size_t j = 0; j < (1 << (RLC_WIDTH - 2)); j++) { + ep2_null(t[i][j]); + ep2_new(t[i][j]); + } } - ep2_new(t); 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 (int i = 0; i < 4; i++) { + len = bn_bits(u) + (ep_curve_is_pairf() == EP_BN); + ep2_norm(t[0][0], p); + for (size_t i = 0; i < 4; i++) { s[i] = bn_sign(_k[i]); bn_abs(_k[i], _k[i]); b[i] = bn_is_even(_k[i]); _k[i]->dp[0] |= b[i]; /* Make some extra room for BN curves that grow subscalars by 1. */ - l = bn_bits(u) + (ep_curve_is_pairf() == EP_BN); _l[i] = RLC_FP_BITS + 1; - bn_rec_reg(reg[i], &_l[i], _k[i], l, 2); + bn_rec_reg(reg[i], &_l[i], _k[i], len, RLC_WIDTH); l = RLC_MAX(l, _l[i]); + + /* Apply Frobenius before flipping sign to build table. */ + if (i > 0) { + ep2_frb(t[i][0], t[i - 1][0], 1); + } } + for (size_t i = 0; i < 4; i++) { + ep2_neg(q, t[i][0]); + dv_copy_cond(q->y[0], t[i][0]->y[0], RLC_FP_DIGS, s[i] == RLC_POS); + dv_copy_cond(q->y[1], t[i][0]->y[1], RLC_FP_DIGS, s[i] == RLC_POS); + ep2_tab(t[i], q, RLC_WIDTH); + } + +#if defined(EP_MIXED) + fp2_set_dig(w->z, 1); + w->coord = BASIC; +#else + w->coord = = EP_ADD; +#endif + ep2_set_infty(r); for (int j = l - 1; j >= 0; j--) { - ep2_dbl(r, r); + for (size_t i = 0; i < RLC_WIDTH - 1; i++) { + ep2_dbl(r, r); + } - for (int i = 0; i < 4; i++) { - c0 = reg[i][j] > 0; - c1 = s[i] == RLC_POS; + for (size_t i = 0; i < 4; i++) { + n0 = reg[i][j]; + c0 = (n0 >> 7); + n0 = ((n0 ^ c0) - c0) >> 1; + + for (size_t m = 0; m < (1 << (RLC_WIDTH - 2)); m++) { + dv_copy_cond(w->x[0], t[i][m]->x[0], RLC_FP_DIGS, m == n0); + dv_copy_cond(w->x[1], t[i][m]->x[1], RLC_FP_DIGS, m == n0); + dv_copy_cond(w->y[0], t[i][m]->y[0], RLC_FP_DIGS, m == n0); + dv_copy_cond(w->y[1], t[i][m]->y[1], RLC_FP_DIGS, m == n0); + #if !defined(EP_MIXED) + dv_copy_cond(w->z[0], t[i][m]->z[0], RLC_FP_DIGS, m == n0); + dv_copy_cond(w->z[1], t[i][m]->z[1], RLC_FP_DIGS, m == n0); + #endif + } - ep2_neg(t, q[i]); - dv_copy_cond(t->y[0], q[i]->y[0], RLC_FP_DIGS, c0 == c1); - dv_copy_cond(t->y[1], q[i]->y[1], RLC_FP_DIGS, c0 == c1); - ep2_add(r, r, t); + ep2_neg(q, w); + dv_copy_cond(q->y[0], w->y[0], RLC_FP_DIGS, c0 == 0); + dv_copy_cond(q->y[1], w->y[1], RLC_FP_DIGS, c0 == 0); + ep2_add(r, r, q); } } - for (int i = 0; i < 4; i++) { - ep2_neg(t, q[i]); - dv_copy_cond(t->y[0], q[i]->y[0], RLC_FP_DIGS, s[i] == RLC_NEG); - dv_copy_cond(t->y[1], q[i]->y[1], RLC_FP_DIGS, s[i] == RLC_NEG); - ep2_add(t, r, t); - dv_copy_cond(r->x[0], t->x[0], RLC_FP_DIGS, b[i]); - dv_copy_cond(r->x[1], t->x[1], RLC_FP_DIGS, b[i]); - dv_copy_cond(r->y[0], t->y[0], RLC_FP_DIGS, b[i]); - dv_copy_cond(r->y[1], t->y[1], RLC_FP_DIGS, b[i]); - dv_copy_cond(r->z[0], t->z[0], RLC_FP_DIGS, b[i]); - dv_copy_cond(r->z[1], t->z[1], RLC_FP_DIGS, b[i]); + for (size_t i = 0; i < 4; i++) { + /* Tables are built with points already negated, so no need here. */ + ep2_sub(q, r, t[i][0]); + dv_copy_cond(r->x[0], q->x[0], RLC_FP_DIGS, b[i]); + dv_copy_cond(r->x[1], q->x[1], RLC_FP_DIGS, b[i]); + dv_copy_cond(r->y[0], q->y[0], RLC_FP_DIGS, b[i]); + dv_copy_cond(r->y[1], q->y[1], RLC_FP_DIGS, b[i]); + dv_copy_cond(r->z[0], q->z[0], RLC_FP_DIGS, b[i]); + dv_copy_cond(r->z[1], q->z[1], RLC_FP_DIGS, b[i]); } /* Convert r to affine coordinates. */ @@ -250,11 +281,14 @@ static void ep2_mul_reg_gls(ep2_t r, const ep2_t p, const bn_t k) { RLC_FINALLY { bn_free(n); bn_free(u); + ep2_free(q); + ep2_free(w); for (int i = 0; i < 4; i++) { bn_free(_k[i]); - ep2_free(q[i]); + for (size_t j = 0; j < (1 << (RLC_WIDTH - 2)); j++) { + ep2_free(t[i][j]); + } } - ep2_free(t); } } From 1e72f297e45eb77d54a71636e848af2af037b057 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sat, 30 Mar 2024 13:14:46 +0100 Subject: [PATCH 16/37] Fix GH Actions build. --- .github/workflows/gmp-sec.yml | 1 + .github/workflows/gmp.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/gmp-sec.yml b/.github/workflows/gmp-sec.yml index 61fd18203..5a2ad6c1a 100644 --- a/.github/workflows/gmp-sec.yml +++ b/.github/workflows/gmp-sec.yml @@ -61,6 +61,7 @@ jobs: gcc cmake gmp + gmp-devel update: true - name: Run CMake (MingW) diff --git a/.github/workflows/gmp.yml b/.github/workflows/gmp.yml index 03cbf8d5a..40d036ecf 100644 --- a/.github/workflows/gmp.yml +++ b/.github/workflows/gmp.yml @@ -61,6 +61,7 @@ jobs: gcc cmake gmp + gmp-devel update: true - name: Run CMake (MingW) From a5c1c9e3078db17c2a4fd36342c7463b421492c3 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sat, 30 Mar 2024 13:16:58 +0100 Subject: [PATCH 17/37] Configuration fix. --- include/relic_epx.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/relic_epx.h b/include/relic_epx.h index 3ce5d9ac0..bd50e7eec 100644 --- a/include/relic_epx.h +++ b/include/relic_epx.h @@ -480,7 +480,7 @@ typedef iso2_st *iso2_t; #elif EP_MUL == LWNAF #define ep2_mul(R, P, K) ep2_mul_lwnaf(R, P, K) #elif EP_MUL == LWREG -#define ep2_mul(R, P, K) ep2_mul_lwnaf(R, P, K) +#define ep2_mul(R, P, K) ep2_mul_lwreg(R, P, K) #endif /** From 7e86e219521fd24964686aa1385466220352d28a Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sat, 30 Mar 2024 15:48:22 +0100 Subject: [PATCH 18/37] API refacor for constant-time functions. --- bench/bench_dv.c | 12 +- bench/bench_fp.c | 12 ++ bench/bench_fpx.c | 144 +++++++++++++++++++++++ demo/ers-etrs/test-bench | Bin 0 -> 842656 bytes include/relic_dv.h | 10 +- include/relic_ep.h | 2 +- include/relic_fp.h | 9 ++ include/relic_fpx.h | 111 +++++++++++++++++- include/relic_label.h | 52 +++++++-- include/relic_pc.h | 20 +++- include/relic_util.h | 2 +- src/bn/relic_bn_mxp.c | 4 +- src/cp/relic_cp_bls.c | 2 +- src/cp/relic_cp_ecdsa.c | 2 +- src/cp/relic_cp_ecies.c | 2 +- src/cp/relic_cp_ecss.c | 2 +- src/cp/relic_cp_mklhs.c | 2 +- src/cp/relic_cp_rsa.c | 8 +- src/dv/relic_dv_util.c | 10 +- src/eb/relic_eb_mul.c | 10 +- src/ed/relic_ed_map.c | 16 +-- src/ed/relic_ed_mul.c | 42 +++---- src/ep/relic_ep_map.c | 30 ++--- src/ep/relic_ep_mul.c | 64 +++++------ src/epx/relic_ep2_map.c | 27 +++-- src/epx/relic_ep2_mul.c | 91 ++++++--------- src/epx/relic_ep3_map.c | 14 +-- src/epx/relic_ep3_mul.c | 38 +++--- src/epx/relic_ep4_map.c | 56 ++++----- src/epx/relic_ep4_mul.c | 50 ++++---- src/epx/relic_ep8_map.c | 10 +- src/epx/relic_ep8_mul.c | 14 +-- src/fb/relic_fb_cmp.c | 2 +- src/fb/relic_fb_exp.c | 4 +- src/fp/relic_fp_crt.c | 2 +- src/fp/relic_fp_exp.c | 4 +- src/fp/relic_fp_inv.c | 38 +++--- src/fp/relic_fp_smb.c | 12 +- src/fp/relic_fp_srt.c | 8 +- src/fp/relic_fp_util.c | 4 + src/fpx/relic_fpx_cyc.c | 216 +++++++---------------------------- src/fpx/relic_fpx_srt.c | 31 ++--- src/fpx/relic_fpx_util.c | 65 +++++++++++ src/pc/relic_pc_exp.c | 189 ++++++++++++++++++++++++++++++ src/relic_util.c | 2 +- src/tmpl/relic_ep_map_tmpl.h | 24 ++-- test/test_dv.c | 34 +++--- test/test_fp.c | 10 +- test/test_pc.c | 13 ++- 49 files changed, 971 insertions(+), 555 deletions(-) create mode 100755 demo/ers-etrs/test-bench diff --git a/bench/bench_dv.c b/bench/bench_dv.c index 7f1584619..62670d21c 100644 --- a/bench/bench_dv.c +++ b/bench/bench_dv.c @@ -67,16 +67,16 @@ static void copy(void) { BENCH_ADD(dv_copy(a, b, RLC_DV_DIGS)); } BENCH_END; - BENCH_RUN("dv_copy_cond") { + BENCH_RUN("dv_copy_sec") { rand_bytes((uint8_t *)a, RLC_DV_DIGS * sizeof(dig_t)); rand_bytes((uint8_t *)b, RLC_DV_DIGS * sizeof(dig_t)); - BENCH_ADD(dv_copy_cond(a, b, RLC_DV_DIGS, 1)); + BENCH_ADD(dv_copy_sec(a, b, RLC_DV_DIGS, 1)); } BENCH_END; - BENCH_RUN("dv_swap_cond") { + BENCH_RUN("dv_swap_sec") { rand_bytes((uint8_t *)a, RLC_DV_DIGS * sizeof(dig_t)); rand_bytes((uint8_t *)b, RLC_DV_DIGS * sizeof(dig_t)); - BENCH_ADD(dv_swap_cond(a, b, RLC_DV_DIGS, 1)); + BENCH_ADD(dv_swap_sec(a, b, RLC_DV_DIGS, 1)); } BENCH_END; BENCH_RUN("dv_cmp") { @@ -85,10 +85,10 @@ static void copy(void) { BENCH_ADD(dv_cmp(a, b, RLC_DV_DIGS)); } BENCH_END; - BENCH_RUN("dv_cmp_const") { + BENCH_RUN("dv_cmp_sec") { rand_bytes((uint8_t *)a, RLC_DV_DIGS * sizeof(dig_t)); rand_bytes((uint8_t *)b, RLC_DV_DIGS * sizeof(dig_t)); - BENCH_ADD(dv_cmp_const(a, b, RLC_DV_DIGS)); + BENCH_ADD(dv_cmp_sec(a, b, RLC_DV_DIGS)); } BENCH_END; dv_free(a); diff --git a/bench/bench_fp.c b/bench/bench_fp.c index ea3f576c2..0d8ae5254 100644 --- a/bench/bench_fp.c +++ b/bench/bench_fp.c @@ -69,6 +69,18 @@ static void util(void) { } BENCH_END; + BENCH_RUN("fp_copy_sec (0)") { + fp_rand(a); + BENCH_ADD(fp_copy_sec(b, a, 0)); + } + BENCH_END; + + BENCH_RUN("fp_copy_sec (1)") { + fp_rand(a); + BENCH_ADD(fp_copy_sec(b, a, 1)); + } + BENCH_END; + BENCH_RUN("fp_zero") { fp_rand(a); BENCH_ADD(fp_zero(a)); diff --git a/bench/bench_fpx.c b/bench/bench_fpx.c index 818aa8b14..f5782659d 100644 --- a/bench/bench_fpx.c +++ b/bench/bench_fpx.c @@ -66,6 +66,18 @@ static void util2(void) { } BENCH_END; + BENCH_RUN("fp2_copy_sec (0)") { + fp2_rand(a); + BENCH_ADD(fp2_copy_sec(b, a, 0)); + } + BENCH_END; + + BENCH_RUN("fp2_copy_sec (1)") { + fp2_rand(a); + BENCH_ADD(fp2_copy_sec(b, a, 1)); + } + BENCH_END; + BENCH_RUN("fp2_neg") { fp2_rand(a); BENCH_ADD(fp2_neg(b, a)); @@ -453,6 +465,18 @@ static void util3(void) { } BENCH_END; + BENCH_RUN("fp3_copy_sec (0)") { + fp3_rand(a); + BENCH_ADD(fp3_copy_sec(b, a, 0)); + } + BENCH_END; + + BENCH_RUN("fp3_copy_sec (1)") { + fp3_rand(a); + BENCH_ADD(fp3_copy_sec(b, a, 1)); + } + BENCH_END; + BENCH_RUN("fp3_neg") { fp3_rand(a); BENCH_ADD(fp3_neg(b, a)); @@ -753,6 +777,18 @@ static void util4(void) { } BENCH_END; + BENCH_RUN("fp4_copy_sec (0)") { + fp4_rand(a); + BENCH_ADD(fp4_copy_sec(b, a, 0)); + } + BENCH_END; + + BENCH_RUN("fp4_copy_sec (1)") { + fp4_rand(a); + BENCH_ADD(fp4_copy_sec(b, a, 1)); + } + BENCH_END; + BENCH_RUN("fp4_neg") { fp4_rand(a); BENCH_ADD(fp4_neg(b, a)); @@ -979,6 +1015,18 @@ static void util6(void) { } BENCH_END; + BENCH_RUN("fp6_copy_sec (0)") { + fp6_rand(a); + BENCH_ADD(fp6_copy_sec(b, a, 0)); + } + BENCH_END; + + BENCH_RUN("fp6_copy_sec (1)") { + fp6_rand(a); + BENCH_ADD(fp6_copy_sec(b, a, 1)); + } + BENCH_END; + BENCH_RUN("fp6_neg") { fp6_rand(a); BENCH_ADD(fp6_neg(b, a)); @@ -1191,6 +1239,18 @@ static void util8(void) { } BENCH_END; + BENCH_RUN("fp8_copy_sec (0)") { + fp8_rand(a); + BENCH_ADD(fp8_copy_sec(b, a, 0)); + } + BENCH_END; + + BENCH_RUN("fp8_copy_sec (1)") { + fp8_rand(a); + BENCH_ADD(fp8_copy_sec(b, a, 1)); + } + BENCH_END; + BENCH_RUN("fp8_neg") { fp8_rand(a); BENCH_ADD(fp8_neg(b, a)); @@ -1483,6 +1543,18 @@ static void util9(void) { } BENCH_END; + BENCH_RUN("fp9_copy_sec (0)") { + fp9_rand(a); + BENCH_ADD(fp9_copy_sec(b, a, 0)); + } + BENCH_END; + + BENCH_RUN("fp9_copy_sec (1)") { + fp9_rand(a); + BENCH_ADD(fp9_copy_sec(b, a, 1)); + } + BENCH_END; + BENCH_RUN("fp9_neg") { fp9_rand(a); BENCH_ADD(fp9_neg(b, a)); @@ -1695,6 +1767,18 @@ static void util12(void) { } BENCH_END; + BENCH_RUN("fp12_copy_sec (0)") { + fp12_rand(a); + BENCH_ADD(fp12_copy_sec(b, a, 0)); + } + BENCH_END; + + BENCH_RUN("fp12_copy_sec (1)") { + fp12_rand(a); + BENCH_ADD(fp12_copy_sec(b, a, 1)); + } + BENCH_END; + BENCH_RUN("fp12_neg") { fp12_rand(a); BENCH_ADD(fp12_neg(b, a)); @@ -2093,6 +2177,18 @@ static void util16(void) { } BENCH_END; + BENCH_RUN("fp16_copy_sec (0)") { + fp16_rand(a); + BENCH_ADD(fp16_copy_sec(b, a, 0)); + } + BENCH_END; + + BENCH_RUN("fp16_copy_sec (1)") { + fp16_rand(a); + BENCH_ADD(fp16_copy_sec(b, a, 1)); + } + BENCH_END; + BENCH_RUN("fp16_neg") { fp16_rand(a); BENCH_ADD(fp16_neg(b, a)); @@ -2328,6 +2424,18 @@ static void util18(void) { } BENCH_END; + BENCH_RUN("fp18_copy_sec (0)") { + fp18_rand(a); + BENCH_ADD(fp18_copy_sec(b, a, 0)); + } + BENCH_END; + + BENCH_RUN("fp18_copy_sec (1)") { + fp18_rand(a); + BENCH_ADD(fp18_copy_sec(b, a, 1)); + } + BENCH_END; + BENCH_RUN("fp18_neg") { fp18_rand(a); BENCH_ADD(fp18_neg(b, a)); @@ -2712,6 +2820,18 @@ static void util24(void) { } BENCH_END; + BENCH_RUN("fp24_copy_sec (0)") { + fp24_rand(a); + BENCH_ADD(fp24_copy_sec(b, a, 0)); + } + BENCH_END; + + BENCH_RUN("fp24_copy_sec (1)") { + fp24_rand(a); + BENCH_ADD(fp24_copy_sec(b, a, 1)); + } + BENCH_END; + BENCH_RUN("fp24_neg") { fp24_rand(a); BENCH_ADD(fp24_neg(b, a)); @@ -3033,6 +3153,18 @@ static void util48(void) { } BENCH_END; + BENCH_RUN("fp12_copy_sec (0)") { + fp12_rand(a); + BENCH_ADD(fp12_copy_sec(b, a, 0)); + } + BENCH_END; + + BENCH_RUN("fp48_copy_sec (1)") { + fp48_rand(a); + BENCH_ADD(fp48_copy_sec(b, a, 1)); + } + BENCH_END; + BENCH_RUN("fp48_neg") { fp48_rand(a); BENCH_ADD(fp48_neg(b, a)); @@ -3399,6 +3531,18 @@ static void util54(void) { } BENCH_END; + BENCH_RUN("fp54_copy_sec (0)") { + fp54_rand(a); + BENCH_ADD(fp54_copy_sec(b, a, 0)); + } + BENCH_END; + + BENCH_RUN("fp54_copy_sec (1)") { + fp54_rand(a); + BENCH_ADD(fp54_copy_sec(b, a, 1)); + } + BENCH_END; + BENCH_RUN("fp54_neg") { fp54_rand(a); BENCH_ADD(fp54_neg(b, a)); diff --git a/demo/ers-etrs/test-bench b/demo/ers-etrs/test-bench new file mode 100755 index 0000000000000000000000000000000000000000..f412aa9ed55c7bbf766a76d45307375d01b34421 GIT binary patch literal 842656 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%0D(3c~b$SDloFRmVOi% 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 for (i = bn_bits(b) - 1; i >= 0; i--) { j = bn_get_bit(b, i); - dv_swap_cond(tab[0]->dp, tab[1]->dp, m->alloc, j ^ 1); + dv_swap_sec(tab[0]->dp, tab[1]->dp, m->alloc, j ^ 1); mask = -(j ^ 1); t = (tab[0]->used ^ tab[1]->used) & mask; tab[0]->used ^= t; @@ -277,7 +277,7 @@ void bn_mxp_monty(bn_t c, const bn_t a, const bn_t b, const bn_t m) { bn_mod(tab[0], tab[0], m, u); bn_sqr(tab[1], tab[1]); bn_mod(tab[1], tab[1], m, u); - dv_swap_cond(tab[0]->dp, tab[1]->dp, m->alloc, j ^ 1); + dv_swap_sec(tab[0]->dp, tab[1]->dp, m->alloc, j ^ 1); mask = -(j ^ 1); t = (tab[0]->used ^ tab[1]->used) & mask; tab[0]->used ^= t; diff --git a/src/cp/relic_cp_bls.c b/src/cp/relic_cp_bls.c index fac290e3e..6ddb347a9 100644 --- a/src/cp/relic_cp_bls.c +++ b/src/cp/relic_cp_bls.c @@ -66,7 +66,7 @@ int cp_bls_sig(g1_t s, const uint8_t *msg, size_t len, const bn_t d) { RLC_TRY { g1_new(p); g1_map(p, msg, len); - g1_mul_key(s, p, d); + g1_mul_sec(s, p, d); } RLC_CATCH_ANY { result = RLC_ERR; diff --git a/src/cp/relic_cp_ecdsa.c b/src/cp/relic_cp_ecdsa.c index 24657312d..34f0403dc 100644 --- a/src/cp/relic_cp_ecdsa.c +++ b/src/cp/relic_cp_ecdsa.c @@ -173,7 +173,7 @@ int cp_ecdsa_ver(const bn_t r, const bn_t s, const uint8_t *msg, size_t len, bn_mod(v, v, n); - cmp = dv_cmp_const(v->dp, r->dp, RLC_MIN(v->used, r->used)); + cmp = dv_cmp_sec(v->dp, r->dp, RLC_MIN(v->used, r->used)); result = (cmp == RLC_NE ? 0 : 1); if (v->used != r->used) { diff --git a/src/cp/relic_cp_ecies.c b/src/cp/relic_cp_ecies.c index da0a808f2..be64ac32d 100644 --- a/src/cp/relic_cp_ecies.c +++ b/src/cp/relic_cp_ecies.c @@ -138,7 +138,7 @@ int cp_ecies_dec(uint8_t *out, size_t *out_len, const ec_t r, const uint8_t *in, bn_write_bin(_x, l, x); md_kdf(key, 2 * size, _x, l); md_hmac(h, in, in_len - RLC_MD_LEN, key + size, size); - if (util_cmp_const(h, in + in_len - RLC_MD_LEN, RLC_MD_LEN)) { + if (util_cmp_sec(h, in + in_len - RLC_MD_LEN, RLC_MD_LEN)) { result = RLC_ERR; } else { if (bc_aes_cbc_dec(out, out_len, in, in_len - RLC_MD_LEN, diff --git a/src/cp/relic_cp_ecss.c b/src/cp/relic_cp_ecss.c index ac8c6cac8..7b1c1f776 100644 --- a/src/cp/relic_cp_ecss.c +++ b/src/cp/relic_cp_ecss.c @@ -166,7 +166,7 @@ int cp_ecss_ver(bn_t e, bn_t s, const uint8_t *msg, size_t len, const ec_t q) { bn_mod(ev, ev, n); - result = dv_cmp_const(ev->dp, e->dp, RLC_MIN(ev->used, + result = dv_cmp_sec(ev->dp, e->dp, RLC_MIN(ev->used, e->used)); result = (result == RLC_NE ? 0 : 1); diff --git a/src/cp/relic_cp_mklhs.c b/src/cp/relic_cp_mklhs.c index 96ef56890..2b070a716 100644 --- a/src/cp/relic_cp_mklhs.c +++ b/src/cp/relic_cp_mklhs.c @@ -88,7 +88,7 @@ int cp_mklhs_sig(g1_t s, const bn_t m, const char *data, const char *id, g1_map(a, str, strlen(id) + strlen(tag)); g1_add(s, s, a); g1_norm(s, s); - g1_mul_key(s, s, sk); + g1_mul_sec(s, s, sk); } RLC_CATCH_ANY { result = RLC_ERR; diff --git a/src/cp/relic_cp_rsa.c b/src/cp/relic_cp_rsa.c index c7ea1d67f..863f4d28e 100644 --- a/src/cp/relic_cp_rsa.c +++ b/src/cp/relic_cp_rsa.c @@ -938,7 +938,7 @@ int cp_rsa_ver(uint8_t *sig, size_t sig_len, const uint8_t *msg, size_t msg_len, memset(h1, 0, RLC_MD_LEN); bn_write_bin(h1, size - pad_len, eb); /* Everything went ok, so signature status is changed. */ - result = util_cmp_const(h1, h2, RLC_MD_LEN); + result = util_cmp_sec(h1, h2, RLC_MD_LEN); } else { memcpy(h1 + 8, msg, msg_len); md_map(h2, h1, RLC_MD_LEN + 8); @@ -947,7 +947,7 @@ int cp_rsa_ver(uint8_t *sig, size_t sig_len, const uint8_t *msg, size_t msg_len, bn_write_bin(h1, size - pad_len, eb); /* Everything went ok, so signature status is changed. */ - result = util_cmp_const(h1, h2, msg_len); + result = util_cmp_sec(h1, h2, msg_len); } #else memset(h1, 0, RLC_MAX(msg_len, RLC_MD_LEN)); @@ -956,10 +956,10 @@ int cp_rsa_ver(uint8_t *sig, size_t sig_len, const uint8_t *msg, size_t msg_len, if (!hash) { md_map(h2, msg, msg_len); /* Everything went ok, so signature status is changed. */ - result = util_cmp_const(h1, h2, RLC_MD_LEN); + result = util_cmp_sec(h1, h2, RLC_MD_LEN); } else { /* Everything went ok, so signature status is changed. */ - result = util_cmp_const(h1, msg, msg_len); + result = util_cmp_sec(h1, msg, msg_len); } #endif result = (result == RLC_EQ ? 1 : 0); diff --git a/src/dv/relic_dv_util.c b/src/dv/relic_dv_util.c index 5084472a1..8422b636a 100644 --- a/src/dv/relic_dv_util.c +++ b/src/dv/relic_dv_util.c @@ -70,20 +70,20 @@ void dv_copy(dig_t *c, const dig_t *a, size_t digits) { memcpy(c, a, digits * sizeof(dig_t)); } -void dv_copy_cond(dig_t *c, const dig_t *a, size_t digits, dig_t cond) { +void dv_copy_sec(dig_t *c, const dig_t *a, size_t digits, dig_t bit) { dig_t mask, t; - mask = -cond; + mask = -bit; for (size_t i = 0; i < digits; i++) { t = (a[i] ^ c[i]) & mask; c[i] ^= t; } } -void dv_swap_cond(dig_t *c, dig_t *a, size_t digits, dig_t cond) { +void dv_swap_sec(dig_t *c, dig_t *a, size_t digits, dig_t bit) { dig_t mask, t; - mask = -cond; + mask = -bit; for (size_t i = 0; i < digits; i++) { t = (a[i] ^ c[i]) & mask; a[i] ^= t; @@ -106,7 +106,7 @@ int dv_cmp(const dig_t *a, const dig_t *b, size_t size) { return r; } -int dv_cmp_const(const dig_t *a, const dig_t *b, size_t size) { +int dv_cmp_sec(const dig_t *a, const dig_t *b, size_t size) { dig_t r = 0; for (size_t i = 0; i < size; i++) { diff --git a/src/eb/relic_eb_mul.c b/src/eb/relic_eb_mul.c index b23b81141..eaef0d16c 100644 --- a/src/eb/relic_eb_mul.c +++ b/src/eb/relic_eb_mul.c @@ -712,7 +712,7 @@ void eb_mul_lodah(eb_t r, const eb_t p, const bn_t k) { bn_abs(t, k); bn_add(t, t, n); bn_add(n, t, n); - dv_swap_cond(t->dp, n->dp, RLC_MAX(t->used, n->used), + dv_swap_sec(t->dp, n->dp, RLC_MAX(t->used, n->used), bn_get_bit(t, bits) == 0); t->used = RLC_SEL(t->used, n->used, bn_get_bit(t, bits) == 0); @@ -743,8 +743,8 @@ void eb_mul_lodah(eb_t r, const eb_t p, const bn_t k) { fb_mul(r2, x2, z1); fb_add(r3, r1, r2); fb_muln_low(r4, r1, r2); - dv_swap_cond(x1, x2, RLC_FB_DIGS, j ^ 1); - dv_swap_cond(z1, z2, RLC_FB_DIGS, j ^ 1); + dv_swap_sec(x1, x2, RLC_FB_DIGS, j ^ 1); + dv_swap_sec(z1, z2, RLC_FB_DIGS, j ^ 1); fb_sqr(z1, r3); fb_muln_low(r1, z1, p->x); fb_addd_low(x1, r1, r4, 2 * RLC_FB_DIGS); @@ -775,8 +775,8 @@ void eb_mul_lodah(eb_t r, const eb_t p, const bn_t k) { fb_rdcn_low(x2, x2); break; } - dv_swap_cond(x1, x2, RLC_FB_DIGS, j ^ 1); - dv_swap_cond(z1, z2, RLC_FB_DIGS, j ^ 1); + dv_swap_sec(x1, x2, RLC_FB_DIGS, j ^ 1); + dv_swap_sec(z1, z2, RLC_FB_DIGS, j ^ 1); } if (fb_is_zero(z1)) { diff --git a/src/ed/relic_ed_map.c b/src/ed/relic_ed_map.c index e95f3da73..170e53387 100644 --- a/src/ed/relic_ed_map.c +++ b/src/ed/relic_ed_map.c @@ -97,7 +97,7 @@ void ed_map_ell2_5mod8(ed_t p, fp_t t) { fp_mul(p->x, p->x, p->z); { const int e1 = fp_cmp(p->x, tv4); - dv_copy_cond(p->y, tv2, RLC_FP_DIGS, e1 == RLC_EQ); + fp_copy_sec(p->y, tv2, e1 == RLC_EQ); } /* e1 goes out of scope */ /* compute numerator of g(x2) */ @@ -111,7 +111,7 @@ void ed_map_ell2_5mod8(ed_t p, fp_t t) { fp_mul(tv2, tv2, p->z); { const int e2 = fp_cmp(p->x, tv2); - dv_copy_cond(tv5, tv3, RLC_FP_DIGS, e2 == RLC_EQ); + fp_copy_sec(tv5, tv3, e2 == RLC_EQ); } /* e2 goes out of scope */ /* figure out whether we wanted y1 or y2 and x1 or x2 */ @@ -120,16 +120,16 @@ void ed_map_ell2_5mod8(ed_t p, fp_t t) { { const int e3 = fp_cmp(tv2, tv4); fp_set_dig(p->x, 1); - dv_copy_cond(p->x, tv1, RLC_FP_DIGS, e3 != RLC_EQ); + fp_copy_sec(p->x, tv1, e3 != RLC_EQ); fp_mul(p->x, p->x, c_486662); fp_neg(p->x, p->x); - dv_copy_cond(p->y, tv5, RLC_FP_DIGS, e3 != RLC_EQ); + fp_copy_sec(p->y, tv5, e3 != RLC_EQ); /* fix sign of y */ fp_prime_back(h, p->y); const int e4 = bn_get_bit(h, 0); fp_neg(tv2, p->y); - dv_copy_cond(p->y, tv2, RLC_FP_DIGS, (e3 == RLC_EQ) ^ (e4 == 1)); + fp_copy_sec(p->y, tv2, (e3 == RLC_EQ) ^ (e4 == 1)); } /* e3 and e4 go out of scope */ fp_add_dig(p->z, tv1, 1); @@ -147,9 +147,9 @@ void ed_map_ell2_5mod8(ed_t p, fp_t t) { /* exceptional case: either denominator == 0 */ const int e4 = fp_is_zero(p->z); fp_set_dig(tv5, 1); - dv_copy_cond(p->x, p->z, RLC_FP_DIGS, e4); /* set x to 0 */ - dv_copy_cond(p->y, tv5, RLC_FP_DIGS, e4); - dv_copy_cond(p->z, tv5, RLC_FP_DIGS, e4); + dv_copy_sec(p->x, p->z, RLC_FP_DIGS, e4); /* set x to 0 */ + dv_copy_sec(p->y, tv5, RLC_FP_DIGS, e4); + dv_copy_sec(p->z, tv5, RLC_FP_DIGS, e4); } /* e4 goes out of scope */ /* clear denominator / compute extended coordinates if necessary */ diff --git a/src/ed/relic_ed_mul.c b/src/ed/relic_ed_mul.c index 65f413a8e..0a9684b9f 100644 --- a/src/ed/relic_ed_mul.c +++ b/src/ed/relic_ed_mul.c @@ -149,35 +149,35 @@ static void ed_mul_reg_imp(ed_t r, const ed_t p, const bn_t k) { n = ((n ^ s) - s) >> 1; for (j = 0; j < (1 << (RLC_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); - dv_copy_cond(u->z, t[j]->z, RLC_FP_DIGS, j == n); + fp_copy_sec(u->x, t[j]->x, j == n); + fp_copy_sec(u->y, t[j]->y, j == n); + fp_copy_sec(u->z, t[j]->z, j == n); #if ED_ADD == EXTND - dv_copy_cond(u->t, t[j]->t, RLC_FP_DIGS, j == n); + fp_copy_sec(u->t, t[j]->t, j == n); #endif } ed_neg(v, u); - dv_copy_cond(u->x, v->x, RLC_FP_DIGS, s != 0); + fp_copy_sec(u->x, v->x, s != 0); #if ED_ADD == EXTND - dv_copy_cond(u->t, v->t, RLC_FP_DIGS, s != 0); + fp_copy_sec(u->t, v->t, s != 0); #endif ed_add(r, r, u); } /* t[0] has an unmodified copy of p. */ ed_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)); -#if ED_ADD == EXTND - dv_copy_cond(r->t, u->t, RLC_FP_DIGS, bn_is_even(k)); + fp_copy_sec(r->x, u->x, bn_is_even(k)); + fp_copy_sec(r->y, u->y, bn_is_even(k)); + fp_copy_sec(r->z, u->z, bn_is_even(k)); +#if ED_Afp == EXTND + fp_copy_sec(r->t, u->t, bn_is_even(k)); #endif /* Convert r to affine coordinates. */ ed_norm(r, r); ed_neg(u, r); - dv_copy_cond(r->x, u->x, RLC_FP_DIGS, bn_sign(k) == RLC_NEG); + fp_copy_sec(r->x, u->x, bn_sign(k) == RLC_NEG); #if ED_ADD == EXTND - dv_copy_cond(r->t, u->t, RLC_FP_DIGS, bn_sign(k) == RLC_NEG); + fp_copy_sec(r->t, u->t, bn_sign(k) == RLC_NEG); #endif } RLC_CATCH_ANY { @@ -342,19 +342,19 @@ void ed_mul_monty(ed_t r, const ed_t p, const bn_t k) { for (int i = bn_bits(k) - 1; i >= 0; i--) { int j = bn_get_bit(k, 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); + dv_swap_sec(t[0]->x, t[1]->x, RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y, t[1]->y, RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z, t[1]->z, RLC_FP_DIGS, j ^ 1); #if ED_ADD == EXTND - dv_swap_cond(t[0]->t, t[1]->t, RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->t, t[1]->t, RLC_FP_DIGS, j ^ 1); #endif ed_add(t[0], t[0], t[1]); ed_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); + dv_swap_sec(t[0]->x, t[1]->x, RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y, t[1]->y, RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z, t[1]->z, RLC_FP_DIGS, j ^ 1); #if ED_ADD == EXTND - dv_swap_cond(t[0]->t, t[1]->t, RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->t, t[1]->t, RLC_FP_DIGS, j ^ 1); #endif } diff --git a/src/ep/relic_ep_map.c b/src/ep/relic_ep_map.c index f07b6aac0..d69ea7f4b 100644 --- a/src/ep/relic_ep_map.c +++ b/src/ep/relic_ep_map.c @@ -57,20 +57,20 @@ TMPL_MAP_ISOGENY_MAP(ep, fp, iso); #endif /* EP_CTMAP */ -#define EP_MAP_COPY_COND(O, I, C) dv_copy_cond(O, I, RLC_FP_DIGS, C) +#define EP_MAP_copy_sec(O, I, C) dv_copy_sec(O, I, RLC_FP_DIGS, C) /** * Simplified SWU mapping from Section 4 of * "Fast and simple constant-time hashing to the BLS12-381 Elliptic Curve" */ -TMPL_MAP_SSWU(ep, fp, dig_t, EP_MAP_COPY_COND); +TMPL_MAP_SSWU(ep, fp, dig_t, EP_MAP_copy_sec); /** * Shallue--van de Woestijne map, based on the definition from * draft-irtf-cfrg-hash-to-curve-06, Section 6.6.1 */ -TMPL_MAP_SVDW(ep, fp, dig_t, EP_MAP_COPY_COND); +TMPL_MAP_SVDW(ep, fp, dig_t, EP_MAP_copy_sec); -#undef EP_MAP_COPY_COND +#undef EP_MAP_copy_sec /** * Maps an array of uniformly random bytes to a point in a prime elliptic @@ -120,7 +120,7 @@ static void ep_map_from_field(ep_t p, const uint8_t *uniform_bytes, size_t len, /* compare sign of y and sign of t; fix if necessary */ \ neg = neg != fp_is_even(PT->y); \ fp_neg(t, PT->y); \ - dv_copy_cond(PT->y, t, RLC_FP_DIGS, neg); \ + dv_copy_sec(PT->y, t, RLC_FP_DIGS, neg); \ } while (0) /* first map invocation */ @@ -380,16 +380,16 @@ void ep_map_swift(ep_t p, const uint8_t *msg, size_t len) { int c2 = fp_is_sqr(u); int c3 = fp_is_sqr(v); - dv_swap_cond(t, u, RLC_FP_DIGS, c2); - dv_swap_cond(x1, y1, RLC_FP_DIGS, c2); - dv_swap_cond(t, v, RLC_FP_DIGS, c3); - dv_swap_cond(x1, z1, RLC_FP_DIGS, c3); + dv_swap_sec(t, u, RLC_FP_DIGS, c2); + dv_swap_sec(x1, y1, RLC_FP_DIGS, c2); + dv_swap_sec(t, v, RLC_FP_DIGS, c3); + dv_swap_sec(x1, z1, RLC_FP_DIGS, c3); if (!fp_srt(t, t)) { RLC_THROW(ERR_NO_VALID); } fp_neg(u, t); - dv_swap_cond(t, u, RLC_FP_DIGS, fp_is_even(t) ^ s); + dv_swap_sec(t, u, RLC_FP_DIGS, fp_is_even(t) ^ s); fp_copy(p->x, x1); fp_copy(p->y, t); @@ -447,16 +447,16 @@ void ep_map_swift(ep_t p, const uint8_t *msg, size_t len) { int c2 = fp_is_sqr(u); int c3 = fp_is_sqr(v); - dv_swap_cond(x1, y1, RLC_FP_DIGS, c2); - dv_swap_cond(t, u, RLC_FP_DIGS, c2); - dv_swap_cond(x1, z1, RLC_FP_DIGS, c3); - dv_swap_cond(t, v, RLC_FP_DIGS, c3); + dv_swap_sec(x1, y1, RLC_FP_DIGS, c2); + dv_swap_sec(t, u, RLC_FP_DIGS, c2); + dv_swap_sec(x1, z1, RLC_FP_DIGS, c3); + dv_swap_sec(t, v, RLC_FP_DIGS, c3); if (!fp_srt(t, t)) { RLC_THROW(ERR_NO_VALID); } fp_neg(u, t); - dv_swap_cond(t, u, RLC_FP_DIGS, fp_is_even(t) ^ s); + dv_swap_sec(t, u, RLC_FP_DIGS, fp_is_even(t) ^ s); fp_copy(p->x, x1); fp_copy(p->y, t); diff --git a/src/ep/relic_ep_mul.c b/src/ep/relic_ep_mul.c index 7f8fa0612..c79cf404d 100644 --- a/src/ep/relic_ep_mul.c +++ b/src/ep/relic_ep_mul.c @@ -259,7 +259,7 @@ static void ep_mul_reg_glv(ep_t r, const ep_t p, const bn_t k) { ep_norm(t[0], p); ep_neg(q, t[0]); - dv_copy_cond(q->y, t[0]->y, RLC_FP_DIGS, s[0] == RLC_POS); + dv_copy_sec(q->y, t[0]->y, RLC_FP_DIGS, s[0] == RLC_POS); ep_tab(t, q, RLC_WIDTH); l = RLC_FP_BITS + 1; @@ -288,38 +288,38 @@ static void ep_mul_reg_glv(ep_t r, const ep_t p, const bn_t k) { n1 = ((n1 ^ c1) - c1) >> 1; for (size_t j = 0; j < (1 << (RLC_WIDTH - 2)); j++) { - dv_copy_cond(u->x, t[j]->x, RLC_FP_DIGS, j == n0); - dv_copy_cond(w->x, t[j]->x, RLC_FP_DIGS, j == n1); - dv_copy_cond(u->y, t[j]->y, RLC_FP_DIGS, j == n0); - dv_copy_cond(w->y, t[j]->y, RLC_FP_DIGS, j == n1); -#if !defined(EP_MIXED) - dv_copy_cond(u->z, t[j]->z, RLC_FP_DIGS, j == n0); - dv_copy_cond(w->z, t[j]->z, RLC_FP_DIGS, j == n1); + fp_copy_sec(u->x, t[j]->x, j == n0); + fp_copy_sec(w->x, t[j]->x, j == n1); + fp_copy_sec(u->y, t[j]->y, j == n0); + fp_copy_sec(w->y, t[j]->y, j == n1); +#if !defined(EP_fpXED) + fp_copy_sec(u->z, t[j]->z, j == n0); + fp_copy_sec(w->z, t[j]->z, j == n1); #endif } ep_neg(q, u); - dv_copy_cond(q->y, u->y, RLC_FP_DIGS, c0 == 0); + fp_copy_sec(q->y, u->y, c0 == 0); ep_add(r, r, q); ep_psi(w, w); ep_neg(q, w); - dv_copy_cond(w->y, q->y, RLC_FP_DIGS, (c1 != 0) ^ (s[0] != s[1])); + fp_copy_sec(w->y, q->y, (c1 != 0) ^ (s[0] != s[1])); ep_add(r, r, w); } /* t[0] has an unmodified copy of p. */ ep_sub(u, r, t[0]); - dv_copy_cond(r->x, u->x, RLC_FP_DIGS, b[0]); - dv_copy_cond(r->y, u->y, RLC_FP_DIGS, b[0]); - dv_copy_cond(r->z, u->z, RLC_FP_DIGS, b[0]); + fp_copy_sec(r->x, u->x, b[0]); + fp_copy_sec(r->y, u->y, b[0]); + fp_copy_sec(r->z, u->z, b[0]); ep_psi(w, t[0]); ep_neg(q, w); - dv_copy_cond(q->y, w->y, RLC_FP_DIGS, s[0] == s[1]); + fp_copy_sec(q->y, w->y, s[0] == s[1]); ep_sub(u, r, q); - dv_copy_cond(r->x, u->x, RLC_FP_DIGS, b[1]); - dv_copy_cond(r->y, u->y, RLC_FP_DIGS, b[1]); - dv_copy_cond(r->z, u->z, RLC_FP_DIGS, b[1]); + fp_copy_sec(r->x, u->x, b[1]); + fp_copy_sec(r->y, u->y, b[1]); + fp_copy_sec(r->z, u->z, b[1]); /* Convert r to affine coordinates. */ ep_norm(r, r); @@ -398,25 +398,25 @@ static void ep_mul_reg_imp(ep_t r, const ep_t p, const bn_t k) { n = ((n ^ s) - s) >> 1; for (j = 0; j < (1 << (RLC_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); + fp_copy_sec(u->x, t[j]->x, j == n); + fp_copy_sec(u->y, t[j]->y, j == n); #if !defined(EP_MIXED) - dv_copy_cond(u->z, t[j]->z, RLC_FP_DIGS, j == n); + fp_copy_sec(u->z, t[j]->z, j == n); #endif } ep_neg(v, u); - dv_copy_cond(u->y, v->y, RLC_FP_DIGS, s != 0); + fp_copy_sec(u->y, v->y, 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)); + fp_copy_sec(r->x, u->x, bn_is_even(k)); + fp_copy_sec(r->y, u->y, bn_is_even(k)); + fp_copy_sec(r->z, u->z, 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); + fp_copy_sec(r->y, u->y, bn_sign(k) == RLC_NEG); } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); @@ -603,7 +603,7 @@ void ep_mul_monty(ep_t r, const ep_t p, const bn_t k) { 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), + dv_swap_sec(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); @@ -616,14 +616,14 @@ void ep_mul_monty(ep_t r, const ep_t p, const bn_t k) { for (int i = bits - 1; i >= 0; i--) { int 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); + dv_swap_sec(t[0]->x, t[1]->x, RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y, t[1]->y, RLC_FP_DIGS, j ^ 1); + dv_swap_sec(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); + dv_swap_sec(t[0]->x, t[1]->x, RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y, t[1]->y, RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z, t[1]->z, RLC_FP_DIGS, j ^ 1); } ep_norm(r, t[0]); diff --git a/src/epx/relic_ep2_map.c b/src/epx/relic_ep2_map.c index 80f01ac24..de5aa5c34 100644 --- a/src/epx/relic_ep2_map.c +++ b/src/epx/relic_ep2_map.c @@ -58,19 +58,19 @@ TMPL_MAP_ISOGENY_MAP(ep2, fp2, iso2) /** * Simplified SWU mapping. */ -#define EP2_MAP_COPY_COND(O, I, C) \ +#define EP2_MAP_copy_sec(O, I, C) \ do { \ - dv_copy_cond(O[0], I[0], RLC_FP_DIGS, C); \ - dv_copy_cond(O[1], I[1], RLC_FP_DIGS, C); \ + fp_copy_sec(O[0], I[0], C); \ + fp_copy_sec(O[1], I[1], C); \ } while (0) -TMPL_MAP_SSWU(ep2, fp2, fp_t, EP2_MAP_COPY_COND) +TMPL_MAP_SSWU(ep2, fp2, fp_t, EP2_MAP_copy_sec) /** * Shallue--van de Woestijne map. */ -TMPL_MAP_SVDW(ep2, fp2, fp_t, EP2_MAP_COPY_COND) +TMPL_MAP_SVDW(ep2, fp2, fp_t, EP2_MAP_copy_sec) -#undef EP2_MAP_COPY_COND +#undef EP2_MAP_copy_sec /* caution: this function overwrites k, which it uses as an auxiliary variable */ static inline int fp2_sgn0(const fp2_t t, bn_t k) { @@ -140,8 +140,7 @@ static void ep2_map_from_field(ep2_t p, const uint8_t *r, size_t len) { /* compare sign of y to sign of t; fix if necessary */ \ neg = neg != fp2_sgn0(PT->y, k); \ fp2_neg(t, PT->y); \ - dv_copy_cond(PT->y[0], t[0], RLC_FP_DIGS, neg); \ - dv_copy_cond(PT->y[1], t[1], RLC_FP_DIGS, neg); \ + fp2_copy_sec(PT->y, t, neg); \ } while (0) /* first map invocation */ @@ -346,10 +345,10 @@ void ep2_map_swift(ep2_t p, const uint8_t *msg, size_t len) { c3 = fp2_is_sqr(v); for (int i = 0; i < 2; i++) { - dv_swap_cond(x1[i], y1[i], RLC_FP_DIGS, c2); - dv_swap_cond(t[i], u[i], RLC_FP_DIGS, c2); - dv_swap_cond(x1[i], z1[i], RLC_FP_DIGS, c3); - dv_swap_cond(t[i], v[i], RLC_FP_DIGS, c3); + dv_swap_sec(x1[i], y1[i], RLC_FP_DIGS, c2); + dv_swap_sec(t[i], u[i], RLC_FP_DIGS, c2); + dv_swap_sec(x1[i], z1[i], RLC_FP_DIGS, c3); + dv_swap_sec(t[i], v[i], RLC_FP_DIGS, c3); } if (!fp2_srt(t, t)) { @@ -365,8 +364,8 @@ void ep2_map_swift(ep2_t p, const uint8_t *msg, size_t len) { sign ^= (t0 | (t0z & t1)); fp2_neg(u, t); - dv_swap_cond(t[0], u[0], RLC_FP_DIGS, sign); - dv_swap_cond(t[1], u[1], RLC_FP_DIGS, sign); + dv_swap_sec(t[0], u[0], RLC_FP_DIGS, sign); + dv_swap_sec(t[1], u[1], RLC_FP_DIGS, sign); fp2_copy(p->x, x1); fp2_copy(p->y, t); diff --git a/src/epx/relic_ep2_mul.c b/src/epx/relic_ep2_mul.c index bd86cf790..fe6dd6226 100644 --- a/src/epx/relic_ep2_mul.c +++ b/src/epx/relic_ep2_mul.c @@ -170,8 +170,8 @@ static void ep2_mul_naf_imp(ep2_t r, const ep2_t p, const bn_t k) { static void ep2_mul_reg_gls(ep2_t r, const ep2_t p, const bn_t k) { int8_t reg[4][RLC_FP_BITS + 1], b[4], s[4], c0, n0; - bn_t n, _k[4], u; ep2_t q, w, t[4][1 << (RLC_WIDTH - 2)]; + bn_t n, _k[4], u; size_t l, len, _l[4]; bn_null(n); @@ -199,6 +199,7 @@ static void ep2_mul_reg_gls(ep2_t r, const ep2_t p, const bn_t k) { bn_rec_frb(_k, 4, _k[0], u, n, ep_curve_is_pairf() == EP_BN); l = 0; + /* Make some extra room for BN curves that grow subscalars by 1. */ len = bn_bits(u) + (ep_curve_is_pairf() == EP_BN); ep2_norm(t[0][0], p); for (size_t i = 0; i < 4; i++) { @@ -207,7 +208,6 @@ static void ep2_mul_reg_gls(ep2_t r, const ep2_t p, const bn_t k) { b[i] = bn_is_even(_k[i]); _k[i]->dp[0] |= b[i]; - /* Make some extra room for BN curves that grow subscalars by 1. */ _l[i] = RLC_FP_BITS + 1; bn_rec_reg(reg[i], &_l[i], _k[i], len, RLC_WIDTH); l = RLC_MAX(l, _l[i]); @@ -220,8 +220,7 @@ static void ep2_mul_reg_gls(ep2_t r, const ep2_t p, const bn_t k) { for (size_t i = 0; i < 4; i++) { ep2_neg(q, t[i][0]); - dv_copy_cond(q->y[0], t[i][0]->y[0], RLC_FP_DIGS, s[i] == RLC_POS); - dv_copy_cond(q->y[1], t[i][0]->y[1], RLC_FP_DIGS, s[i] == RLC_POS); + fp2_copy_sec(q->y, t[i][0]->y, s[i] == RLC_POS); ep2_tab(t[i], q, RLC_WIDTH); } @@ -244,19 +243,15 @@ static void ep2_mul_reg_gls(ep2_t r, const ep2_t p, const bn_t k) { n0 = ((n0 ^ c0) - c0) >> 1; for (size_t m = 0; m < (1 << (RLC_WIDTH - 2)); m++) { - dv_copy_cond(w->x[0], t[i][m]->x[0], RLC_FP_DIGS, m == n0); - dv_copy_cond(w->x[1], t[i][m]->x[1], RLC_FP_DIGS, m == n0); - dv_copy_cond(w->y[0], t[i][m]->y[0], RLC_FP_DIGS, m == n0); - dv_copy_cond(w->y[1], t[i][m]->y[1], RLC_FP_DIGS, m == n0); + fp2_copy_sec(w->x, t[i][m]->x, m == n0); + fp2_copy_sec(w->y, t[i][m]->y, m == n0); #if !defined(EP_MIXED) - dv_copy_cond(w->z[0], t[i][m]->z[0], RLC_FP_DIGS, m == n0); - dv_copy_cond(w->z[1], t[i][m]->z[1], RLC_FP_DIGS, m == n0); + fp2_copy_sec(w->z, t[i][m]->z, m == n0); #endif } ep2_neg(q, w); - dv_copy_cond(q->y[0], w->y[0], RLC_FP_DIGS, c0 == 0); - dv_copy_cond(q->y[1], w->y[1], RLC_FP_DIGS, c0 == 0); + fp2_copy_sec(q->y, w->y, c0 == 0); ep2_add(r, r, q); } } @@ -264,12 +259,9 @@ static void ep2_mul_reg_gls(ep2_t r, const ep2_t p, const bn_t k) { for (size_t i = 0; i < 4; i++) { /* Tables are built with points already negated, so no need here. */ ep2_sub(q, r, t[i][0]); - dv_copy_cond(r->x[0], q->x[0], RLC_FP_DIGS, b[i]); - dv_copy_cond(r->x[1], q->x[1], RLC_FP_DIGS, b[i]); - dv_copy_cond(r->y[0], q->y[0], RLC_FP_DIGS, b[i]); - dv_copy_cond(r->y[1], q->y[1], RLC_FP_DIGS, b[i]); - dv_copy_cond(r->z[0], q->z[0], RLC_FP_DIGS, b[i]); - dv_copy_cond(r->z[1], q->z[1], RLC_FP_DIGS, b[i]); + fp2_copy_sec(r->x, q->x, b[i]); + fp2_copy_sec(r->y, q->y, b[i]); + fp2_copy_sec(r->z, q->z, b[i]); } /* Convert r to affine coordinates. */ @@ -298,10 +290,9 @@ static void ep2_mul_reg_gls(ep2_t r, const ep2_t p, const bn_t k) { static void ep2_mul_reg_imp(ep2_t r, const ep2_t p, const bn_t k) { bn_t _k; - int i, j, n; int8_t s, reg[1 + RLC_CEIL(RLC_FP_BITS + 1, RLC_WIDTH - 1)]; ep2_t t[1 << (RLC_WIDTH - 2)], u, v; - size_t l; + size_t l, n; bn_null(_k); @@ -310,7 +301,7 @@ static void ep2_mul_reg_imp(ep2_t r, const ep2_t p, const bn_t k) { ep2_new(u); ep2_new(v); /* Prepare the precomputation table. */ - for (i = 0; i < (1 << (RLC_WIDTH - 2)); i++) { + for (size_t i = 0; i < (1 << (RLC_WIDTH - 2)); i++) { ep2_null(t[i]); ep2_new(t[i]); } @@ -335,8 +326,8 @@ static void ep2_mul_reg_imp(ep2_t r, const ep2_t p, const bn_t k) { u->coord = EP_ADD; #endif ep2_set_infty(r); - for (i = l - 1; i >= 0; i--) { - for (j = 0; j < RLC_WIDTH - 1; j++) { + for (int i = l - 1; i >= 0; i--) { + for (size_t j = 0; j < RLC_WIDTH - 1; j++) { ep2_dbl(r, r); } @@ -344,41 +335,33 @@ static void ep2_mul_reg_imp(ep2_t r, const ep2_t p, const bn_t k) { s = (n >> 7); n = ((n ^ s) - s) >> 1; - for (j = 0; j < (1 << (RLC_WIDTH - 2)); j++) { - dv_copy_cond(u->x[0], t[j]->x[0], RLC_FP_DIGS, j == n); - dv_copy_cond(u->x[1], t[j]->x[1], RLC_FP_DIGS, j == n); - dv_copy_cond(u->y[0], t[j]->y[0], RLC_FP_DIGS, j == n); - dv_copy_cond(u->y[1], t[j]->y[1], RLC_FP_DIGS, j == n); + for (size_t j = 0; j < (1 << (RLC_WIDTH - 2)); j++) { + fp2_copy_sec(u->x, t[j]->x, j == n); + fp2_copy_sec(u->y, t[j]->y, j == n); #if !defined(EP_MIXED) - dv_copy_cond(u->z[0], t[j]->z[0], RLC_FP_DIGS, j == n); - dv_copy_cond(u->z[1], t[j]->z[1], RLC_FP_DIGS, j == n); + fp_copy_sec(u->z, t[j]->z, j == n); #endif } ep2_neg(v, u); - dv_copy_cond(u->y[0], v->y[0], RLC_FP_DIGS, s != 0); - dv_copy_cond(u->y[1], v->y[1], RLC_FP_DIGS, s != 0); + fp2_copy_sec(u->y, v->y, s != 0); ep2_add(r, r, u); } /* t[0] has an unmodified copy of p. */ ep2_sub(u, r, t[0]); - dv_copy_cond(r->x[0], u->x[0], RLC_FP_DIGS, bn_is_even(k)); - dv_copy_cond(r->x[1], u->x[1], RLC_FP_DIGS, bn_is_even(k)); - dv_copy_cond(r->y[0], u->y[0], RLC_FP_DIGS, bn_is_even(k)); - dv_copy_cond(r->y[1], u->y[1], RLC_FP_DIGS, bn_is_even(k)); - dv_copy_cond(r->z[0], u->z[0], RLC_FP_DIGS, bn_is_even(k)); - dv_copy_cond(r->z[1], u->z[1], RLC_FP_DIGS, bn_is_even(k)); + fp2_copy_sec(r->x, u->x, bn_is_even(k)); + fp2_copy_sec(r->y, u->y, bn_is_even(k)); + fp2_copy_sec(r->z, u->z, bn_is_even(k)); /* Convert r to affine coordinates. */ ep2_norm(r, r); ep2_neg(u, r); - dv_copy_cond(r->y[0], u->y[0], RLC_FP_DIGS, bn_sign(k) == RLC_NEG); - dv_copy_cond(r->y[1], u->y[1], RLC_FP_DIGS, bn_sign(k) == RLC_NEG); + fp2_copy_sec(r->y, u->y, bn_sign(k) == RLC_NEG); } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); } RLC_FINALLY { /* Free the precomputation table. */ - for (i = 0; i < (1 << (RLC_WIDTH - 2)); i++) { + for (size_t i = 0; i < (1 << (RLC_WIDTH - 2)); i++) { ep2_free(t[i]); } bn_free(_k); @@ -552,7 +535,7 @@ void ep2_mul_monty(ep2_t r, const ep2_t p, const bn_t k) { 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), + dv_swap_sec(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); @@ -565,20 +548,20 @@ void ep2_mul_monty(ep2_t r, const ep2_t p, const bn_t k) { for (int i = bits - 1; i >= 0; i--) { int j = bn_get_bit(l, i); - dv_swap_cond(t[0]->x[0], t[1]->x[0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->x[1], t[1]->x[1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[0], t[1]->y[0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[1], t[1]->y[1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[0], t[1]->z[0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[1], t[1]->z[1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[0], t[1]->x[0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[1], t[1]->x[1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[0], t[1]->y[0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[1], t[1]->y[1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[0], t[1]->z[0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[1], t[1]->z[1], RLC_FP_DIGS, j ^ 1); ep2_add(t[0], t[0], t[1]); ep2_dbl(t[1], t[1]); - dv_swap_cond(t[0]->x[0], t[1]->x[0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->x[1], t[1]->x[1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[0], t[1]->y[0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[1], t[1]->y[1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[0], t[1]->z[0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[1], t[1]->z[1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[0], t[1]->x[0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[1], t[1]->x[1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[0], t[1]->y[0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[1], t[1]->y[1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[0], t[1]->z[0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[1], t[1]->z[1], RLC_FP_DIGS, j ^ 1); } ep2_norm(r, t[0]); diff --git a/src/epx/relic_ep3_map.c b/src/epx/relic_ep3_map.c index 757a5ad0b..47c3e0e29 100644 --- a/src/epx/relic_ep3_map.c +++ b/src/epx/relic_ep3_map.c @@ -131,10 +131,10 @@ void ep3_map(ep3_t p, const uint8_t *msg, size_t len) { c3 = fp3_is_sqr(v); for (int i = 0; i < 3; i++) { - dv_swap_cond(x1[i], y1[i], RLC_FP_DIGS, c2); - dv_swap_cond(t[i], u[i], RLC_FP_DIGS, c2); - dv_swap_cond(x1[i], z1[i], RLC_FP_DIGS, c3); - dv_swap_cond(t[i], v[i], RLC_FP_DIGS, c3); + dv_swap_sec(x1[i], y1[i], RLC_FP_DIGS, c2); + dv_swap_sec(t[i], u[i], RLC_FP_DIGS, c2); + dv_swap_sec(x1[i], z1[i], RLC_FP_DIGS, c3); + dv_swap_sec(t[i], v[i], RLC_FP_DIGS, c3); } if (!fp3_srt(t, t)) { @@ -154,9 +154,9 @@ void ep3_map(ep3_t p, const uint8_t *msg, size_t len) { sign ^= (t0 | (t0z & (t1 | (t1z & t2)))); fp3_neg(u, t); - dv_swap_cond(t[0], u[0], RLC_FP_DIGS, sign); - dv_swap_cond(t[1], u[1], RLC_FP_DIGS, sign); - dv_swap_cond(t[2], u[2], RLC_FP_DIGS, sign); + dv_swap_sec(t[0], u[0], RLC_FP_DIGS, sign); + dv_swap_sec(t[1], u[1], RLC_FP_DIGS, sign); + dv_swap_sec(t[2], u[2], RLC_FP_DIGS, sign); fp3_copy(p->x, x1); fp3_copy(p->y, t); diff --git a/src/epx/relic_ep3_mul.c b/src/epx/relic_ep3_mul.c index 4ea09a1ee..6c9f515d6 100644 --- a/src/epx/relic_ep3_mul.c +++ b/src/epx/relic_ep3_mul.c @@ -383,7 +383,7 @@ void ep3_mul_monty(ep3_t r, const ep3_t p, const bn_t k) { 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), + dv_swap_sec(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); @@ -396,26 +396,26 @@ void ep3_mul_monty(ep3_t r, const ep3_t p, const bn_t k) { for (int i = bits - 1; i >= 0; i--) { int j = bn_get_bit(l, i); - dv_swap_cond(t[0]->x[0], t[1]->x[0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->x[1], t[1]->x[1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->x[2], t[1]->x[2], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[0], t[1]->y[0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[1], t[1]->y[1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[2], t[1]->y[2], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[0], t[1]->z[0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[1], t[1]->z[1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[2], t[1]->z[2], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[0], t[1]->x[0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[1], t[1]->x[1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[2], t[1]->x[2], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[0], t[1]->y[0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[1], t[1]->y[1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[2], t[1]->y[2], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[0], t[1]->z[0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[1], t[1]->z[1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[2], t[1]->z[2], RLC_FP_DIGS, j ^ 1); ep3_add(t[0], t[0], t[1]); ep3_dbl(t[1], t[1]); - dv_swap_cond(t[0]->x[0], t[1]->x[0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->x[1], t[1]->x[1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->x[2], t[1]->x[2], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[0], t[1]->y[0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[1], t[1]->y[1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[2], t[1]->y[2], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[0], t[1]->z[0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[1], t[1]->z[1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[2], t[1]->z[2], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[0], t[1]->x[0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[1], t[1]->x[1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[2], t[1]->x[2], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[0], t[1]->y[0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[1], t[1]->y[1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[2], t[1]->y[2], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[0], t[1]->z[0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[1], t[1]->z[1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[2], t[1]->z[2], RLC_FP_DIGS, j ^ 1); } ep3_norm(r, t[0]); diff --git a/src/epx/relic_ep4_map.c b/src/epx/relic_ep4_map.c index 2b11045c5..cb01375af 100644 --- a/src/epx/relic_ep4_map.c +++ b/src/epx/relic_ep4_map.c @@ -190,32 +190,32 @@ void ep4_map(ep4_t p, const uint8_t *msg, size_t len) { int c2 = fp4_is_sqr(u); int c3 = fp4_is_sqr(v); - dv_swap_cond(t[0][0], u[0][0], RLC_FP_DIGS, c2); - dv_swap_cond(t[0][1], u[0][1], RLC_FP_DIGS, c2); - dv_swap_cond(t[1][0], u[1][0], RLC_FP_DIGS, c2); - dv_swap_cond(t[1][1], u[1][1], RLC_FP_DIGS, c2); - dv_swap_cond(x1[0][0], y1[0][0], RLC_FP_DIGS, c2); - dv_swap_cond(x1[0][1], y1[0][1], RLC_FP_DIGS, c2); - dv_swap_cond(x1[1][0], y1[1][0], RLC_FP_DIGS, c2); - dv_swap_cond(x1[1][1], y1[1][1], RLC_FP_DIGS, c2); - dv_swap_cond(t[0][0], v[0][0], RLC_FP_DIGS, c3); - dv_swap_cond(t[0][1], v[0][1], RLC_FP_DIGS, c3); - dv_swap_cond(t[1][0], v[1][0], RLC_FP_DIGS, c3); - dv_swap_cond(t[1][1], v[1][1], RLC_FP_DIGS, c3); - dv_swap_cond(x1[0][0], z1[0][0], RLC_FP_DIGS, c3); - dv_swap_cond(x1[0][1], z1[0][1], RLC_FP_DIGS, c3); - dv_swap_cond(x1[1][0], z1[1][0], RLC_FP_DIGS, c3); - dv_swap_cond(x1[1][1], z1[1][1], RLC_FP_DIGS, c3); + dv_swap_sec(t[0][0], u[0][0], RLC_FP_DIGS, c2); + dv_swap_sec(t[0][1], u[0][1], RLC_FP_DIGS, c2); + dv_swap_sec(t[1][0], u[1][0], RLC_FP_DIGS, c2); + dv_swap_sec(t[1][1], u[1][1], RLC_FP_DIGS, c2); + dv_swap_sec(x1[0][0], y1[0][0], RLC_FP_DIGS, c2); + dv_swap_sec(x1[0][1], y1[0][1], RLC_FP_DIGS, c2); + dv_swap_sec(x1[1][0], y1[1][0], RLC_FP_DIGS, c2); + dv_swap_sec(x1[1][1], y1[1][1], RLC_FP_DIGS, c2); + dv_swap_sec(t[0][0], v[0][0], RLC_FP_DIGS, c3); + dv_swap_sec(t[0][1], v[0][1], RLC_FP_DIGS, c3); + dv_swap_sec(t[1][0], v[1][0], RLC_FP_DIGS, c3); + dv_swap_sec(t[1][1], v[1][1], RLC_FP_DIGS, c3); + dv_swap_sec(x1[0][0], z1[0][0], RLC_FP_DIGS, c3); + dv_swap_sec(x1[0][1], z1[0][1], RLC_FP_DIGS, c3); + dv_swap_sec(x1[1][0], z1[1][0], RLC_FP_DIGS, c3); + dv_swap_sec(x1[1][1], z1[1][1], RLC_FP_DIGS, c3); if (!fp4_srt(t, t)) { RLC_THROW(ERR_NO_VALID); } fp4_neg(u, t); c2 = fp_is_even(t[0][0]); - dv_swap_cond(t[0][0], u[0][0], RLC_FP_DIGS, c2 ^ sign); - dv_swap_cond(t[0][1], u[0][1], RLC_FP_DIGS, c2 ^ sign); - dv_swap_cond(t[1][0], u[1][0], RLC_FP_DIGS, c2 ^ sign); - dv_swap_cond(t[1][1], u[1][1], RLC_FP_DIGS, c2 ^ sign); + dv_swap_sec(t[0][0], u[0][0], RLC_FP_DIGS, c2 ^ sign); + dv_swap_sec(t[0][1], u[0][1], RLC_FP_DIGS, c2 ^ sign); + dv_swap_sec(t[1][0], u[1][0], RLC_FP_DIGS, c2 ^ sign); + dv_swap_sec(t[1][1], u[1][1], RLC_FP_DIGS, c2 ^ sign); fp4_copy(p->x, x1); fp4_copy(p->y, t); @@ -276,10 +276,10 @@ void ep4_map(ep4_t p, const uint8_t *msg, size_t len) { for (int i = 0; i < 2; i++) { for (int j = 0; j < 2; j++) { - dv_swap_cond(x1[i][j], y1[i][j], RLC_FP_DIGS, c2); - dv_swap_cond(t[i][j], u[i][j], RLC_FP_DIGS, c2); - dv_swap_cond(x1[i][j], z1[i][j], RLC_FP_DIGS, c3); - dv_swap_cond(t[i][j], v[i][j], RLC_FP_DIGS, c3); + dv_swap_sec(x1[i][j], y1[i][j], RLC_FP_DIGS, c2); + dv_swap_sec(t[i][j], u[i][j], RLC_FP_DIGS, c2); + dv_swap_sec(x1[i][j], z1[i][j], RLC_FP_DIGS, c3); + dv_swap_sec(t[i][j], v[i][j], RLC_FP_DIGS, c3); } } @@ -301,10 +301,10 @@ void ep4_map(ep4_t p, const uint8_t *msg, size_t len) { sign ^= (s[0] | (t0z & s[1])); fp4_neg(u, t); - dv_swap_cond(t[0][0], u[0][0], RLC_FP_DIGS, sign); - dv_swap_cond(t[0][1], u[0][1], RLC_FP_DIGS, sign); - dv_swap_cond(t[1][0], u[1][0], RLC_FP_DIGS, sign); - dv_swap_cond(t[1][1], u[1][1], RLC_FP_DIGS, sign); + dv_swap_sec(t[0][0], u[0][0], RLC_FP_DIGS, sign); + dv_swap_sec(t[0][1], u[0][1], RLC_FP_DIGS, sign); + dv_swap_sec(t[1][0], u[1][0], RLC_FP_DIGS, sign); + dv_swap_sec(t[1][1], u[1][1], RLC_FP_DIGS, sign); fp4_copy(p->x, x1); fp4_copy(p->y, t); diff --git a/src/epx/relic_ep4_mul.c b/src/epx/relic_ep4_mul.c index 41bf5390b..d8b605e40 100644 --- a/src/epx/relic_ep4_mul.c +++ b/src/epx/relic_ep4_mul.c @@ -371,7 +371,7 @@ void ep4_mul_monty(ep4_t r, const ep4_t p, const bn_t k) { 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), + dv_swap_sec(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); @@ -384,32 +384,32 @@ void ep4_mul_monty(ep4_t r, const ep4_t p, const bn_t k) { for (int i = bits - 1; i >= 0; i--) { int j = bn_get_bit(l, i); - dv_swap_cond(t[0]->x[0][0], t[1]->x[0][0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->x[0][1], t[1]->x[0][1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->x[1][0], t[1]->x[1][0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->x[1][1], t[1]->x[1][1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[0][0], t[1]->y[0][0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[0][1], t[1]->y[0][1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[1][0], t[1]->y[1][0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[1][1], t[1]->y[1][1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[0][0], t[1]->z[0][0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[0][1], t[1]->z[0][1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[1][0], t[1]->z[1][0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[1][1], t[1]->z[1][1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[0][0], t[1]->x[0][0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[0][1], t[1]->x[0][1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[1][0], t[1]->x[1][0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[1][1], t[1]->x[1][1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[0][0], t[1]->y[0][0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[0][1], t[1]->y[0][1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[1][0], t[1]->y[1][0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[1][1], t[1]->y[1][1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[0][0], t[1]->z[0][0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[0][1], t[1]->z[0][1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[1][0], t[1]->z[1][0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[1][1], t[1]->z[1][1], RLC_FP_DIGS, j ^ 1); ep4_add(t[0], t[0], t[1]); ep4_dbl(t[1], t[1]); - dv_swap_cond(t[0]->x[0][0], t[1]->x[0][0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->x[0][1], t[1]->x[0][1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->x[1][0], t[1]->x[1][0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->x[1][1], t[1]->x[1][1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[0][0], t[1]->y[0][0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[0][1], t[1]->y[0][1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[1][0], t[1]->y[1][0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[1][1], t[1]->y[1][1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[0][0], t[1]->z[0][0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[0][1], t[1]->z[0][1], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[1][0], t[1]->z[1][0], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[1][1], t[1]->z[1][1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[0][0], t[1]->x[0][0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[0][1], t[1]->x[0][1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[1][0], t[1]->x[1][0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[1][1], t[1]->x[1][1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[0][0], t[1]->y[0][0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[0][1], t[1]->y[0][1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[1][0], t[1]->y[1][0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[1][1], t[1]->y[1][1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[0][0], t[1]->z[0][0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[0][1], t[1]->z[0][1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[1][0], t[1]->z[1][0], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[1][1], t[1]->z[1][1], RLC_FP_DIGS, j ^ 1); } ep4_norm(r, t[0]); diff --git a/src/epx/relic_ep8_map.c b/src/epx/relic_ep8_map.c index 421f17c61..d309316c2 100644 --- a/src/epx/relic_ep8_map.c +++ b/src/epx/relic_ep8_map.c @@ -142,10 +142,10 @@ void ep8_map(ep8_t p, const uint8_t *msg, size_t len) { for (int i = 0; i < 2; i++) { for (int j = 0; j < 2; j++) { for (int l = 0; l < 2; l++) { - dv_swap_cond(x1[i][j][l], y1[i][j][l], RLC_FP_DIGS, c2); - dv_swap_cond(t[i][j][l], u[i][j][l], RLC_FP_DIGS, c2); - dv_swap_cond(x1[i][j][l], z1[i][j][l], RLC_FP_DIGS, c3); - dv_swap_cond(t[i][j][l], v[i][j][l], RLC_FP_DIGS, c3); + dv_swap_sec(x1[i][j][l], y1[i][j][l], RLC_FP_DIGS, c2); + dv_swap_sec(t[i][j][l], u[i][j][l], RLC_FP_DIGS, c2); + dv_swap_sec(x1[i][j][l], z1[i][j][l], RLC_FP_DIGS, c3); + dv_swap_sec(t[i][j][l], v[i][j][l], RLC_FP_DIGS, c3); } } } @@ -173,7 +173,7 @@ void ep8_map(ep8_t p, const uint8_t *msg, size_t len) { for (int i = 0; i < 2; i++) { for (int j = 0; j < 2; j++) { for (int l = 0; l < 2; l++) { - dv_swap_cond(t[i][j][l], u[i][j][l], RLC_FP_DIGS, sign); + dv_swap_sec(t[i][j][l], u[i][j][l], RLC_FP_DIGS, sign); } } } diff --git a/src/epx/relic_ep8_mul.c b/src/epx/relic_ep8_mul.c index 8e0b0f60a..646df48e4 100644 --- a/src/epx/relic_ep8_mul.c +++ b/src/epx/relic_ep8_mul.c @@ -327,7 +327,7 @@ void ep8_mul_monty(ep8_t r, const ep8_t p, const bn_t k) { 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), + dv_swap_sec(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); @@ -343,9 +343,9 @@ void ep8_mul_monty(ep8_t r, const ep8_t p, const bn_t k) { for (int l = 0; l < 2; l++) { for (int m = 0; m < 2; m++) { for (int n = 0; n < 2; n++) { - dv_swap_cond(t[0]->x[l][m][n], t[1]->x[l][m][n], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[l][m][n], t[1]->y[l][m][n], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[l][m][n], t[1]->z[l][m][n], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[l][m][n], t[1]->x[l][m][n], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[l][m][n], t[1]->y[l][m][n], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[l][m][n], t[1]->z[l][m][n], RLC_FP_DIGS, j ^ 1); } } } @@ -354,9 +354,9 @@ void ep8_mul_monty(ep8_t r, const ep8_t p, const bn_t k) { for (int l = 0; l < 2; l++) { for (int m = 0; m < 2; m++) { for (int n = 0; n < 2; n++) { - dv_swap_cond(t[0]->x[l][m][n], t[1]->x[l][m][n], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->y[l][m][n], t[1]->y[l][m][n], RLC_FP_DIGS, j ^ 1); - dv_swap_cond(t[0]->z[l][m][n], t[1]->z[l][m][n], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->x[l][m][n], t[1]->x[l][m][n], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->y[l][m][n], t[1]->y[l][m][n], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0]->z[l][m][n], t[1]->z[l][m][n], RLC_FP_DIGS, j ^ 1); } } } diff --git a/src/fb/relic_fb_cmp.c b/src/fb/relic_fb_cmp.c index 9e3dc2085..3eb0ef1b8 100644 --- a/src/fb/relic_fb_cmp.c +++ b/src/fb/relic_fb_cmp.c @@ -48,5 +48,5 @@ int fb_cmp_dig(const fb_t a, dig_t b) { } int fb_cmp(const fb_t a, const fb_t b) { - return dv_cmp_const(a, b, RLC_FB_DIGS); + return dv_cmp_sec(a, b, RLC_FB_DIGS); } diff --git a/src/fb/relic_fb_exp.c b/src/fb/relic_fb_exp.c index 600287306..b25d688a9 100644 --- a/src/fb/relic_fb_exp.c +++ b/src/fb/relic_fb_exp.c @@ -173,10 +173,10 @@ void fb_exp_monty(fb_t c, const fb_t a, const bn_t b) { for (int i = bn_bits(b) - 1; i >= 0; i--) { int j = bn_get_bit(b, i); - dv_swap_cond(t[0], t[1], RLC_FB_DIGS, j ^ 1); + dv_swap_sec(t[0], t[1], RLC_FB_DIGS, j ^ 1); fb_mul(t[0], t[0], t[1]); fb_sqr(t[1], t[1]); - dv_swap_cond(t[0], t[1], RLC_FB_DIGS, j ^ 1); + dv_swap_sec(t[0], t[1], RLC_FB_DIGS, j ^ 1); } if (bn_sign(b) == RLC_NEG) { diff --git a/src/fp/relic_fp_crt.c b/src/fp/relic_fp_crt.c index 140f64683..74e5326f3 100644 --- a/src/fp/relic_fp_crt.c +++ b/src/fp/relic_fp_crt.c @@ -157,7 +157,7 @@ int fp_crt(fp_t c, const fp_t a) { fp_exp(t0, a, e); /* Recover 3^f-root of unity, and continue algorithm. */ - fp_copy(t3, fp_prime_get_crt()); + fp_copy(t3, (const dig_t *)fp_prime_get_crt()); fp_copy(c, t3); for (int i = 0; i < f - 1; i++) { diff --git a/src/fp/relic_fp_exp.c b/src/fp/relic_fp_exp.c index de847ba78..715dc55dc 100644 --- a/src/fp/relic_fp_exp.c +++ b/src/fp/relic_fp_exp.c @@ -168,10 +168,10 @@ void fp_exp_monty(fp_t c, const fp_t a, const bn_t b) { for (int i = bn_bits(b) - 1; i >= 0; i--) { int j = bn_get_bit(b, i); - dv_swap_cond(t[0], t[1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0], t[1], RLC_FP_DIGS, j ^ 1); fp_mul(t[0], t[0], t[1]); fp_sqr(t[1], t[1]); - dv_swap_cond(t[0], t[1], RLC_FP_DIGS, j ^ 1); + dv_swap_sec(t[0], t[1], RLC_FP_DIGS, j ^ 1); } if (bn_sign(b) == RLC_NEG) { diff --git a/src/fp/relic_fp_inv.c b/src/fp/relic_fp_inv.c index 91615ede3..4ca9b265b 100644 --- a/src/fp/relic_fp_inv.c +++ b/src/fp/relic_fp_inv.c @@ -512,10 +512,10 @@ void fp_inv_divst(fp_t c, const fp_t a) { /* Conditionally negate delta if d0 is set. */ delta = (delta ^ -d0) + d0; /* Conditionally swap based on d0. */ - dv_swap_cond(r, v, RLC_FP_DIGS, d0); + dv_swap_sec(r, v, RLC_FP_DIGS, d0); fp_negm_low(t, r); - dv_swap_cond(f, g, RLC_FP_DIGS, d0); - dv_copy_cond(r, t, RLC_FP_DIGS, d0); + dv_swap_sec(f, g, RLC_FP_DIGS, d0); + dv_copy_sec(r, t, RLC_FP_DIGS, d0); for (int j = 0; j < RLC_FP_DIGS; j++) { g[j] = RLC_SEL(g[j], ~g[j], d0); } @@ -541,7 +541,7 @@ void fp_inv_divst(fp_t c, const fp_t a) { g[RLC_FP_DIGS - 1] |= (dig_t)gs << (RLC_DIG - 1); } fp_neg(t, v); - dv_copy_cond(v, t, RLC_FP_DIGS, fs); + fp_copy_sec(v, t, fs); dv_copy(t, fp_prime_get(), RLC_FP_DIGS); fp_add_dig(t, t, 1); @@ -650,10 +650,10 @@ void fp_inv_jmpds(fp_t c, const fp_t a) { /* Update column vector below. */ v1[0] = RLC_SEL(m[1], -m[1], RLC_SIGN(m[1])); fp_negm_low(t, v1); - dv_copy_cond(v1, t, RLC_FP_DIGS, RLC_SIGN(m[1])); + fp_copy_sec(v1, t, RLC_SIGN(m[1])); u1[0] = RLC_SEL(m[3], -m[3], RLC_SIGN(m[3])); fp_negm_low(t, u1); - dv_copy_cond(u1, t, RLC_FP_DIGS, RLC_SIGN(m[3])); + fp_copy_sec(u1, t, RLC_SIGN(m[3])); dv_copy(p01, v1, 2 * RLC_FP_DIGS); dv_copy(p11, u1, 2 * RLC_FP_DIGS); @@ -686,19 +686,19 @@ void fp_inv_jmpds(fp_t c, const fp_t a) { /* Update column vector below. */ bn_mul2_low(v0, p01, m[0], RLC_FP_DIGS + j); fp_subd_low(t, p, v0); - dv_copy_cond(v0, t, RLC_FP_DIGS + j + 1, RLC_SIGN(m[0])); + dv_copy_sec(v0, t, RLC_FP_DIGS + j + 1, RLC_SIGN(m[0])); bn_mul2_low(v1, p11, m[1], RLC_FP_DIGS + j); fp_subd_low(t, p, v1); - dv_copy_cond(v1, t, RLC_FP_DIGS + j + 1, RLC_SIGN(m[1])); + dv_copy_sec(v1, t, RLC_FP_DIGS + j + 1, RLC_SIGN(m[1])); bn_mul2_low(u0, p01, m[2], RLC_FP_DIGS + j); fp_subd_low(t, p, u0); - dv_copy_cond(u0, t, RLC_FP_DIGS + j + 1, RLC_SIGN(m[2])); + dv_copy_sec(u0, t, RLC_FP_DIGS + j + 1, RLC_SIGN(m[2])); bn_mul2_low(u1, p11, m[3], RLC_FP_DIGS + j); fp_subd_low(t, p, u1); - dv_copy_cond(u1, t, RLC_FP_DIGS + j + 1, RLC_SIGN(m[3])); + dv_copy_sec(u1, t, RLC_FP_DIGS + j + 1, RLC_SIGN(m[3])); j = i % RLC_FP_DIGS; if (j == 0) { @@ -719,19 +719,19 @@ void fp_inv_jmpds(fp_t c, const fp_t a) { /* Update column vector below. */ bn_mul2_low(v0, p01, m[0], 2 * RLC_FP_DIGS); fp_subd_low(t, p, v0); - dv_copy_cond(v0, t, 2 * RLC_FP_DIGS, RLC_SIGN(m[0])); + dv_copy_sec(v0, t, 2 * RLC_FP_DIGS, RLC_SIGN(m[0])); bn_mul2_low(v1, p11, m[1], 2 * RLC_FP_DIGS); fp_subd_low(t, p, v1); - dv_copy_cond(v1, t, 2 * RLC_FP_DIGS, RLC_SIGN(m[1])); + dv_copy_sec(v1, t, 2 * RLC_FP_DIGS, RLC_SIGN(m[1])); bn_mul2_low(u0, p01, m[2], 2 * RLC_FP_DIGS); fp_subd_low(t, p, u0); - dv_copy_cond(u0, t, 2 * RLC_FP_DIGS, RLC_SIGN(m[2])); + dv_copy_sec(u0, t, 2 * RLC_FP_DIGS, RLC_SIGN(m[2])); bn_mul2_low(u1, p11, m[3], 2 * RLC_FP_DIGS); fp_subd_low(t, p, u1); - dv_copy_cond(u1, t, 2 * RLC_FP_DIGS, RLC_SIGN(m[3])); + dv_copy_sec(u1, t, 2 * RLC_FP_DIGS, RLC_SIGN(m[3])); fp_addc_low(t, u0, u1); fp_rdc(p11, t); @@ -768,11 +768,11 @@ void fp_inv_jmpds(fp_t c, const fp_t a) { /* Update column vector below. */ bn_mul2_low(v0, p01, m[0], RLC_FP_DIGS + j); fp_subd_low(t, p, v0); - dv_copy_cond(v0, t, RLC_FP_DIGS + j + 1, RLC_SIGN(m[0])); + dv_copy_sec(v0, t, RLC_FP_DIGS + j + 1, RLC_SIGN(m[0])); bn_mul2_low(v1, p11, m[1], RLC_FP_DIGS + j); fp_subd_low(t, p, v1); - dv_copy_cond(v1, t, RLC_FP_DIGS + j + 1, RLC_SIGN(m[1])); + dv_copy_sec(v1, t, RLC_FP_DIGS + j + 1, RLC_SIGN(m[1])); fp_addd_low(t, v0, v1); fp_rdc(p01, t); @@ -784,11 +784,11 @@ void fp_inv_jmpds(fp_t c, const fp_t a) { /* Update column vector below. */ bn_mul2_low(v0, p01, m[0], 2 * RLC_FP_DIGS); fp_subd_low(t, p, v0); - dv_copy_cond(v0, t, 2 * RLC_FP_DIGS, RLC_SIGN(m[0])); + dv_copy_sec(v0, t, 2 * RLC_FP_DIGS, RLC_SIGN(m[0])); bn_mul2_low(v1, p11, m[1], 2 * RLC_FP_DIGS); fp_subd_low(t, p, v1); - dv_copy_cond(v1, t, 2 * RLC_FP_DIGS, RLC_SIGN(m[1])); + dv_copy_sec(v1, t, 2 * RLC_FP_DIGS, RLC_SIGN(m[1])); fp_addc_low(t, v0, v1); fp_rdc(p01, t); @@ -796,7 +796,7 @@ void fp_inv_jmpds(fp_t c, const fp_t a) { /* Negate based on sign of f at the end. */ fp_negm_low(t, p01); - dv_copy_cond(p01, t, RLC_FP_DIGS, f[RLC_FP_DIGS] >> (RLC_DIG - 1)); + dv_copy_sec(p01, t, RLC_FP_DIGS, f[RLC_FP_DIGS] >> (RLC_DIG - 1)); /* Multiply by (precomp * R^j) % p, one for each iteration of the loop, * one for the constant, one more to be removed by reduction. */ diff --git a/src/fp/relic_fp_smb.c b/src/fp/relic_fp_smb.c index be7973591..bdab95d3d 100644 --- a/src/fp/relic_fp_smb.c +++ b/src/fp/relic_fp_smb.c @@ -208,7 +208,7 @@ int fp_smb_divst(const fp_t a) { #endif r = dv_cmp(g, f, RLC_FP_DIGS); fp_subn_low(t, g, f); - dv_copy_cond(g, t, RLC_FP_DIGS, r != RLC_LT); + fp_copy_sec(g, t, r != RLC_LT); fs = gs = RLC_POS; @@ -251,9 +251,9 @@ int fp_smb_divst(const fp_t a) { t[0] = 1; bn_negs_low(f, f, fs, RLC_FP_DIGS); - r = RLC_SEL(r, 1 - k, dv_cmp_const(f, t, RLC_FP_DIGS) == RLC_EQ); + r = RLC_SEL(r, 1 - k, dv_cmp_sec(f, t, RLC_FP_DIGS) == RLC_EQ); bn_negs_low(t, t, 1, RLC_FP_DIGS); - r = RLC_SEL(r, 1 - k, dv_cmp_const(f, t, RLC_FP_DIGS) == RLC_EQ); + r = RLC_SEL(r, 1 - k, dv_cmp_sec(f, t, RLC_FP_DIGS) == RLC_EQ); r = RLC_SEL(r, 1 - k, fp_is_zero(f)); r = RLC_SEL(r, 0, fp_is_zero(a)); } RLC_CATCH_ANY { @@ -350,11 +350,11 @@ int fp_smb_jmpds(const fp_t a) { j = (j + (j & 1)) % 4; fp_zero(t0); - r = RLC_SEL(r, 1 - j, dv_cmp_const(f, t0, RLC_FP_DIGS) == RLC_EQ); + r = RLC_SEL(r, 1 - j, dv_cmp_sec(f, t0, RLC_FP_DIGS) == RLC_EQ); t0[0] = 1; - r = RLC_SEL(r, 1 - j, dv_cmp_const(f, t0, RLC_FP_DIGS) == RLC_EQ); + r = RLC_SEL(r, 1 - j, dv_cmp_sec(f, t0, RLC_FP_DIGS) == RLC_EQ); bn_negs_low(t0, t0, 1, RLC_FP_DIGS); - r = RLC_SEL(r, 1 - j, dv_cmp_const(f, t0, RLC_FP_DIGS) == RLC_EQ); + r = RLC_SEL(r, 1 - j, dv_cmp_sec(f, t0, RLC_FP_DIGS) == RLC_EQ); } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); diff --git a/src/fp/relic_fp_srt.c b/src/fp/relic_fp_srt.c index c5656be1c..693fd99a9 100644 --- a/src/fp/relic_fp_srt.c +++ b/src/fp/relic_fp_srt.c @@ -110,16 +110,14 @@ int fp_srt(fp_t c, const fp_t a) { fp_sqr(t2, t2); } fp_mul(t0, c, t3); - dv_copy_cond(c, t0, RLC_FP_DIGS, - fp_cmp_dig(t2, 1) != RLC_EQ); + fp_copy_sec(c, t0, fp_cmp_dig(t2, 1) != RLC_EQ); fp_sqr(t3, t3); fp_mul(t0, t1, t3); - dv_copy_cond(t1, t0, RLC_FP_DIGS, - fp_cmp_dig(t2, 1) != RLC_EQ); + fp_copy_sec(t1, t0, fp_cmp_dig(t2, 1) != RLC_EQ); } fp_neg(t0, c); - dv_copy_cond(c, t0, RLC_FP_DIGS, fp_is_even(c) == 0); + fp_copy_sec(c, t0, fp_is_even(c) == 0); break; } } diff --git a/src/fp/relic_fp_util.c b/src/fp/relic_fp_util.c index 8866fbb43..9556a9fd8 100644 --- a/src/fp/relic_fp_util.c +++ b/src/fp/relic_fp_util.c @@ -40,6 +40,10 @@ void fp_copy(fp_t c, const fp_t a) { dv_copy(c, a, RLC_FP_DIGS); } +void fp_copy_sec(fp_t c, const fp_t a, dig_t bit) { + dv_copy_sec(c, a, RLC_FP_DIGS, bit); +} + void fp_zero(fp_t a) { dv_zero(a, RLC_FP_DIGS); } diff --git a/src/fpx/relic_fpx_cyc.c b/src/fpx/relic_fpx_cyc.c index 02803b6c9..ffc1bb640 100644 --- a/src/fpx/relic_fpx_cyc.c +++ b/src/fpx/relic_fpx_cyc.c @@ -443,13 +443,11 @@ void fp12_back_cyc(fp12_t c, const fp12_t a) { int f = fp2_is_zero(a[1][0]); /* If f, t0 = 2 * g4 * g5, t1 = g3. */ fp2_copy(t2, a[0][1]); - dv_copy_cond(t2[0], a[1][2][0], RLC_FP_DIGS, f); - dv_copy_cond(t2[1], a[1][2][1], RLC_FP_DIGS, f); + fp2_copy_sec(t2, a[1][2], f); /* t0 = g4^2. */ fp2_mul(t0, a[0][1], t2); fp2_dbl(t2, t0); - dv_copy_cond(t0[0], t2[0], RLC_FP_DIGS, f); - dv_copy_cond(t0[1], t2[1], RLC_FP_DIGS, f); + fp2_copy_sec(t0, t2, f); /* t1 = 3 * g4^2 - 2 * g3. */ fp2_sub(t1, t0, a[0][2]); fp2_dbl(t1, t1); @@ -461,13 +459,11 @@ void fp12_back_cyc(fp12_t c, const fp12_t a) { /* t1 = (4 * g2). */ fp2_dbl(t1, a[1][0]); fp2_dbl(t1, t1); - dv_copy_cond(t1[0], a[0][2][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[1], a[0][2][1], RLC_FP_DIGS, f); + fp2_copy_sec(t1, a[0][2], f); /* If unity, decompress to unity as well. */ f = fp12_cmp_dig(a, 1) == RLC_EQ; fp2_set_dig(t2, 1); - dv_copy_cond(t1[0], t2[0], RLC_FP_DIGS, f); - dv_copy_cond(t1[1], t2[1], RLC_FP_DIGS, f); + fp2_copy_sec(t1, t2, f); /* t1 = 1/g3 or 1/(4*g2), depending on the above. */ fp2_inv(t1, t1); @@ -529,13 +525,11 @@ void fp12_back_cyc_sim(fp12_t c[], const fp12_t a[], int n) { int f = fp2_is_zero(a[i][1][0]); /* If f, t0 = 2 * g4 * g5, t1 = g3. */ fp2_copy(t2[i], a[i][0][1]); - dv_copy_cond(t2[i][0], a[i][1][2][0], RLC_FP_DIGS, f); - dv_copy_cond(t2[i][1], a[i][1][2][1], RLC_FP_DIGS, f); + fp2_copy_sec(t2[i], a[i][1][2], f); /* t0 = g4^2. */ fp2_mul(t0[i], a[i][0][1], t2[i]); fp2_dbl(t2[i], t0[i]); - dv_copy_cond(t0[i][0], t2[i][0], RLC_FP_DIGS, f); - dv_copy_cond(t0[i][1], t2[i][1], RLC_FP_DIGS, f); + fp2_copy_sec(t0[i], t2[i], f); /* t1 = 3 * g4^2 - 2 * g3. */ fp2_sub(t1[i], t0[i], a[i][0][2]); fp2_dbl(t1[i], t1[i]); @@ -547,13 +541,11 @@ void fp12_back_cyc_sim(fp12_t c[], const fp12_t a[], int n) { /* t1 = (4 * g2). */ fp2_dbl(t1[i], a[i][1][0]); fp2_dbl(t1[i], t1[i]); - dv_copy_cond(t1[i][0], a[i][0][2][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][1], a[i][0][2][1], RLC_FP_DIGS, f); + fp2_copy_sec(t1[i], a[i][0][2], f); /* If unity, decompress to unity as well. */ f = (fp12_cmp_dig(a[i], 1) == RLC_EQ); fp2_set_dig(t2[i], 1); - dv_copy_cond(t1[i][0], t2[i][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][1], t2[i][1], RLC_FP_DIGS, f); + fp2_copy_sec(t1[i], t2[i], f); } /* t1 = 1 / t1. */ @@ -1389,15 +1381,11 @@ void fp18_back_cyc(fp18_t c, const fp18_t a) { int f = fp3_is_zero(a[1][0]); /* If f, t0 = 2 * g4 * g5, t1 = g3. */ fp3_copy(t2, a[0][1]); - dv_copy_cond(t2[0], a[1][2][0], RLC_FP_DIGS, f); - dv_copy_cond(t2[1], a[1][2][1], RLC_FP_DIGS, f); - dv_copy_cond(t2[2], a[1][2][2], RLC_FP_DIGS, f); + fp3_copy_sec(t2, a[1][2], f); /* t0 = g4^2. */ fp3_mul(t0, a[0][1], t2); fp3_dbl(t2, t0); - dv_copy_cond(t0[0], t2[0], RLC_FP_DIGS, f); - dv_copy_cond(t0[1], t2[1], RLC_FP_DIGS, f); - dv_copy_cond(t0[2], t2[2], RLC_FP_DIGS, f); + fp3_copy_sec(t0, t2, f); /* t1 = 3 * g4^2 - 2 * g3. */ fp3_sub(t1, t0, a[0][2]); fp3_dbl(t1, t1); @@ -1409,15 +1397,11 @@ void fp18_back_cyc(fp18_t c, const fp18_t a) { /* t1 = (4 * g2). */ fp3_dbl(t1, a[1][0]); fp3_dbl(t1, t1); - dv_copy_cond(t1[0], a[0][2][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[1], a[0][2][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[2], a[0][2][2], RLC_FP_DIGS, f); + fp3_copy_sec(t1, a[0][2], f); /* If unity, decompress to unity as well. */ f = fp18_cmp_dig(a, 1) == RLC_EQ; fp3_set_dig(t2, 1); - dv_copy_cond(t1[0], t2[0], RLC_FP_DIGS, f); - dv_copy_cond(t1[1], t2[1], RLC_FP_DIGS, f); - dv_copy_cond(t1[2], t2[2], RLC_FP_DIGS, f); + fp3_copy_sec(t1, t2, f); /* t1 = 1/g3 or 1/(4 * g2), depending on the above. */ fp3_inv(t1, t1); @@ -1479,15 +1463,11 @@ void fp18_back_cyc_sim(fp18_t c[], const fp18_t a[], int n) { int f = fp3_is_zero(a[i][1][0]); /* If f, t0 = 2 * g4 * g5, t1 = g3. */ fp3_copy(t2[i], a[i][0][1]); - dv_copy_cond(t2[i][0], a[i][1][2][0], RLC_FP_DIGS, f); - dv_copy_cond(t2[i][1], a[i][1][2][1], RLC_FP_DIGS, f); - dv_copy_cond(t2[i][2], a[i][1][2][2], RLC_FP_DIGS, f); + fp3_copy_sec(t2[i], a[i][1][2], f); /* t0 = g4^2. */ fp3_mul(t0[i], a[i][0][1], t2[i]); fp3_dbl(t2[i], t0[i]); - dv_copy_cond(t0[i][0], t2[i][0], RLC_FP_DIGS, f); - dv_copy_cond(t0[i][1], t2[i][1], RLC_FP_DIGS, f); - dv_copy_cond(t0[i][2], t2[i][2], RLC_FP_DIGS, f); + fp3_copy_sec(t0[i], t2[i], f); /* t1 = 3 * g4^2 - 2 * g3. */ fp3_sub(t1[i], t0[i], a[i][0][2]); fp3_dbl(t1[i], t1[i]); @@ -1499,15 +1479,11 @@ void fp18_back_cyc_sim(fp18_t c[], const fp18_t a[], int n) { /* t1 = (4 * g2). */ fp3_dbl(t1[i], a[i][1][0]); fp3_dbl(t1[i], t1[i]); - dv_copy_cond(t1[i][0], a[i][0][2][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][1], a[i][0][2][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][2], a[i][0][2][2], RLC_FP_DIGS, f); + fp3_copy_sec(t1[i], a[i][0][2], f); /* If unity, decompress to unity as well. */ f = (fp18_cmp_dig(a[i], 1) == RLC_EQ); fp3_set_dig(t2[i], 1); - dv_copy_cond(t1[i][0], t2[i][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][1], t2[i][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][2], t2[i][2], RLC_FP_DIGS, f); + fp3_copy_sec(t1[i], t2[i], f); } /* t1 = 1 / t1. */ @@ -2063,17 +2039,11 @@ void fp24_back_cyc(fp24_t c, const fp24_t a) { int f = fp4_is_zero(a[1][0]); /* If f, t0 = 2 * g4 * g5, t1 = g3. */ fp4_copy(t2, a[2][0]); - dv_copy_cond(t2[0][0], a[2][1][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t2[0][1], a[2][1][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t2[1][0], a[2][1][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t2[1][1], a[2][1][1][1], RLC_FP_DIGS, f); + fp4_copy_sec(t2, a[2][1], f); /* t0 = g4^2. */ fp4_mul(t0, a[2][0], t2); fp4_dbl(t2, t0); - dv_copy_cond(t0[0][0], t2[0][0], RLC_FP_DIGS, f); - dv_copy_cond(t0[0][1], t2[0][1], RLC_FP_DIGS, f); - dv_copy_cond(t0[1][0], t2[1][0], RLC_FP_DIGS, f); - dv_copy_cond(t0[1][1], t2[1][1], RLC_FP_DIGS, f); + fp4_copy_sec(t0, t2, f); /* t1 = 3 * g4^2 - 2 * g3. */ fp4_sub(t1, t0, a[1][1]); fp4_dbl(t1, t1); @@ -2085,17 +2055,11 @@ void fp24_back_cyc(fp24_t c, const fp24_t a) { /* t1 = (4 * g2). */ fp4_dbl(t1, a[1][0]); fp4_dbl(t1, t1); - dv_copy_cond(t1[0][0], a[1][1][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[0][1], a[1][1][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[1][0], a[1][1][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[1][1], a[1][1][1][1], RLC_FP_DIGS, f); + fp4_copy_sec(t1, a[1][1], f); /* If unity, decompress to unity as well. */ f = fp24_cmp_dig(a, 1) == RLC_EQ; fp4_set_dig(t2, 1); - dv_copy_cond(t1[0][0], t2[0][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[0][1], t2[0][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[1][0], t2[1][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[1][1], t2[1][1], RLC_FP_DIGS, f); + fp4_copy_sec(t1, t2, f); fp4_inv(t1, t1); /* c_1 = g1. */ @@ -2156,17 +2120,11 @@ void fp24_back_cyc_sim(fp24_t c[], const fp24_t a[], int n) { int f = fp4_is_zero(a[i][1][0]); /* If f, t0 = 2 * g4 * g5, t1 = g3. */ fp4_copy(t2[i], a[i][2][0]); - dv_copy_cond(t2[i][0][0], a[i][2][1][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t2[i][0][1], a[i][2][1][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t2[i][1][0], a[i][2][1][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t2[i][1][1], a[i][2][1][1][1], RLC_FP_DIGS, f); + fp4_copy_sec(t2[i], a[i][2][1], f); /* t0 = g4^2. */ fp4_mul(t0[i], a[i][2][0], t2[i]); fp4_dbl(t2[i], t0[i]); - dv_copy_cond(t0[i][0][0], t2[i][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t0[i][0][1], t2[i][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t0[i][1][0], t2[i][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t0[i][1][1], t2[i][1][1], RLC_FP_DIGS, f); + fp4_copy_sec(t0[i], t2[i], f); /* t1 = 3 * g4^2 - 2 * g3. */ fp4_sub(t1[i], t0[i], a[i][1][1]); fp4_dbl(t1[i], t1[i]); @@ -2178,17 +2136,11 @@ void fp24_back_cyc_sim(fp24_t c[], const fp24_t a[], int n) { /* t1 = (4 * g2). */ fp4_dbl(t1[i], a[i][1][0]); fp4_dbl(t1[i], t1[i]); - dv_copy_cond(t1[i][0][0], a[i][1][1][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][0][1], a[i][1][1][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][1][0], a[i][1][1][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][1][1], a[i][1][1][1][1], RLC_FP_DIGS, f); + fp4_copy_sec(t1[i], a[i][1][1], f); /* If unity, decompress to unity as well. */ f = fp24_cmp_dig(a[i], 1) == RLC_EQ; fp4_set_dig(t2[i], 1); - dv_copy_cond(t1[i][0][0], t2[i][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][0][1], t2[i][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][1][0], t2[i][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][1][1], t2[i][1][1], RLC_FP_DIGS, f); + fp4_copy_sec(t1[i], t2[i], f); } /* t1 = 1 / t1. */ @@ -2692,25 +2644,11 @@ void fp48_back_cyc(fp48_t c, const fp48_t a) { int f = fp8_is_zero(a[1][0]); /* If f, t0 = 2 * g4 * g5, t1 = g3. */ fp8_copy(t2, a[0][1]); - dv_copy_cond(t2[0][0][0], a[1][2][0][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t2[0][0][1], a[1][2][0][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t2[0][1][0], a[1][2][0][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t2[0][1][1], a[1][2][0][1][1], RLC_FP_DIGS, f); - dv_copy_cond(t2[1][0][0], a[1][2][1][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t2[1][0][1], a[1][2][1][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t2[1][1][0], a[1][2][1][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t2[1][1][1], a[1][2][1][1][1], RLC_FP_DIGS, f); + fp8_copy_sec(t2, a[1][2], f); /* t0 = g4^2. */ fp8_mul(t0, a[0][1], t2); fp8_dbl(t2, t0); - dv_copy_cond(t0[0][0][0], t2[0][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t0[0][0][1], t2[0][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t0[0][1][0], t2[0][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t0[0][1][1], t2[0][1][1], RLC_FP_DIGS, f); - dv_copy_cond(t0[1][0][0], t2[1][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t0[1][0][1], t2[1][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t0[1][1][0], t2[1][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t0[1][1][1], t2[1][1][1], RLC_FP_DIGS, f); + fp8_copy_sec(t0, t2, f); /* t1 = 3 * g4^2 - 2 * g3. */ fp8_sub(t1, t0, a[0][2]); fp8_dbl(t1, t1); @@ -2722,25 +2660,11 @@ void fp48_back_cyc(fp48_t c, const fp48_t a) { /* t1 = (4 * g2). */ fp8_dbl(t1, a[1][0]); fp8_dbl(t1, t1); - dv_copy_cond(t1[0][0][0], a[0][2][0][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[0][0][1], a[0][2][0][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[0][1][0], a[0][2][0][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[0][1][1], a[0][2][0][1][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[1][0][0], a[0][2][1][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[1][0][1], a[0][2][1][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[1][1][0], a[0][2][1][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[1][1][1], a[0][2][1][1][1], RLC_FP_DIGS, f); + fp8_copy_sec(t1, a[0][2], f); /* If unity, decompress to unity as well. */ f = fp48_cmp_dig(a, 1) == RLC_EQ; fp8_set_dig(t2, 1); - dv_copy_cond(t1[0][0][0], t2[0][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[0][0][1], t2[0][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[0][1][0], t2[0][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[0][1][1], t2[0][1][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[1][0][0], t2[1][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[1][0][1], t2[1][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[1][1][0], t2[1][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[1][1][1], t2[1][1][1], RLC_FP_DIGS, f); + fp8_copy_sec(t1, t2, f); /* t1 = 1/g3 or 1/(4 * g2), depending on the above. */ fp8_inv(t1, t1); @@ -2802,25 +2726,11 @@ void fp48_back_cyc_sim(fp48_t c[], const fp48_t a[], int n) { int f = fp8_is_zero(a[i][1][0]); /* If f, t0[i] = 2 * g4 * g5, t1[i] = g3. */ fp8_copy(t2[i], a[i][0][1]); - dv_copy_cond(t2[i][0][0][0], a[i][1][2][0][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t2[i][0][0][1], a[i][1][2][0][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t2[i][0][1][0], a[i][1][2][0][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t2[i][0][1][1], a[i][1][2][0][1][1], RLC_FP_DIGS, f); - dv_copy_cond(t2[i][1][0][0], a[i][1][2][1][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t2[i][1][0][1], a[i][1][2][1][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t2[i][1][1][0], a[i][1][2][1][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t2[i][1][1][1], a[i][1][2][1][1][1], RLC_FP_DIGS, f); + fp8_copy_sec(t2[i], a[i][1][2], f); /* t0[i] = g4^2. */ fp8_mul(t0[i], a[i][0][1], t2[i]); fp8_dbl(t2[i], t0[i]); - dv_copy_cond(t0[i][0][0][0], t2[i][0][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t0[i][0][0][1], t2[i][0][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t0[i][0][1][0], t2[i][0][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t0[i][0][1][1], t2[i][0][1][1], RLC_FP_DIGS, f); - dv_copy_cond(t0[i][1][0][0], t2[i][1][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t0[i][1][0][1], t2[i][1][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t0[i][1][1][0], t2[i][1][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t0[i][1][1][1], t2[i][1][1][1], RLC_FP_DIGS, f); + fp8_copy_sec(t0[i], t2[i], f); /* t1[i] = 3 * g4^2 - 2 * g3. */ fp8_sub(t1[i], t0[i], a[i][0][2]); fp8_dbl(t1[i], t1[i]); @@ -2832,25 +2742,11 @@ void fp48_back_cyc_sim(fp48_t c[], const fp48_t a[], int n) { /* t1[i] = (4 * g2). */ fp8_dbl(t1[i], a[i][1][0]); fp8_dbl(t1[i], t1[i]); - dv_copy_cond(t1[i][0][0][0], a[i][0][2][0][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][0][0][1], a[i][0][2][0][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][0][1][0], a[i][0][2][0][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][0][1][1], a[i][0][2][0][1][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][1][0][0], a[i][0][2][1][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][1][0][1], a[i][0][2][1][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][1][1][0], a[i][0][2][1][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][1][1][1], a[i][0][2][1][1][1], RLC_FP_DIGS, f); + fp8_copy_sec(t1[i], a[i][0][2], f); /* If unity, decompress to unity as well. */ f = fp48_cmp_dig(a[i], 1) == RLC_EQ; fp8_set_dig(t2[i], 1); - dv_copy_cond(t1[i][0][0][0], t2[i][0][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][0][0][1], t2[i][0][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][0][1][0], t2[i][0][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][0][1][1], t2[i][0][1][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][1][0][0], t2[i][1][0][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][1][0][1], t2[i][1][0][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][1][1][0], t2[i][1][1][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][1][1][1], t2[i][1][1][1], RLC_FP_DIGS, f); + fp8_copy_sec(t1[i], t2[i], f); } /* t1 = 1 / t1. */ @@ -3353,19 +3249,11 @@ void fp54_back_cyc(fp54_t c, const fp54_t a) { int f = fp9_is_zero(a[1][0]); /* If f, t0 = 2 * g4 * g5, t1 = g3. */ fp9_copy(t2, a[2][0]); - for (int j = 0; j < 3; j++) { - dv_copy_cond(t2[j][0], a[2][1][j][0], RLC_FP_DIGS, f); - dv_copy_cond(t2[j][1], a[2][1][j][1], RLC_FP_DIGS, f); - dv_copy_cond(t2[j][2], a[2][1][j][2], RLC_FP_DIGS, f); - } + fp9_copy_sec(t2, a[2][1], f); /* t0 = g4^2. */ fp9_mul(t0, a[2][0], t2); fp9_dbl(t2, t0); - for (int j = 0; j < 3; j++) { - dv_copy_cond(t0[j][0], t2[j][0], RLC_FP_DIGS, f); - dv_copy_cond(t0[j][1], t2[j][1], RLC_FP_DIGS, f); - dv_copy_cond(t0[j][2], t2[j][2], RLC_FP_DIGS, f); - } + fp9_copy_sec(t0, t2, f); /* t1 = 3 * g4^2 - 2 * g3. */ fp9_sub(t1, t0, a[1][1]); fp9_dbl(t1, t1); @@ -3377,19 +3265,11 @@ void fp54_back_cyc(fp54_t c, const fp54_t a) { /* t1 = (4 * g2). */ fp9_dbl(t1, a[1][0]); fp9_dbl(t1, t1); - for (int j = 0; j < 3; j++) { - dv_copy_cond(t1[j][0], a[1][1][j][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[j][1], a[1][1][j][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[j][2], a[1][1][j][2], RLC_FP_DIGS, f); - } + fp9_copy_sec(t1, a[1][1], f); /* If unity, decompress to unity as well. */ f = fp54_cmp_dig(a, 1) == RLC_EQ; fp9_set_dig(t2, 1); - for (int j = 0; j < 3; j++) { - dv_copy_cond(t1[j][0], t2[j][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[j][1], t2[j][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[j][2], t2[j][2], RLC_FP_DIGS, f); - } + fp9_copy_sec(t1, t2, f); /* t1 = 1/(4 * g2). */ fp9_dbl(t1, a[1][0]); @@ -3453,19 +3333,11 @@ void fp54_back_cyc_sim(fp54_t c[], const fp54_t a[], int n) { int f = fp9_is_zero(a[i][1][0]); /* If f, t0[i] = 2 * g4 * g5, t1[i] = g3. */ fp9_copy(t2[i], a[i][2][0]); - for (int j = 0; j < 3; j++) { - dv_copy_cond(t2[i][j][0], a[i][2][1][j][0], RLC_FP_DIGS, f); - dv_copy_cond(t2[i][j][1], a[i][2][1][j][1], RLC_FP_DIGS, f); - dv_copy_cond(t2[i][j][2], a[i][2][1][j][2], RLC_FP_DIGS, f); - } + fp9_copy_sec(t2[i], a[i][2][1], f); /* t0[i] = g4^2. */ fp9_mul(t0[i], a[i][2][0], t2[i]); fp9_dbl(t2[i], t0[i]); - for (int j = 0; j < 3; j++) { - dv_copy_cond(t0[i][j][0], t2[i][j][0], RLC_FP_DIGS, f); - dv_copy_cond(t0[i][j][1], t2[i][j][1], RLC_FP_DIGS, f); - dv_copy_cond(t0[i][j][2], t2[i][j][2], RLC_FP_DIGS, f); - } + fp9_copy_sec(t0[i], t2[i], f); /* t1[i] = 3 * g4^2 - 2 * g3. */ fp9_sub(t1[i], t0[i], a[i][1][1]); fp9_dbl(t1[i], t1[i]); @@ -3477,19 +3349,11 @@ void fp54_back_cyc_sim(fp54_t c[], const fp54_t a[], int n) { /* t1[i] = (4 * g2). */ fp9_dbl(t1[i], a[i][1][0]); fp9_dbl(t1[i], t1[i]); - for (int j = 0; j < 3; j++) { - dv_copy_cond(t1[i][j][0], a[i][1][1][j][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][j][1], a[i][1][1][j][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][j][2], a[i][1][1][j][2], RLC_FP_DIGS, f); - } + fp9_copy_sec(t1[i], a[i][1][1], f); /* If unity, decompress to unity as well. */ f = fp54_cmp_dig(a[i], 1) == RLC_EQ; fp9_set_dig(t2[i], 1); - for (int j = 0; j < 3; j++) { - dv_copy_cond(t1[i][j][0], t2[i][j][0], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][j][1], t2[i][j][1], RLC_FP_DIGS, f); - dv_copy_cond(t1[i][j][2], t2[i][j][2], RLC_FP_DIGS, f); - } + fp9_copy_sec(t1[i], t2[i], f); } /* t1 = 1 / t1. */ diff --git a/src/fpx/relic_fpx_srt.c b/src/fpx/relic_fpx_srt.c index 574e36825..3b4ec0f6e 100644 --- a/src/fpx/relic_fpx_srt.c +++ b/src/fpx/relic_fpx_srt.c @@ -126,7 +126,7 @@ int fp2_srt(fp2_t c, const fp2_t a) { /* t1 = (a_0 - sqrt(t0)) / 2 */ fp_sub(t1, a[0], t1); fp_hlv(t1, t1); - dv_copy_cond(t0, t1, RLC_FP_DIGS, !fp_is_sqr(t0)); + fp_copy_sec(t0, t1, !fp_is_sqr(t0)); /* Should always be a quadratic residue. */ fp_srt(t2, t0); @@ -244,22 +244,12 @@ int fp3_srt(fp3_t c, const fp3_t a) { fp_mul(t0[0], t3[0], root); fp_mul(t0[1], t3[1], root); fp_mul(t0[2], t3[2], root); - dv_copy_cond(t3[0], t0[0], RLC_FP_DIGS, - fp3_cmp_dig(t2, 1) != RLC_EQ); - dv_copy_cond(t3[1], t0[1], RLC_FP_DIGS, - fp3_cmp_dig(t2, 1) != RLC_EQ); - dv_copy_cond(t3[2], t0[2], RLC_FP_DIGS, - fp3_cmp_dig(t2, 1) != RLC_EQ); + fp3_copy_sec(t3, t0, fp3_cmp_dig(t2, 1) != RLC_EQ); fp_sqr(root, root); fp_mul(t0[0], t1[0], root); fp_mul(t0[1], t1[1], root); fp_mul(t0[2], t1[2], root); - dv_copy_cond(t1[0], t0[0], RLC_FP_DIGS, - fp3_cmp_dig(t2, 1) != RLC_EQ); - dv_copy_cond(t1[1], t0[1], RLC_FP_DIGS, - fp3_cmp_dig(t2, 1) != RLC_EQ); - dv_copy_cond(t1[2], t0[2], RLC_FP_DIGS, - fp3_cmp_dig(t2, 1) != RLC_EQ); + fp3_copy_sec(t1, t0, fp3_cmp_dig(t2, 1) != RLC_EQ); fp3_copy(t2, t1); } break; @@ -311,9 +301,7 @@ int fp3_srt(fp3_t c, const fp3_t a) { fp3_sqr(t0, t3); r = (fp3_cmp(t0, a) == RLC_EQ ? 1 : 0); fp3_zero(c); - dv_copy_cond(c[0], t3[0], RLC_FP_DIGS, r); - dv_copy_cond(c[1], t3[1], RLC_FP_DIGS, r); - dv_copy_cond(c[2], t3[2], RLC_FP_DIGS, r); + fp3_copy_sec(c, t3, r); } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); } RLC_FINALLY { @@ -412,8 +400,7 @@ int fp4_srt(fp4_t c, const fp4_t a) { fp2_sub(t1, a[0], t1); fp_hlv(t1[0], t1[0]); fp_hlv(t1[1], t1[1]); - dv_copy_cond(t0[0], t1[0], RLC_FP_DIGS, !c0); - dv_copy_cond(t0[1], t1[1], RLC_FP_DIGS, !c0); + fp2_copy_sec(t0, t1, !c0); /* Should always be a quadratic residue. */ fp2_srt(t2, t0); /* c_0 = sqrt(t0) */ @@ -523,10 +510,7 @@ int fp8_srt(fp8_t c, const fp8_t a) { fp_hlv(t1[0][1], t1[0][1]); fp_hlv(t1[1][0], t1[1][0]); fp_hlv(t1[1][1], t1[1][1]); - dv_copy_cond(t0[0][0], t1[0][0], RLC_FP_DIGS, !c0); - dv_copy_cond(t0[0][1], t1[0][1], RLC_FP_DIGS, !c0); - dv_copy_cond(t0[1][0], t1[1][0], RLC_FP_DIGS, !c0); - dv_copy_cond(t0[1][1], t1[1][1], RLC_FP_DIGS, !c0); + fp4_copy_sec(t0, t1, !c0); /* Should always be a quadratic residue. */ fp4_srt(t2, t0); /* c_0 = sqrt(t0) */ @@ -640,8 +624,7 @@ int fp16_srt(fp16_t c, const fp16_t a) { for (int j = 0; j < 2; j++) { for (int k = 0; k < 2; k++) { fp_hlv(t1[i][j][k], t1[i][j][k]); - dv_copy_cond(t0[i][j][k], t1[i][j][k], RLC_FP_DIGS, - !c0); + fp_copy_sec(t0[i][j][k], t1[i][j][k], !c0); } } } diff --git a/src/fpx/relic_fpx_util.c b/src/fpx/relic_fpx_util.c index bbc946c41..a006f6582 100644 --- a/src/fpx/relic_fpx_util.c +++ b/src/fpx/relic_fpx_util.c @@ -41,6 +41,11 @@ void fp2_copy(fp2_t c, const fp2_t a) { fp_copy(c[1], a[1]); } +void fp2_copy_sec(fp2_t c, const fp2_t a, dig_t bit) { + fp_copy_sec(c[0], a[0], bit); + fp_copy_sec(c[1], a[1], bit); +} + void fp2_zero(fp2_t a) { fp_zero(a[0]); fp_zero(a[1]); @@ -134,6 +139,12 @@ void fp3_copy(fp3_t c, const fp3_t a) { fp_copy(c[2], a[2]); } +void fp3_copy_sec(fp3_t c, const fp3_t a, dig_t bit) { + fp_copy_sec(c[0], a[0], bit); + fp_copy_sec(c[1], a[1], bit); + fp_copy_sec(c[2], a[2], bit); +} + void fp3_zero(fp3_t a) { fp_zero(a[0]); fp_zero(a[1]); @@ -191,6 +202,11 @@ void fp4_copy(fp4_t c, const fp4_t a) { fp2_copy(c[1], a[1]); } +void fp4_copy_sec(fp4_t c, const fp4_t a, dig_t bit) { + fp2_copy_sec(c[0], a[0], bit); + fp2_copy_sec(c[1], a[1], bit); +} + void fp4_zero(fp4_t a) { fp2_zero(a[0]); fp2_zero(a[1]); @@ -243,6 +259,12 @@ void fp6_copy(fp6_t c, const fp6_t a) { fp2_copy(c[2], a[2]); } +void fp6_copy_sec(fp6_t c, const fp6_t a, dig_t bit) { + fp2_copy_sec(c[0], a[0], bit); + fp2_copy_sec(c[1], a[1], bit); + fp2_copy_sec(c[2], a[2], bit); +} + void fp6_zero(fp6_t a) { fp2_zero(a[0]); fp2_zero(a[1]); @@ -300,6 +322,11 @@ void fp8_copy(fp8_t c, const fp8_t a) { fp4_copy(c[1], a[1]); } +void fp8_copy_sec(fp8_t c, const fp8_t a, dig_t bit) { + fp4_copy_sec(c[0], a[0], bit); + fp4_copy_sec(c[1], a[1], bit); +} + void fp8_zero(fp8_t a) { fp4_zero(a[0]); fp4_zero(a[1]); @@ -360,6 +387,12 @@ void fp9_copy(fp9_t c, const fp9_t a) { fp3_copy(c[2], a[2]); } +void fp9_copy_sec(fp9_t c, const fp9_t a, dig_t bit) { + fp3_copy_sec(c[0], a[0], bit); + fp3_copy_sec(c[1], a[1], bit); + fp3_copy_sec(c[2], a[2], bit); +} + void fp9_zero(fp9_t a) { fp3_zero(a[0]); fp3_zero(a[1]); @@ -417,6 +450,11 @@ void fp12_copy(fp12_t c, const fp12_t a) { fp6_copy(c[1], a[1]); } +void fp12_copy_sec(fp12_t c, const fp12_t a, dig_t bit) { + fp6_copy_sec(c[0], a[0], bit); + fp6_copy_sec(c[1], a[1], bit); +} + void fp12_zero(fp12_t a) { fp6_zero(a[0]); fp6_zero(a[1]); @@ -509,6 +547,11 @@ void fp16_copy(fp16_t c, const fp16_t a) { fp8_copy(c[1], a[1]); } +void fp16_copy_sec(fp16_t c, const fp16_t a, dig_t bit) { + fp8_copy_sec(c[0], a[0], bit); + fp8_copy_sec(c[1], a[1], bit); +} + void fp16_zero(fp16_t a) { fp8_zero(a[0]); fp8_zero(a[1]); @@ -568,6 +611,11 @@ void fp18_copy(fp18_t c, const fp18_t a) { fp9_copy(c[1], a[1]); } +void fp18_copy_sec(fp18_t c, const fp18_t a, dig_t bit) { + fp9_copy_sec(c[0], a[0], bit); + fp9_copy_sec(c[1], a[1], bit); +} + void fp18_zero(fp18_t a) { fp9_zero(a[0]); fp9_zero(a[1]); @@ -661,6 +709,12 @@ void fp24_copy(fp24_t c, const fp24_t a) { fp8_copy(c[2], a[2]); } +void fp24_copy_sec(fp24_t c, const fp24_t a, dig_t bit) { + fp8_copy_sec(c[0], a[0], bit); + fp8_copy_sec(c[1], a[1], bit); + fp8_copy_sec(c[2], a[2], bit); +} + void fp24_zero(fp24_t a) { fp8_zero(a[0]); fp8_zero(a[1]); @@ -759,6 +813,11 @@ void fp48_copy(fp48_t c, const fp48_t a) { fp24_copy(c[1], a[1]); } +void fp48_copy_sec(fp48_t c, const fp48_t a, dig_t bit) { + fp24_copy_sec(c[0], a[0], bit); + fp24_copy_sec(c[1], a[1], bit); +} + void fp48_zero(fp48_t a) { fp24_zero(a[0]); fp24_zero(a[1]); @@ -852,6 +911,12 @@ void fp54_copy(fp54_t c, const fp54_t a) { fp18_copy(c[2], a[2]); } +void fp54_copy_sec(fp54_t c, const fp54_t a, dig_t bit) { + fp18_copy_sec(c[0], a[0], bit); + fp18_copy_sec(c[1], a[1], bit); + fp18_copy_sec(c[2], a[2], bit); +} + void fp54_zero(fp54_t a) { fp18_zero(a[0]); fp18_zero(a[1]); diff --git a/src/pc/relic_pc_exp.c b/src/pc/relic_pc_exp.c index 61bd8ed89..de9d20488 100644 --- a/src/pc/relic_pc_exp.c +++ b/src/pc/relic_pc_exp.c @@ -31,6 +31,157 @@ #include "relic_pc.h" #include "relic_core.h" +/*============================================================================*/ +/* Private definitions */ +/*============================================================================*/ + +/** + * Size of a precomputation table using the double-table comb method. + */ +#define RLC_GT_TABLE (1 << (RLC_WIDTH - 2)) + +/** + * Exponentiates an element from G_T in constant time. + * + * @param[out] c - the result. + * @param[in] a - the element to exponentiate. + * @param[in] b - the exponent. + * @param[in] f - the maximum Frobenius power. + */ +void gt_exp_imp(gt_t c, const gt_t a, const bn_t b, size_t f) { + int8_t c0, n0, *reg = RLC_ALLOCA(int8_t, f * (RLC_FP_BITS + 1)); + int8_t *e = RLC_ALLOCA(int8_t, f), *s = RLC_ALLOCA(int8_t, f); + gt_t q, w, *t = RLC_ALLOCA(gt_t, f * RLC_GT_TABLE); + bn_t n, u, *_b = RLC_ALLOCA(bn_t, f); + size_t l, len, *_l = RLC_ALLOCA(size_t, f); + + if (reg == NULL || e == NULL || t == NULL || _b == NULL || _l == NULL) { + RLC_THROW(ERR_NO_MEMORY); + return; + } + + if (bn_is_zero(b)) { + RLC_FREE(reg); + RLC_FREE(e); + RLC_FREE(s); + RLC_FREE(t); + RLC_FREE(_b); + RLC_FREE(_l); + return gt_set_unity(c); + } + + bn_null(n); + bn_null(u); + gt_null(q); + gt_null(w); + + RLC_TRY { + bn_new(n); + bn_new(u); + gt_new(q); + gt_new(w); + for (size_t i = 0; i < f; i++) { + bn_null(_b[i]); + bn_new(_b[i]); + for (size_t j = 0; j < RLC_GT_TABLE; j++) { + gt_null(t[i * RLC_GT_TABLE + j]); + gt_new(t[i * RLC_GT_TABLE + j]); + } + } + + gt_get_ord(n); + fp_prime_get_par(u); + bn_abs(_b[0], b); + bn_mod(_b[0], _b[0], n); + if (bn_sign(b) == RLC_NEG) { + bn_neg(_b[0], _b[0]); + } + bn_rec_frb(_b, f, _b[0], u, n, ep_curve_is_pairf() == EP_BN); + + l = 0; + len = bn_bits(u) + (ep_curve_is_pairf() == EP_BN); + gt_copy(t[0], a); + for (size_t i = 0; i < f; i++) { + s[i] = bn_sign(_b[i]); + bn_abs(_b[i], _b[i]); + e[i] = bn_is_even(_b[i]); + _b[i]->dp[0] |= e[i]; + + _l[i] = RLC_FP_BITS + 1; + bn_rec_reg(reg + i * (RLC_FP_BITS + 1), &_l[i], _b[i], len, RLC_WIDTH); + l = RLC_MAX(l, _l[i]); + /* Apply Frobenius before flipping sign to build table. */ + if (i > 0) { + gt_frb(t[i * RLC_GT_TABLE], t[(i - 1) * RLC_GT_TABLE], 1); + } + } + + for (size_t i = 0; i < f; i++) { + gt_inv(q, t[i * RLC_GT_TABLE]); + gt_copy_sec(q, t[i * RLC_GT_TABLE], s[i] == RLC_POS); + if (RLC_WIDTH > 2) { + gt_sqr(t[i * RLC_GT_TABLE], q); + gt_mul(t[i * RLC_GT_TABLE + 1], t[i * RLC_GT_TABLE], q); + for (size_t j = 2; j < RLC_GT_TABLE; j++) { + gt_mul(t[i * RLC_GT_TABLE + j], t[i * RLC_GT_TABLE + j - 1], + t[i * (RLC_GT_TABLE)]); + } + } + gt_copy(t[i * RLC_GT_TABLE], q); + } + + gt_set_unity(c); + for (int j = l - 1; j >= 0; j--) { + for (size_t i = 0; i < RLC_WIDTH - 1; i++) { + gt_sqr(c, c); + } + + for (size_t i = 0; i < f; i++) { + n0 = reg[i * (RLC_FP_BITS + 1) + j]; + c0 = (n0 >> 7); + n0 = ((n0 ^ c0) - c0) >> 1; + + for (size_t m = 0; m < RLC_GT_TABLE; m++) { + gt_copy_sec(w, t[i * RLC_GT_TABLE + m], m == n0); + } + + gt_inv(q, w); + gt_copy_sec(q, w, c0 == 0); + gt_mul(c, c, q); + + } + } + + for (size_t i = 0; i < 4; i++) { + /* Tables are built with points already negated, so no need here. */ + gt_inv(q, t[i * RLC_GT_TABLE]); + gt_mul(q, c, q); + gt_copy_sec(c, q, e[i]); + } + } + RLC_CATCH_ANY { + RLC_THROW(ERR_CAUGHT); + } + RLC_FINALLY { + bn_free(n); + bn_free(u); + gt_free(q); + gt_free(w); + for (size_t i = 0; i < f; i++) { + bn_free(_b[i]); + for (size_t j = 0; j < RLC_GT_TABLE; j++) { + gt_free(t[i * RLC_GT_TABLE + j]); + } + } + RLC_FREE(reg); + RLC_FREE(e); + RLC_FREE(s); + RLC_FREE(t); + RLC_FREE(_b); + RLC_FREE(_l); + } +} + /*============================================================================*/ /* Public definitions */ /*============================================================================*/ @@ -157,6 +308,44 @@ void gt_exp(gt_t c, const gt_t a, const bn_t b) { #endif } +void gt_exp_key(gt_t c, const gt_t a, const bn_t b) { + size_t f = 0; + + if (bn_bits(b) <= RLC_DIG) { + gt_exp_dig(c, a, b->dp[0]); + if (bn_sign(b) == RLC_NEG) { + gt_inv(c, c); + } + return; + } + + switch (ep_param_embed()) { + case 1: + case 2: + f = 1; + break; + case 12: + f = 4; + break; + case 18: + f = 6; + break; + case 16: + case 24: + f = 8; + break; + case 48: + f = 16; + break; + } + +#if FP_PRIME <= 1536 + gt_exp_imp(c, a, b, f); +#else + RLC_CAT(RLC_GT_LOWER, exp_monty)(c, a, b); +#endif +} + void gt_exp_dig(gt_t c, const gt_t a, dig_t b) { gt_t s, t; bn_t _b; diff --git a/src/relic_util.c b/src/relic_util.c index 161084893..c69fb5cca 100644 --- a/src/relic_util.c +++ b/src/relic_util.c @@ -138,7 +138,7 @@ size_t util_bits_dig(dig_t a) { return RLC_DIG - arch_lzcnt(a); } -int util_cmp_const(const void *a, const void *b, size_t size) { +int util_cmp_sec(const void *a, const void *b, size_t size) { const uint8_t *_a = (const uint8_t *)a; const uint8_t *_b = (const uint8_t *)b; uint8_t result = 0; diff --git a/src/tmpl/relic_ep_map_tmpl.h b/src/tmpl/relic_ep_map_tmpl.h index 0a514c5aa..ef33391fc 100644 --- a/src/tmpl/relic_ep_map_tmpl.h +++ b/src/tmpl/relic_ep_map_tmpl.h @@ -165,7 +165,7 @@ * Simplified SWU mapping from Section 4 of * "Fast and simple constant-time hashing to the BLS12-381 Elliptic Curve" */ -#define TMPL_MAP_SSWU(CUR, PFX, PTR_TY, COPY_COND) \ +#define TMPL_MAP_SSWU(CUR, PFX, PTR_TY, copy_sec) \ static void CUR##_map_sswu(CUR##_t p, const PFX##_t t) { \ PFX##_t t0, t1, t2, t3; \ ctx_t *ctx = core_get(); \ @@ -197,11 +197,11 @@ const int e1 = PFX##_is_zero(t2); \ PFX##_neg(t3, u); /* t3 = -u */ \ /* exception: -u instead of u^2t^4 + ut^2 */ \ - COPY_COND(t2, t3, e1); \ + copy_sec(t2, t3, e1); \ /* t2 = -1/u or 1/(u^2 * t^4 + u*t^2) */ \ PFX##_inv(t2, t2); \ PFX##_add_dig(t3, t2, 1); /* t3 = 1 + t2 */ \ - COPY_COND(t2, t3, e1 == 0); /* only add 1 if t2 != -1/u */ \ + copy_sec(t2, t3, e1 == 0); /* only add 1 if t2 != -1/u */ \ } \ /* e1 goes out of scope */ \ /* compute x1, g(x1) */ \ @@ -219,8 +219,8 @@ { \ /* try x2, g(x2) */ \ const int e1 = PFX##_is_sqr(p->y); \ - COPY_COND(p->x, t2, e1 == 0); \ - COPY_COND(p->y, t3, e1 == 0); \ + copy_sec(p->x, t2, e1 == 0); \ + copy_sec(p->y, t3, e1 == 0); \ } \ if (!PFX##_srt(p->y, p->y)) { \ RLC_THROW(ERR_NO_VALID); \ @@ -241,7 +241,7 @@ * Shallue--van de Woestijne map, based on the definition from * draft-irtf-cfrg-hash-to-curve-06, Section 6.6.1 */ -#define TMPL_MAP_SVDW(CUR, PFX, PTR_TY, COPY_COND) \ +#define TMPL_MAP_SVDW(CUR, PFX, PTR_TY, copy_sec) \ static void CUR##_map_svdw(CUR##_t p, const PFX##_t t) { \ PFX##_t t1, t2, t3, t4; \ ctx_t *ctx = core_get(); \ @@ -274,10 +274,10 @@ { \ /* compute inv0(t3), i.e., 0 if t3 == 0, 1/t3 otherwise */ \ const int e0 = PFX##_is_zero(t3); \ - COPY_COND(t3, gU, e0); /* g(u) is nonzero */ \ + copy_sec(t3, gU, e0); /* g(u) is nonzero */ \ PFX##_inv(t3, t3); \ PFX##_zero(t4); \ - COPY_COND(t3, t4, e0); \ + copy_sec(t3, t4, e0); \ } \ /* e0 goes out of scope */ \ PFX##_mul(t4, t, t1); \ @@ -291,9 +291,9 @@ const int e0 = PFX##_is_sqr(p->y); \ /* compute x2 and g(x2) */ \ PFX##_add(t4, mUover2, t4); \ - COPY_COND(p->x, t4, e0 == 0); \ + copy_sec(p->x, t4, e0 == 0); \ CUR##_rhs(t1, p); \ - COPY_COND(p->y, t1, e0 == 0); \ + copy_sec(p->y, t1, e0 == 0); \ } \ { \ const int e1 = PFX##_is_sqr(p->y); \ @@ -303,9 +303,9 @@ PFX##_sqr(t1, t1); \ PFX##_mul(t1, t1, c4); \ PFX##_add(t1, t1, u); \ - COPY_COND(p->x, t1, e1 == 0); \ + copy_sec(p->x, t1, e1 == 0); \ CUR##_rhs(t2, p); \ - COPY_COND(p->y, t2, e1 == 0); \ + copy_sec(p->y, t2, e1 == 0); \ } \ if (!PFX##_srt(p->y, p->y)) { \ RLC_THROW(ERR_NO_VALID); \ diff --git a/test/test_dv.c b/test/test_dv.c index c272f33c5..29d4182e0 100644 --- a/test/test_dv.c +++ b/test/test_dv.c @@ -81,17 +81,17 @@ static int copy(void) { } } dv_copy(a, b, RLC_DV_DIGS); - TEST_ASSERT(dv_cmp_const(a, b, RLC_DV_DIGS) == RLC_EQ, end); + TEST_ASSERT(dv_cmp_sec(a, b, RLC_DV_DIGS) == RLC_EQ, end); } TEST_END; TEST_CASE("conditional copy and comparison are consistent") { rand_bytes((uint8_t *)a, RLC_DV_DIGS * sizeof(dig_t)); rand_bytes((uint8_t *)b, RLC_DV_DIGS * sizeof(dig_t)); - dv_copy_cond(a, b, RLC_DV_DIGS, 0); - TEST_ASSERT(dv_cmp_const(a, b, RLC_DV_DIGS) == RLC_NE, end); - dv_copy_cond(a, b, RLC_DV_DIGS, 1); - TEST_ASSERT(dv_cmp_const(a, b, RLC_DV_DIGS) == RLC_EQ, end); + dv_copy_sec(a, b, RLC_DV_DIGS, 0); + TEST_ASSERT(dv_cmp_sec(a, b, RLC_DV_DIGS) == RLC_NE, end); + dv_copy_sec(a, b, RLC_DV_DIGS, 1); + TEST_ASSERT(dv_cmp_sec(a, b, RLC_DV_DIGS) == RLC_EQ, end); } TEST_END; } RLC_CATCH_ANY { @@ -123,8 +123,8 @@ static int swap(void) { rand_bytes((uint8_t *)a, RLC_DV_DIGS * sizeof(dig_t)); rand_bytes((uint8_t *)b, RLC_DV_DIGS * sizeof(dig_t)); dv_copy(c, a, RLC_DV_DIGS); - dv_swap_cond(a, b, RLC_DV_DIGS, 1); - TEST_ASSERT(dv_cmp_const(c, b, RLC_DV_DIGS) == RLC_EQ, end); + dv_swap_sec(a, b, RLC_DV_DIGS, 1); + TEST_ASSERT(dv_cmp_sec(c, b, RLC_DV_DIGS) == RLC_EQ, end); } TEST_END; @@ -133,16 +133,16 @@ static int swap(void) { rand_bytes((uint8_t *)b, RLC_DV_DIGS * sizeof(dig_t)); dv_copy(c, a, RLC_DV_DIGS); dv_copy(d, b, RLC_DV_DIGS); - dv_swap_cond(a, b, RLC_DV_DIGS, 0); - TEST_ASSERT(dv_cmp_const(c, a, RLC_DV_DIGS) == RLC_EQ, end); - TEST_ASSERT(dv_cmp_const(d, b, RLC_DV_DIGS) == RLC_EQ, end); - TEST_ASSERT(dv_cmp_const(c, b, RLC_DV_DIGS) == RLC_NE, end); - TEST_ASSERT(dv_cmp_const(d, a, RLC_DV_DIGS) == RLC_NE, end); - dv_swap_cond(a, b, RLC_DV_DIGS, 1); - TEST_ASSERT(dv_cmp_const(c, b, RLC_DV_DIGS) == RLC_EQ, end); - TEST_ASSERT(dv_cmp_const(d, a, RLC_DV_DIGS) == RLC_EQ, end); - TEST_ASSERT(dv_cmp_const(c, a, RLC_DV_DIGS) == RLC_NE, end); - TEST_ASSERT(dv_cmp_const(d, b, RLC_DV_DIGS) == RLC_NE, end); + dv_swap_sec(a, b, RLC_DV_DIGS, 0); + TEST_ASSERT(dv_cmp_sec(c, a, RLC_DV_DIGS) == RLC_EQ, end); + TEST_ASSERT(dv_cmp_sec(d, b, RLC_DV_DIGS) == RLC_EQ, end); + TEST_ASSERT(dv_cmp_sec(c, b, RLC_DV_DIGS) == RLC_NE, end); + TEST_ASSERT(dv_cmp_sec(d, a, RLC_DV_DIGS) == RLC_NE, end); + dv_swap_sec(a, b, RLC_DV_DIGS, 1); + TEST_ASSERT(dv_cmp_sec(c, b, RLC_DV_DIGS) == RLC_EQ, end); + TEST_ASSERT(dv_cmp_sec(d, a, RLC_DV_DIGS) == RLC_EQ, end); + TEST_ASSERT(dv_cmp_sec(c, a, RLC_DV_DIGS) == RLC_NE, end); + TEST_ASSERT(dv_cmp_sec(d, b, RLC_DV_DIGS) == RLC_NE, end); } TEST_END; } RLC_CATCH_ANY { diff --git a/test/test_fp.c b/test/test_fp.c index a25936fc5..49fa961a3 100644 --- a/test/test_fp.c +++ b/test/test_fp.c @@ -87,6 +87,14 @@ static int util(void) { fp_copy(b, a); TEST_ASSERT(fp_cmp(a, b) == RLC_EQ, end); } + fp_rand(a); + fp_rand(b); + if (fp_cmp(a, b) != RLC_EQ) { + fp_copy_sec(b, a, 0); + TEST_ASSERT(fp_cmp(a, b) != RLC_EQ, end); + fp_copy_sec(b, a, 1); + TEST_ASSERT(fp_cmp(a, b) == RLC_EQ, end); + } } TEST_END; @@ -1145,7 +1153,7 @@ static int cube_root(void) { fp_mul(c, c, a); TEST_ASSERT(fp_crt(b, c), end); if (fp_prime_get_cnr()) { - fp_copy(d, fp_prime_get_crt()); + fp_copy(d, (const dig_t *)fp_prime_get_crt()); while (fp_cmp_dig(d, 1) != RLC_EQ) { fp_copy(c, d); fp_sqr(d, d); diff --git a/test/test_pc.c b/test/test_pc.c index cf06c9300..2da34e8ed 100644 --- a/test/test_pc.c +++ b/test/test_pc.c @@ -1488,18 +1488,21 @@ int exponentiation(void) { TEST_CASE("exponentiation is correct") { gt_rand(a); - gt_rand(b); + gt_exp_dig(b, a, 0); + TEST_ASSERT(gt_is_unity(b), end); + gt_exp_dig(b, a, 1); + TEST_ASSERT(gt_cmp(a, b) == RLC_EQ, end); bn_rand_mod(d, n); + gt_exp(b, a, d); + gt_exp_key(c, a, d); + TEST_ASSERT(gt_cmp(b, c) == RLC_EQ, end); + gt_rand(b); bn_rand_mod(e, n); gt_exp_sim(c, a, d, b, e); gt_exp(a, a, d); gt_exp(b, b, e); gt_mul(b, a, b); TEST_ASSERT(gt_cmp(b, c) == RLC_EQ, end); - gt_exp_dig(b, a, 0); - TEST_ASSERT(gt_is_unity(b), end); - gt_exp_dig(b, a, 1); - TEST_ASSERT(gt_cmp(a, b) == RLC_EQ, end); bn_rand(d, RLC_POS, RLC_DIG); gt_exp(b, a, d); gt_exp_dig(c, a, d->dp[0]); From 427deda7186ad4e216e886eeed9bac47b2628dab Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sat, 30 Mar 2024 16:01:03 +0100 Subject: [PATCH 19/37] Rename function to match new pattern. --- include/relic_pc.h | 11 ++++++++++- src/pc/relic_pc_exp.c | 2 +- test/test_pc.c | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/include/relic_pc.h b/include/relic_pc.h index 9d8df2bed..fb98adb5f 100644 --- a/include/relic_pc.h +++ b/include/relic_pc.h @@ -740,6 +740,15 @@ typedef RLC_CAT(RLC_GT_LOWER, t) gt_t; */ #define g1_mul_sec(R, P, K) RLC_CAT(RLC_G1_LOWER, mul_lwreg)(R, P, K) +/** + * Multiplies an element from G_2 by a secret scalar. Computes R = [k]P. + * + * @param[out] R - the result. + * @param[in] P - the element to multiply. + * @param[in] K - the secret scalar. + */ +#define g2_mul_sec(R, P, K) RLC_CAT(RLC_G1_LOWER, mul_lwreg)(R, P, K) + /** * Multiplies an element from a larger group containing G_1 by a scalar. * Computes R = [k]P. @@ -1046,7 +1055,7 @@ void gt_exp(gt_t c, const gt_t a, const bn_t b); * @param[in] a - the element to exponentiate. * @param[in] b - the integer exponent. */ -void gt_exp_key(gt_t c, const gt_t a, const bn_t b); +void gt_exp_sec(gt_t c, const gt_t a, const bn_t b); /** * Exponentiates an element from G_T by a small integer. Computes c = a^b. diff --git a/src/pc/relic_pc_exp.c b/src/pc/relic_pc_exp.c index de9d20488..8d974380a 100644 --- a/src/pc/relic_pc_exp.c +++ b/src/pc/relic_pc_exp.c @@ -308,7 +308,7 @@ void gt_exp(gt_t c, const gt_t a, const bn_t b) { #endif } -void gt_exp_key(gt_t c, const gt_t a, const bn_t b) { +void gt_exp_sec(gt_t c, const gt_t a, const bn_t b) { size_t f = 0; if (bn_bits(b) <= RLC_DIG) { diff --git a/test/test_pc.c b/test/test_pc.c index 2da34e8ed..6e6aaa169 100644 --- a/test/test_pc.c +++ b/test/test_pc.c @@ -1494,7 +1494,7 @@ int exponentiation(void) { TEST_ASSERT(gt_cmp(a, b) == RLC_EQ, end); bn_rand_mod(d, n); gt_exp(b, a, d); - gt_exp_key(c, a, d); + gt_exp_sec(c, a, d); TEST_ASSERT(gt_cmp(b, c) == RLC_EQ, end); gt_rand(b); bn_rand_mod(e, n); From 0d1e82d7385e3f7ec102db8851f9a7355bce784e Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sat, 30 Mar 2024 16:06:14 +0100 Subject: [PATCH 20/37] Add missing benchmark. --- bench/bench_pc.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bench/bench_pc.c b/bench/bench_pc.c index b161cb91a..eef06d6f3 100755 --- a/bench/bench_pc.c +++ b/bench/bench_pc.c @@ -676,6 +676,14 @@ static void arith(void) { } BENCH_END; + BENCH_RUN("gt_exp_sec") { + gt_rand(a); + pc_get_ord(d); + bn_rand_mod(e, d); + BENCH_ADD(gt_exp_sec(c, a, e)); + } + BENCH_END; + BENCH_RUN("gt_exp_gen") { pc_get_ord(d); bn_rand_mod(e, d); From a4b5c629881ddae9e4e50256752f7e94fc74c6b0 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sat, 30 Mar 2024 16:08:40 +0100 Subject: [PATCH 21/37] More missing benchmarks. --- bench/bench_pc.c | 16 ++++++++++++++++ include/relic_pc.h | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/bench/bench_pc.c b/bench/bench_pc.c index eef06d6f3..ee89804fe 100755 --- a/bench/bench_pc.c +++ b/bench/bench_pc.c @@ -216,6 +216,14 @@ static void arith1(void) { } BENCH_END; + BENCH_RUN("g1_mul_sec") { + bn_rand(k, RLC_POS, bn_bits(n)); + bn_rand_mod(k, n); + g1_rand(p); + BENCH_ADD(g1_mul_sec(q, p, k)); + } + BENCH_END; + BENCH_RUN("g1_mul_gen") { bn_rand(k, RLC_POS, bn_bits(n)); bn_rand_mod(k, n); @@ -468,6 +476,14 @@ static void arith2(void) { } BENCH_END; + BENCH_RUN("g2_mul_sec") { + bn_rand(k, RLC_POS, bn_bits(n)); + bn_rand_mod(k, n); + g2_rand(p); + BENCH_ADD(g2_mul_sec(q, p, k)); + } + BENCH_END; + BENCH_RUN("g2_mul_gen") { bn_rand(k, RLC_POS, bn_bits(n)); bn_rand_mod(k, n); diff --git a/include/relic_pc.h b/include/relic_pc.h index fb98adb5f..45b8b85e0 100644 --- a/include/relic_pc.h +++ b/include/relic_pc.h @@ -747,7 +747,7 @@ typedef RLC_CAT(RLC_GT_LOWER, t) gt_t; * @param[in] P - the element to multiply. * @param[in] K - the secret scalar. */ -#define g2_mul_sec(R, P, K) RLC_CAT(RLC_G1_LOWER, mul_lwreg)(R, P, K) +#define g2_mul_sec(R, P, K) RLC_CAT(RLC_G2_LOWER, mul_lwreg)(R, P, K) /** * Multiplies an element from a larger group containing G_1 by a scalar. From 59a448ae62344de861e1880c101976f394f6a22c Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sat, 30 Mar 2024 16:14:43 +0100 Subject: [PATCH 22/37] Generalize exponentiation to more curves. --- src/pc/relic_pc_exp.c | 68 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/src/pc/relic_pc_exp.c b/src/pc/relic_pc_exp.c index 8d974380a..14b148c03 100644 --- a/src/pc/relic_pc_exp.c +++ b/src/pc/relic_pc_exp.c @@ -35,6 +35,72 @@ /* Private definitions */ /*============================================================================*/ +/** + * Apply Frobenius endomorphism in different pairing-friendly curve families. + * + * @param[in] c - the result. + * @param[in] a - the extension field element to exponentiate. + */ +static void gt_gls(gt_t c, const gt_t a) { + gt_t b; + + gt_null(b); + + RLC_TRY { + gt_new(b); + + switch (ep_curve_is_pairf()) { + case EP_K16: + /* u = (2*p^5 - p) mod r */ + gt_frb(b, a, 1); + gt_frb(c, b, 4); + gt_sqr(c, c); + gt_inv(b, b); + gt_mul(c, c, b); + break; + case EP_N16: + /* u = -p^5 mod r */ + gt_frb(c, a, 5); + gt_inv(c, c); + break; + case EP_SG18: + /* -3*u = (2*p^2 - p^5) mod r */ + gt_frb(b, a, 5); + gt_inv(b, b); + gt_frb(c, a, 2); + gt_sqr(c, c); + gt_mul(c, c, b); + break; + case EP_K18: + /* For KSS18, we have that x = p^4 - 3*p = (p^3 - 3)p mod n. */ + gt_sqr(b, a); + gt_mul(b, b, a); + gt_frb(c, a, 3); + gt_inv(b, b); + gt_mul(c, c, b); + gt_frb(c, c, 1); + break; + case EP_FM18: + /* For FM18, we have that u = (p^4-p) mod r. */ + gt_frb(b, a, 3); + gt_inv(b, b); + gt_mul(c, a, b); + gt_frb(c, c, 1); + gt_inv(c, c); + break; + default: + gt_frb(c, a, 1); + break; + } + } + RLC_CATCH_ANY { + RLC_THROW(ERR_CAUGHT); + } + RLC_FINALLY { + gt_free(b); + } +} + /** * Size of a precomputation table using the double-table comb method. */ @@ -112,7 +178,7 @@ void gt_exp_imp(gt_t c, const gt_t a, const bn_t b, size_t f) { l = RLC_MAX(l, _l[i]); /* Apply Frobenius before flipping sign to build table. */ if (i > 0) { - gt_frb(t[i * RLC_GT_TABLE], t[(i - 1) * RLC_GT_TABLE], 1); + gt_gls(t[i * RLC_GT_TABLE], t[(i - 1) * RLC_GT_TABLE]); } } From 856a0970ef2877a34a0d3ee7c92babfe480a3c25 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sat, 30 Mar 2024 20:54:03 +0100 Subject: [PATCH 23/37] Refactor API for RHS. --- bench/bench_eb.c | 2 +- bench/bench_ed.c | 2 +- include/relic_eb.h | 8 ++--- include/relic_ed.h | 8 ++--- src/eb/relic_eb_map.c | 2 +- src/eb/relic_eb_pck.c | 2 +- src/eb/relic_eb_util.c | 8 ++--- src/ed/relic_ed_util.c | 73 +++++++++++++++++------------------------- 8 files changed, 45 insertions(+), 60 deletions(-) diff --git a/bench/bench_eb.c b/bench/bench_eb.c index 1bd9d94d3..c6b4b945f 100644 --- a/bench/bench_eb.c +++ b/bench/bench_eb.c @@ -116,7 +116,7 @@ static void util(void) { BENCH_RUN("eb_rhs") { eb_rand(p); - BENCH_ADD(eb_rhs(q->x, p)); + BENCH_ADD(eb_rhs(q->x, p->x)); } BENCH_END; BENCH_RUN("eb_tab (4)") { diff --git a/bench/bench_ed.c b/bench/bench_ed.c index f2db37565..8f1f5f939 100644 --- a/bench/bench_ed.c +++ b/bench/bench_ed.c @@ -116,7 +116,7 @@ static void util(void) { BENCH_RUN("ed_rhs") { ed_rand(p); - BENCH_ADD(ed_rhs(q->x, p)); + BENCH_ADD(ed_rhs(q->x, p->x)); } BENCH_END; BENCH_RUN("ed_tab (4)") { diff --git a/include/relic_eb.h b/include/relic_eb.h index 8fbd928b0..1a5da9899 100644 --- a/include/relic_eb.h +++ b/include/relic_eb.h @@ -511,13 +511,13 @@ void eb_rand(eb_t p); void eb_blind(eb_t r, const eb_t p); /** - * Computes the right-hand side of the elliptic curve equation at a certain - * elliptic curve point. + * Computes the right-hand side of the elliptic curve equation at the + * x-coordinate of a certain binary elliptic curve point. * * @param[out] rhs - the result. - * @param[in] p - the point. + * @param[in] x - the x-coordinate of the point. */ -void eb_rhs(fb_t rhs, const eb_t p); +void eb_rhs(fb_t rhs, const fb_t p); /** Tests if a point is in the curve. * diff --git a/include/relic_ed.h b/include/relic_ed.h index abc888f3f..b30d196fe 100644 --- a/include/relic_ed.h +++ b/include/relic_ed.h @@ -321,13 +321,13 @@ void ed_rand(ed_t p); void ed_blind(ed_t r, const ed_t p); /** - * Computes the right-hand side of the elliptic curve equation at a certain - * Edwards elliptic curve point. + * Computes the right-hand side of the elliptic curve equation at the + * x-coordinate of a certain Edwards elliptic curve point. * * @param[out] rhs - the result. - * @param[in] p - the point. + * @param[in] x - the x-coordinate of the point. */ -void ed_rhs(fp_t rhs, const ed_t p); +void ed_rhs(fp_t rhs, const fp_t p); /** * Copies the second argument to the first argument. diff --git a/src/eb/relic_eb_map.c b/src/eb/relic_eb_map.c index bbc61810f..2f87783ae 100644 --- a/src/eb/relic_eb_map.c +++ b/src/eb/relic_eb_map.c @@ -58,7 +58,7 @@ void eb_map(eb_t p, const uint8_t *msg, size_t len) { while (1) { dv_copy(p->x, k->dp, RLC_FB_DIGS); - eb_rhs(t1, p); + eb_rhs(t1, p->x); /* t0 = 1/x1^2. */ fb_sqr(t0, p->x); diff --git a/src/eb/relic_eb_pck.c b/src/eb/relic_eb_pck.c index 3766a2a2a..b012e47e3 100644 --- a/src/eb/relic_eb_pck.c +++ b/src/eb/relic_eb_pck.c @@ -63,7 +63,7 @@ int eb_upk(eb_t r, const eb_t p) { fb_new(t0); fb_new(t1); - eb_rhs(t1, p); + eb_rhs(t1, p->x); fb_sqr(t0, p->x); /* t0 = 1/x1^2. */ diff --git a/src/eb/relic_eb_util.c b/src/eb/relic_eb_util.c index fc44b7096..92316d739 100644 --- a/src/eb/relic_eb_util.c +++ b/src/eb/relic_eb_util.c @@ -78,7 +78,7 @@ void eb_rand(eb_t p) { } } -void eb_rhs(fb_t rhs, const eb_t p) { +void eb_rhs(fb_t rhs, const fb_t x) { fb_t t0, t1; fb_null(t0); @@ -89,9 +89,9 @@ void eb_rhs(fb_t rhs, const eb_t p) { fb_new(t1); /* t0 = x1^2. */ - fb_sqr(t0, p->x); + fb_sqr(t0, x); /* t1 = x1^3. */ - fb_mul(t1, t0, p->x); + fb_mul(t1, t0, x); /* t1 = x1^3 + a * x1^2 + b. */ switch (eb_curve_opt_a()) { @@ -171,7 +171,7 @@ int eb_on_curve(const eb_t p) { eb_norm(t, p); fb_mul(lhs, t->x, t->y); - eb_rhs(t->x, t); + eb_rhs(t->x, t->x); fb_sqr(t->y, t->y); fb_add(lhs, lhs, t->y); r = (fb_cmp(lhs, t->x) == RLC_EQ) || eb_is_infty(p); diff --git a/src/ed/relic_ed_util.c b/src/ed/relic_ed_util.c index 8602a16bd..698780ac5 100644 --- a/src/ed/relic_ed_util.c +++ b/src/ed/relic_ed_util.c @@ -128,61 +128,46 @@ void ed_blind(ed_t r, const ed_t p) { } } -void ed_rhs(fp_t rhs, const ed_t p) { - fp_t t0, t1; - - fp_null(t0); - fp_null(t1); - - RLC_TRY { - fp_new(t0); - fp_new(t1); - - // 1 = a * X^2 + Y^2 - d * X^2 * Y^2 - fp_sqr(t0, p->x); - fp_mul(t0, t0, core_get()->ed_a); - fp_sqr(t1, p->y); - fp_add(t1, t1, t0); - fp_mul(t0, p->x, p->y); - fp_sqr(t0, t0); - fp_mul(t0, t0, core_get()->ed_d); - fp_sub(rhs, t1, t0); - } RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } RLC_FINALLY { - fp_free(t0); - fp_free(t1); - } +void ed_rhs(fp_t rhs, const fp_t x) { + /* y^2 * (d * x^2 - 1) = 1a * x^2 - 1. */ + fp_sqr(rhs, x); + fp_mul(rhs, rhs, core_get()->ed_a); + fp_sub_dig(rhs, rhs, 1); } int ed_on_curve(const ed_t p) { ed_t t; - int r = 0; + int r = 1; ed_null(t); if (fp_is_zero(p->z)) { - r = 0; - } else { - RLC_TRY { - ed_new(t); - ed_norm(t, p); + return 0; + } + + RLC_TRY { + ed_new(t); + ed_norm(t, p); - ed_rhs(t->z, t); + /* Compute y^2 * (d * x^2 - 1) */ #if ED_ADD == EXTND - fp_mul(t->y, t->x, t->y); - r = ((fp_cmp_dig(t->z, 1) == RLC_EQ) && - (fp_cmp(t->y, t->t) == RLC_EQ)) || ed_is_infty(p); -#else - r = (fp_cmp_dig(t->z, 1) == RLC_EQ) || ed_is_infty(p); + fp_mul(t->z, t->x, t->y); + r &= (fp_cmp(t->z, t->t) == RLC_EQ); #endif - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - ed_free(t); - } + fp_sqr(t->z, t->y); + fp_sqr(t->t, t->x); + fp_mul(t->t, t->t, core_get()->ed_d); + fp_sub_dig(t->t, t->t, 1); + fp_mul(t->t, t->t, t->z); + ed_rhs(t->z, t->x); + r &= (fp_cmp(t->t, t->z) == RLC_EQ); + r |= ed_is_infty(p); + } + RLC_CATCH_ANY { + RLC_THROW(ERR_CAUGHT); + } + RLC_FINALLY { + ed_free(t); } return r; } From 184bfd7331d3910ac869e503a4a64f85120ff4a9 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sat, 30 Mar 2024 20:54:48 +0100 Subject: [PATCH 24/37] Change API for general scalar mult to newer one. --- src/ep/relic_ep_mul_cof.c | 2 +- src/epx/relic_ep2_mul_cof.c | 2 +- src/epx/relic_ep3_mul_cof.c | 2 +- src/epx/relic_ep4_mul_cof.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ep/relic_ep_mul_cof.c b/src/ep/relic_ep_mul_cof.c index e80b3b861..2101f6ece 100644 --- a/src/ep/relic_ep_mul_cof.c +++ b/src/ep/relic_ep_mul_cof.c @@ -149,7 +149,7 @@ void ep_mul_cof(ep_t r, const ep_t p) { default: /* multiply by cofactor to get the correct group. */ ep_curve_get_cof(k); - ep_mul_basic(r, p, k); + ep_mul_big(r, p, k); } } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); diff --git a/src/epx/relic_ep2_mul_cof.c b/src/epx/relic_ep2_mul_cof.c index 505e77860..64f54df20 100644 --- a/src/epx/relic_ep2_mul_cof.c +++ b/src/epx/relic_ep2_mul_cof.c @@ -165,7 +165,7 @@ void ep2_mul_cof(ep2_t r, const ep2_t p) { default: /* Now, multiply by cofactor to get the correct group. */ ep2_curve_get_cof(k); - ep2_mul_basic(r, p, k); + ep2_mul_big(r, p, k); break; } } RLC_CATCH_ANY { diff --git a/src/epx/relic_ep3_mul_cof.c b/src/epx/relic_ep3_mul_cof.c index 3c3711ca8..cd3bc1909 100644 --- a/src/epx/relic_ep3_mul_cof.c +++ b/src/epx/relic_ep3_mul_cof.c @@ -317,7 +317,7 @@ void ep3_mul_cof(ep3_t r, const ep3_t p) { default: /* Now, multiply by cofactor to get the correct group. */ ep3_curve_get_cof(k); - ep3_mul_basic(r, p, k); + ep3_mul_big(r, p, k); break; } } RLC_CATCH_ANY { diff --git a/src/epx/relic_ep4_mul_cof.c b/src/epx/relic_ep4_mul_cof.c index b79aa17b6..31fa4a6af 100644 --- a/src/epx/relic_ep4_mul_cof.c +++ b/src/epx/relic_ep4_mul_cof.c @@ -369,7 +369,7 @@ void ep4_mul_cof(ep4_t r, const ep4_t p) { default: /* Now, multiply by cofactor to get the correct group. */ ep4_curve_get_cof(k); - ep4_mul_basic(r, p, k); + ep4_mul_big(r, p, k); break; } } RLC_CATCH_ANY { From c39487b140b2de546e516dfa361602aec1486d07 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sun, 31 Mar 2024 04:06:39 +0200 Subject: [PATCH 25/37] Many documentation fixes and minor API changes. --- bench/bench_ep.c | 2 +- bench/bench_epx.c | 24 +++ bench/bench_fpx.c | 10 +- include/relic_core.h | 2 +- include/relic_ep.h | 8 +- include/relic_epx.h | 327 ++++++++++++++++++++++++----------- include/relic_fpx.h | 17 +- include/relic_pc.h | 7 +- include/relic_pp.h | 34 +++- preset/gmp-pbc-k13072.sh | 2 + src/ep/relic_ep_curve.c | 13 +- src/ep/relic_ep_map.c | 132 +++++++++----- src/ep/relic_ep_pck.c | 2 +- src/ep/relic_ep_util.c | 8 +- src/epx/relic_ep2_add.c | 4 +- src/epx/relic_ep2_cmp.c | 4 +- src/epx/relic_ep2_curve.c | 120 +++++++------ src/epx/relic_ep2_dbl.c | 4 +- src/epx/relic_ep2_frb.c | 4 +- src/epx/relic_ep2_map.c | 321 ++++++++++++++++++++++++---------- src/epx/relic_ep2_mul.c | 118 ++++++------- src/epx/relic_ep2_mul_cof.c | 2 +- src/epx/relic_ep2_mul_fix.c | 2 +- src/epx/relic_ep2_mul_sim.c | 2 +- src/epx/relic_ep2_neg.c | 4 +- src/epx/relic_ep2_norm.c | 4 +- src/epx/relic_ep2_pck.c | 6 +- src/epx/relic_ep2_util.c | 12 +- src/epx/relic_ep3_add.c | 283 ++++++------------------------ src/epx/relic_ep3_cmp.c | 92 ++++++---- src/epx/relic_ep3_curve.c | 47 ++++- src/epx/relic_ep3_dbl.c | 160 ++++------------- src/epx/relic_ep3_frb.c | 4 +- src/epx/relic_ep3_map.c | 4 +- src/epx/relic_ep3_mul.c | 241 +++++++++++++++++++++++++- src/epx/relic_ep3_mul_cof.c | 2 +- src/epx/relic_ep3_mul_fix.c | 2 +- src/epx/relic_ep3_mul_sim.c | 2 +- src/epx/relic_ep3_neg.c | 4 +- src/epx/relic_ep3_norm.c | 60 ++++--- src/epx/relic_ep3_util.c | 21 ++- src/epx/relic_ep4_add.c | 283 ++++++------------------------ src/epx/relic_ep4_cmp.c | 92 ++++++---- src/epx/relic_ep4_curve.c | 65 ++++++- src/epx/relic_ep4_dbl.c | 160 ++++------------- src/epx/relic_ep4_frb.c | 4 +- src/epx/relic_ep4_map.c | 4 +- src/epx/relic_ep4_mul.c | 244 +++++++++++++++++++++++++- src/epx/relic_ep4_mul_cof.c | 2 +- src/epx/relic_ep4_mul_fix.c | 2 +- src/epx/relic_ep4_mul_sim.c | 2 +- src/epx/relic_ep4_neg.c | 4 +- src/epx/relic_ep4_norm.c | 60 ++++--- src/epx/relic_ep4_util.c | 21 ++- src/epx/relic_ep8_add.c | 283 ++++++------------------------ src/epx/relic_ep8_cmp.c | 92 ++++++---- src/epx/relic_ep8_curve.c | 47 ++++- src/epx/relic_ep8_dbl.c | 160 ++++------------- src/epx/relic_ep8_frb.c | 4 +- src/epx/relic_ep8_map.c | 4 +- src/epx/relic_ep8_mul.c | 240 ++++++++++++++++++++++++- src/epx/relic_ep8_mul_cof.c | 2 +- src/epx/relic_ep8_mul_fix.c | 2 +- src/epx/relic_ep8_mul_sim.c | 2 +- src/epx/relic_ep8_neg.c | 4 +- src/epx/relic_ep8_norm.c | 63 ++++--- src/epx/relic_ep8_util.c | 28 +-- src/fpx/relic_fp8_mul.c | 5 + src/fpx/relic_fpx_cyc.c | 102 +++++++++++ src/fpx/relic_fpx_util.c | 20 +-- src/pc/relic_pc_exp.c | 17 +- src/pc/relic_pc_util.c | 2 + src/pp/relic_pp_dbl_k16.c | 48 +---- src/pp/relic_pp_dbl_k8.c | 43 +---- src/pp/relic_pp_map_k12.c | 3 +- src/pp/relic_pp_map_k16.c | 21 ++- src/pp/relic_pp_map_k18.c | 5 +- src/pp/relic_pp_map_k54.c | 1 + src/pp/relic_pp_map_k8.c | 58 +++++++ src/tmpl/relic_ep_map_tmpl.h | 30 ++-- test/test_ep.c | 18 +- test/test_epx.c | 302 +++++++++++++++++++++++++++++++- test/test_fpx.c | 6 +- tools/run-pairings.sh | 42 +++-- 84 files changed, 2841 insertions(+), 1873 deletions(-) create mode 100755 preset/gmp-pbc-k13072.sh diff --git a/bench/bench_ep.c b/bench/bench_ep.c index 54b36f890..26aee2cba 100644 --- a/bench/bench_ep.c +++ b/bench/bench_ep.c @@ -130,7 +130,7 @@ static void util(void) { BENCH_RUN("ep_rhs") { ep_rand(p); - BENCH_ADD(ep_rhs(q->x, p)); + BENCH_ADD(ep_rhs(q->x, p->x)); } BENCH_END; BENCH_RUN("ep_tab (4)") { diff --git a/bench/bench_epx.c b/bench/bench_epx.c index 5896e9822..27e96484c 100644 --- a/bench/bench_epx.c +++ b/bench/bench_epx.c @@ -949,6 +949,14 @@ static void arith3(void) { } BENCH_END; #endif +#if EP_MUL == LWREG || !defined(STRIP) + BENCH_RUN("ep3_mul_lwreg") { + bn_rand_mod(k, n); + ep3_rand(p); + BENCH_ADD(ep3_mul_lwreg(q, p, k)); + } BENCH_END; +#endif + BENCH_RUN("ep3_mul_gen") { bn_rand_mod(k, n); BENCH_ADD(ep3_mul_gen(q, k)); @@ -1443,6 +1451,14 @@ static void arith4(void) { } BENCH_END; #endif +#if EP_MUL == LWREG || !defined(STRIP) + BENCH_RUN("ep4_mul_lwreg") { + bn_rand_mod(k, n); + ep4_rand(p); + BENCH_ADD(ep4_mul_lwreg(q, p, k)); + } BENCH_END; +#endif + BENCH_RUN("ep4_mul_gen") { bn_rand_mod(k, n); BENCH_ADD(ep4_mul_gen(q, k)); @@ -1937,6 +1953,14 @@ static void arith8(void) { } BENCH_END; #endif +#if EP_MUL == LWREG || !defined(STRIP) + BENCH_RUN("ep8_mul_lwreg") { + bn_rand_mod(k, n); + ep8_rand(p); + BENCH_ADD(ep8_mul_lwreg(q, p, k)); + } BENCH_END; +#endif + BENCH_RUN("ep8_mul_gen") { bn_rand_mod(k, n); BENCH_ADD(ep8_mul_gen(q, k)); diff --git a/bench/bench_fpx.c b/bench/bench_fpx.c index f5782659d..4deaa7738 100644 --- a/bench/bench_fpx.c +++ b/bench/bench_fpx.c @@ -1301,13 +1301,13 @@ static void util8(void) { BENCH_RUN("fp8_write_bin") { fp8_rand(a); - BENCH_ADD(fp8_write_bin(bin, sizeof(bin), a)); + BENCH_ADD(fp8_write_bin(bin, sizeof(bin), a, 0)); } BENCH_END; BENCH_RUN("fp8_read_bin") { fp8_rand(a); - fp8_write_bin(bin, sizeof(bin), a); + fp8_write_bin(bin, sizeof(bin), a, 0); BENCH_ADD(fp8_read_bin(a, bin, sizeof(bin))); } BENCH_END; @@ -3153,9 +3153,9 @@ static void util48(void) { } BENCH_END; - BENCH_RUN("fp12_copy_sec (0)") { - fp12_rand(a); - BENCH_ADD(fp12_copy_sec(b, a, 0)); + BENCH_RUN("fp48_copy_sec (0)") { + fp48_rand(a); + BENCH_ADD(fp48_copy_sec(b, a, 0)); } BENCH_END; diff --git a/include/relic_core.h b/include/relic_core.h index 92bbbd580..13805aeb5 100644 --- a/include/relic_core.h +++ b/include/relic_core.h @@ -276,7 +276,7 @@ typedef struct _ctx_t { /** The distinguished non-square used by the mapping function */ fp_st ep_map_u; /** Precomputed constants for hashing. */ - fp_st ep_map_c[5]; + fp_st ep_map_c[7]; #ifdef EP_ENDOM fp_st beta; #if EP_MUL == LWNAF || EP_FIX == COMBS || EP_FIX == LWNAF || EP_SIM == INTER || !defined(STRIP) diff --git a/include/relic_ep.h b/include/relic_ep.h index 4ea236407..2a773f5db 100644 --- a/include/relic_ep.h +++ b/include/relic_ep.h @@ -794,13 +794,13 @@ void ep_rand(ep_t p); void ep_blind(ep_t r, const ep_t p); /** - * Computes the right-hand side of the elliptic curve equation at a certain - * prime elliptic curve point. + * Computes the right-hand side of the elliptic curve equation at the + * x-coordinate of a certain prime elliptic curve point. * * @param[out] rhs - the result. - * @param[in] p - the point. + * @param[in] x - the x-coordinate of the point. */ -void ep_rhs(fp_t rhs, const ep_t p); +void ep_rhs(fp_t rhs, const fp_t x); /** * Tests if a point is in the curve. diff --git a/include/relic_epx.h b/include/relic_epx.h index bd50e7eec..ffd06f2ee 100644 --- a/include/relic_epx.h +++ b/include/relic_epx.h @@ -150,7 +150,7 @@ typedef ep3_st *ep3_t; #endif /** - * Represents an elliptic curve point over a octic extension over a prime + * Represents an elliptic curve point over an octic extension over a prime * field. */ typedef struct { @@ -165,7 +165,7 @@ typedef struct { } ep4_st; /** - * Pointer to an elliptic curve point over a octic extension field. + * Pointer to an elliptic curve point over an octic extension field. */ #if ALLOC == AUTO typedef ep4_st ep4_t[1]; @@ -575,9 +575,11 @@ typedef iso2_st *iso2_t; * @param[in] Q - the second point to add. */ #if EP_ADD == BASIC -#define ep3_add(R, P, Q) ep3_add_basic(R, P, Q); -#elif EP_ADD == PROJC || EP_ADD == JACOB -#define ep3_add(R, P, Q) ep3_add_projc(R, P, Q); +#define ep3_add(R, P, Q) ep3_add_basic(R, P, Q) +#elif EP_ADD == PROJC +#define ep3_add(R, P, Q) ep3_add_projc(R, P, Q) +#elif EP_ADD == JACOB +#define ep3_add(R, P, Q) ep3_add_jacob(R, P, Q) #endif /** @@ -588,9 +590,11 @@ typedef iso2_st *iso2_t; * @param[in] P - the point to double. */ #if EP_ADD == BASIC -#define ep3_dbl(R, P) ep3_dbl_basic(R, P); -#elif EP_ADD == PROJC || EP_ADD == JACOB -#define ep3_dbl(R, P) ep3_dbl_projc(R, P); +#define ep3_dbl(R, P) ep3_dbl_basic(R, P) +#elif EP_ADD == PROJC +#define ep3_dbl(R, P) ep3_dbl_projc(R, P) +#elif EP_ADD == JACOB +#define ep3_dbl(R, P) ep3_dbl_jacob(R, P) #endif /** @@ -607,8 +611,10 @@ typedef iso2_st *iso2_t; #define ep3_mul(R, P, K) ep3_mul_slide(R, P, K) #elif EP_MUL == MONTY #define ep3_mul(R, P, K) ep3_mul_monty(R, P, K) -#elif EP_MUL == LWNAF || EP_MUL == LWREG +#elif EP_MUL == LWNAF #define ep3_mul(R, P, K) ep3_mul_lwnaf(R, P, K) +#elif EP_MUL == LWREG +#define ep3_mul(R, P, K) ep3_mul_lwreg(R, P, K) #endif /** @@ -687,9 +693,11 @@ typedef iso2_st *iso2_t; * @param[in] Q - the second point to add. */ #if EP_ADD == BASIC -#define ep4_add(R, P, Q) ep4_add_basic(R, P, Q); -#elif EP_ADD == PROJC || EP_ADD == JACOB -#define ep4_add(R, P, Q) ep4_add_projc(R, P, Q); +#define ep4_add(R, P, Q) ep4_add_basic(R, P, Q) +#elif EP_ADD == PROJC +#define ep4_add(R, P, Q) ep4_add_projc(R, P, Q) +#elif EP_ADD == JACOB +#define ep4_add(R, P, Q) ep4_add_jacob(R, P, Q) #endif /** @@ -700,9 +708,11 @@ typedef iso2_st *iso2_t; * @param[in] P - the point to double. */ #if EP_ADD == BASIC -#define ep4_dbl(R, P) ep4_dbl_basic(R, P); -#elif EP_ADD == PROJC || EP_ADD == JACOB -#define ep4_dbl(R, P) ep4_dbl_projc(R, P); +#define ep4_dbl(R, P) ep4_dbl_basic(R, P) +#elif EP_ADD == PROJC +#define ep4_dbl(R, P) ep4_dbl_projc(R, P) +#elif EP_ADD == JACOB +#define ep4_dbl(R, P) ep4_dbl_jacob(R, P) #endif /** @@ -719,8 +729,10 @@ typedef iso2_st *iso2_t; #define ep4_mul(R, P, K) ep4_mul_slide(R, P, K) #elif EP_MUL == MONTY #define ep4_mul(R, P, K) ep4_mul_monty(R, P, K) -#elif EP_MUL == LWNAF || EP_MUL == LWREG +#elif EP_MUL == LWNAF #define ep4_mul(R, P, K) ep4_mul_lwnaf(R, P, K) +#elif EP_MUL == LWREG +#define ep4_mul(R, P, K) ep4_mul_lwreg(R, P, K) #endif /** @@ -791,7 +803,7 @@ typedef iso2_st *iso2_t; #define ep4_mul_big(R, P, K) ep4_mul_basic(R, P, K) /** - * Adds two points in an elliptic curve over a octic extension field. + * Adds two points in an elliptic curve over an octic extension field. * Computes R = P + Q. * * @param[out] R - the result. @@ -799,26 +811,30 @@ typedef iso2_st *iso2_t; * @param[in] Q - the second point to add. */ #if EP_ADD == BASIC -#define ep8_add(R, P, Q) ep8_add_basic(R, P, Q); -#elif EP_ADD == PROJC || EP_ADD == JACOB -#define ep8_add(R, P, Q) ep8_add_projc(R, P, Q); +#define ep8_add(R, P, Q) ep8_add_basic(R, P, Q) +#elif EP_ADD == PROJC +#define ep8_add(R, P, Q) ep8_add_projc(R, P, Q) +#elif EP_ADD == JACOB +#define ep8_add(R, P, Q) ep8_add_jacob(R, P, Q) #endif /** - * Doubles a point in an elliptic curve over a octic extension field. + * Doubles a point in an elliptic curve over an octic extension field. * Computes R = 2P. * * @param[out] R - the result. * @param[in] P - the point to double. */ #if EP_ADD == BASIC -#define ep8_dbl(R, P) ep8_dbl_basic(R, P); -#elif EP_ADD == PROJC || EP_ADD == JACOB -#define ep8_dbl(R, P) ep8_dbl_projc(R, P); +#define ep8_dbl(R, P) ep8_dbl_basic(R, P) +#elif EP_ADD == PROJC +#define ep8_dbl(R, P) ep8_dbl_projc(R, P) +#elif EP_ADD == JACOB +#define ep8_dbl(R, P) ep8_dbl_jacob(R, P) #endif /** - * Multiplies a point in an elliptic curve over a octic extension field. + * Multiplies a point in an elliptic curve over an octic extension field. * Computes R = [k]P. * * @param[out] R - the result. @@ -831,13 +847,15 @@ typedef iso2_st *iso2_t; #define ep8_mul(R, P, K) ep8_mul_slide(R, P, K) #elif EP_MUL == MONTY #define ep8_mul(R, P, K) ep8_mul_monty(R, P, K) -#elif EP_MUL == LWNAF || EP_MUL == LWREG +#elif EP_MUL == LWNAF #define ep8_mul(R, P, K) ep8_mul_lwnaf(R, P, K) +#elif EP_MUL == LWREG +#define ep8_mul(R, P, K) ep8_mul_lwreg(R, P, K) #endif /** * Builds a precomputation table for multiplying a fixed prime elliptic point - * over a octic extension. + * over an octic extension. * * @param[out] T - the precomputation table. * @param[in] P - the point to multiply. @@ -854,7 +872,7 @@ typedef iso2_st *iso2_t; #endif /** - * Multiplies a fixed prime elliptic point over a octic extension using a + * Multiplies a fixed prime elliptic point over an octic extension using a * precomputation table. Computes R = [k]P. * * @param[out] R - the result. @@ -893,7 +911,7 @@ typedef iso2_st *iso2_t; #endif /** - * Multiplies a point in an elliptic curve over a octic extension field by + * Multiplies a point in an elliptic curve over an octic extension field by * an unrestricted integer scalar. Computes R = [k]P. * * @param[out] R - the result. @@ -1074,13 +1092,13 @@ void ep2_rand(ep2_t p); void ep2_blind(ep2_t r, const ep2_t p); /** - * Computes the right-hand side of the elliptic curve equation at a certain - * elliptic curve point. + * Computes the right-hand side of the elliptic curve equation at the + * x-coordinate of a certain prime elliptic curve point. * * @param[out] rhs - the result. - * @param[in] p - the point. + * @param[in] x - the x-coordinate of the point. */ -void ep2_rhs(fp2_t rhs, const ep2_t p); +void ep2_rhs(fp2_t rhs, const fp2_t x); /** * Tests if a point is in the curve. @@ -1586,12 +1604,12 @@ void ep2_pck(ep2_t r, const ep2_t p); int ep2_upk(ep2_t r, const ep2_t p); /** - * Initializes the elliptic curve over octic extension. + * Initializes the elliptic curve over cubic extension. */ void ep3_curve_init(void); /** - * Finalizes the elliptic curve over octic extension. + * Finalizes the elliptic curve over cubic extension. */ void ep3_curve_clean(void); @@ -1623,6 +1641,22 @@ int ep3_curve_opt_a(void); */ int ep3_curve_opt_b(void); +/** + * Multiplies a field element by the a-coefficient of the curve. + * + * @param[out] c - the result. + * @param[in] a - the field element to multiply. + */ +void ep3_curve_mul_a(fp3_t c, const fp3_t a); + +/** + * Multiplies a field element by the b-coefficient of the curve. + * + * @param[out] c - the result. + * @param[in] a - the field element to multiply. + */ +void ep3_curve_mul_b(fp3_t c, const fp3_t a); + /** * Tests if the configured elliptic curve is a twist. * @@ -1659,7 +1693,7 @@ void ep3_curve_get_ord(bn_t n); void ep3_curve_get_cof(bn_t h); /** - * Configures an elliptic curve over a octic extension by its coefficients. + * Configures an elliptic curve over a cubic extension by its coefficients. * * @param[in] a - the 'a' coefficient of the curve. * @param[in] b - the 'b' coefficient of the curve. @@ -1725,13 +1759,13 @@ void ep3_rand(ep3_t p); void ep3_blind(ep3_t r, const ep3_t p); /** - * Computes the right-hand side of the elliptic curve equation at a certain - * elliptic curve point. + * Computes the right-hand side of the elliptic curve equation at the + * x-coordinate of a certain prime elliptic curve point. * * @param[out] rhs - the result. - * @param[in] p - the point. + * @param[in] x - the x-coordinate of the point. */ -void ep3_rhs(fp3_t rhs, const ep3_t p); +void ep3_rhs(fp3_t rhs, const fp3_t x); /** * Tests if a point is in the curve. @@ -1758,7 +1792,7 @@ void ep3_print(const ep3_t p); /** * Returns the number of bytes necessary to store a prime elliptic curve point - * over a octic extension with optional point compression. + * over a cubic extension with optional point compression. * * @param[in] a - the prime field element. * @param[in] pack - the flag to indicate compression. @@ -1767,7 +1801,7 @@ void ep3_print(const ep3_t p); int ep3_size_bin(const ep3_t a, int pack); /** - * Reads a prime elliptic curve point over a octic extension from a byte + * Reads a prime elliptic curve point over a cubic extension from a byte * vector in big-endian format. * * @param[out] a - the result. @@ -1779,7 +1813,7 @@ int ep3_size_bin(const ep3_t a, int pack); void ep3_read_bin(ep3_t a, const uint8_t *bin, size_t len); /** - * Writes a prime elliptic curve pointer over a octic extension to a byte + * Writes a prime elliptic curve pointer over a cubic extension to a byte * vector in big-endian format with optional point compression. * * @param[out] bin - the byte vector. @@ -1792,7 +1826,7 @@ void ep3_write_bin(uint8_t *bin, size_t len, const ep3_t a, int pack); /** * Negates a point represented in affine coordinates in an elliptic curve over - * a octic extension. + * a cubic extension. * * @param[out] r - the result. * @param[out] p - the point to negate. @@ -1801,7 +1835,7 @@ void ep3_neg(ep3_t r, const ep3_t p); /** * Adds to points represented in affine coordinates in an elliptic curve over a - * octic extension. + * cubic extension. * * @param[out] r - the result. * @param[in] p - the first point to add. @@ -1811,7 +1845,7 @@ void ep3_add_basic(ep3_t r, const ep3_t p, const ep3_t q); /** * Adds to points represented in affine coordinates in an elliptic curve over a - * octic extension and returns the computed slope. + * cubic extension and returns the computed slope. * * @param[out] r - the result. * @param[out] s - the slope. @@ -1822,7 +1856,7 @@ void ep3_add_slp_basic(ep3_t r, fp3_t s, const ep3_t p, const ep3_t q); /** * Adds two points represented in projective coordinates in an elliptic curve - * over a octic extension. + * over a cubic extension. * * @param[out] r - the result. * @param[in] p - the first point to add. @@ -1830,8 +1864,18 @@ void ep3_add_slp_basic(ep3_t r, fp3_t s, const ep3_t p, const ep3_t q); */ void ep3_add_projc(ep3_t r, const ep3_t p, const ep3_t q); +/** + * Adds two points represented in Jacobian coordinates in an elliptic curve + * over a cubic extension. + * + * @param[out] r - the result. + * @param[in] p - the first point to add. + * @param[in] q - the second point to add. + */ +void ep3_add_jacob(ep3_t r, const ep3_t p, const ep3_t q); + /** - * Subtracts a point in an elliptic curve over a octic extension from + * Subtracts a point in an elliptic curve over a cubic extension from * another. * * @param[out] r - the result. @@ -1842,7 +1886,7 @@ void ep3_sub(ep3_t r, const ep3_t p, const ep3_t q); /** * Doubles a point represented in affine coordinates in an elliptic curve over - * a octic extension. + * a cubic extension. * * @param[out] r - the result. * @param[int] p - the point to double. @@ -1851,7 +1895,7 @@ void ep3_dbl_basic(ep3_t r, const ep3_t p); /** * Doubles a point represented in affine coordinates in an elliptic curve over - * a octic extension and returns the computed slope. + * a cubic extension and returns the computed slope. * * @param[out] r - the result. * @param[out] s - the slope. @@ -1861,13 +1905,22 @@ void ep3_dbl_slp_basic(ep3_t r, fp3_t s, const ep3_t p); /** * Doubles a point represented in projective coordinates in an elliptic curve - * over a octic extension. + * over a cubic extension. * * @param[out] r - the result. * @param[in] p - the point to double. */ void ep3_dbl_projc(ep3_t r, const ep3_t p); +/** + * Doubles a point represented in Jacobian coordinates in an elliptic curve + * over a cubic extension. + * + * @param[out] r - the result. + * @param[in] p - the point to double. + */ +void ep3_dbl_jacob(ep3_t r, const ep3_t p); + /** * Multiplies a prime elliptic point by an integer using the binary method. * @@ -1933,7 +1986,7 @@ void ep3_mul_gen(ep3_t r, const bn_t k); void ep3_mul_dig(ep3_t r, const ep3_t p, const dig_t k); /** - * Multiplies a point in an elliptic curve over a octic extension field by + * Multiplies a point in an elliptic curve over a cubic extension field by * the curve cofactor or a small multiple for which a short vector exists. * In short, it takes a point in the curve to the large prime-order subgroup. * @@ -2166,10 +2219,10 @@ void ep3_map(ep3_t p, const uint8_t *msg, size_t len); /** * Computes a power of the Gailbraith-Lin-Scott homomorphism of a point * represented in affine coordinates on a twisted elliptic curve over a - * octic exension. That is, Psi^i(P) = Twist(P)(Frob^i(unTwist(P)). - * On the trace-zero group of a octic twist, consists of a power of the + * cubic exension. That is, Psi^i(P) = Twist(P)(Frob^i(unTwist(P)). + * On the trace-zero group of a cubic twist, consists of a power of the * Frobenius map of a point represented in affine coordinates in an elliptic - * curve over a octic exension. Computes Frob^i(P) = (p^i)P. + * curve over a cubic exension. Computes Frob^i(P) = (p^i)P. * * @param[out] r - the result in affine coordinates. * @param[in] p - a point in affine coordinates. @@ -2178,7 +2231,7 @@ void ep3_map(ep3_t p, const uint8_t *msg, size_t len); void ep3_frb(ep3_t r, const ep3_t p, int i); /** - * Compresses a point in an elliptic curve over a octic extension. + * Compresses a point in an elliptic curve over a cubic extension. * * @param[out] r - the result. * @param[in] p - the point to compress. @@ -2186,7 +2239,7 @@ void ep3_frb(ep3_t r, const ep3_t p, int i); void ep3_pck(ep3_t r, const ep3_t p); /** - * Decompresses a point in an elliptic curve over a octic extension. + * Decompresses a point in an elliptic curve over a cubic extension. * * @param[out] r - the result. * @param[in] p - the point to decompress. @@ -2195,12 +2248,12 @@ void ep3_pck(ep3_t r, const ep3_t p); int ep3_upk(ep3_t r, const ep3_t p); /** - * Initializes the elliptic curve over octic extension. + * Initializes the elliptic curve over quartic extension. */ void ep4_curve_init(void); /** - * Finalizes the elliptic curve over octic extension. + * Finalizes the elliptic curve over quartic extension. */ void ep4_curve_clean(void); @@ -2232,6 +2285,22 @@ int ep4_curve_opt_a(void); */ int ep4_curve_opt_b(void); +/** + * Multiplies a field element by the a-coefficient of the curve. + * + * @param[out] c - the result. + * @param[in] a - the field element to multiply. + */ +void ep4_curve_mul_a(fp4_t c, const fp4_t a); + +/** + * Multiplies a field element by the b-coefficient of the curve. + * + * @param[out] c - the result. + * @param[in] a - the field element to multiply. + */ +void ep4_curve_mul_b(fp4_t c, const fp4_t a); + /** * Tests if the configured elliptic curve is a twist. * @@ -2268,7 +2337,7 @@ void ep4_curve_get_ord(bn_t n); void ep4_curve_get_cof(bn_t h); /** - * Configures an elliptic curve over a octic extension by its coefficients. + * Configures an elliptic curve over a quartic extension by its coefficients. * * @param[in] a - the 'a' coefficient of the curve. * @param[in] b - the 'b' coefficient of the curve. @@ -2334,13 +2403,13 @@ void ep4_rand(ep4_t p); void ep4_blind(ep4_t r, const ep4_t p); /** - * Computes the right-hand side of the elliptic curve equation at a certain - * elliptic curve point. + * Computes the right-hand side of the elliptic curve equation at the + * x-coordinate of a certain prime elliptic curve point. * * @param[out] rhs - the result. - * @param[in] p - the point. + * @param[in] x - the x-coordinate of the point. */ -void ep4_rhs(fp4_t rhs, const ep4_t p); +void ep4_rhs(fp4_t rhs, const fp4_t x); /** * Tests if a point is in the curve. @@ -2367,7 +2436,7 @@ void ep4_print(const ep4_t p); /** * Returns the number of bytes necessary to store a prime elliptic curve point - * over a octic extension with optional point compression. + * over a quartic extension with optional point compression. * * @param[in] a - the prime field element. * @param[in] pack - the flag to indicate compression. @@ -2376,7 +2445,7 @@ void ep4_print(const ep4_t p); int ep4_size_bin(const ep4_t a, int pack); /** - * Reads a prime elliptic curve point over a octic extension from a byte + * Reads a prime elliptic curve point over a quartic extension from a byte * vector in big-endian format. * * @param[out] a - the result. @@ -2388,7 +2457,7 @@ int ep4_size_bin(const ep4_t a, int pack); void ep4_read_bin(ep4_t a, const uint8_t *bin, size_t len); /** - * Writes a prime elliptic curve pointer over a octic extension to a byte + * Writes a prime elliptic curve pointer over a quartic extension to a byte * vector in big-endian format with optional point compression. * * @param[out] bin - the byte vector. @@ -2401,7 +2470,7 @@ void ep4_write_bin(uint8_t *bin, size_t len, const ep4_t a, int pack); /** * Negates a point represented in affine coordinates in an elliptic curve over - * a octic extension. + * a quartic extension. * * @param[out] r - the result. * @param[out] p - the point to negate. @@ -2410,7 +2479,7 @@ void ep4_neg(ep4_t r, const ep4_t p); /** * Adds to points represented in affine coordinates in an elliptic curve over a - * octic extension. + * quartic extension. * * @param[out] r - the result. * @param[in] p - the first point to add. @@ -2420,7 +2489,7 @@ void ep4_add_basic(ep4_t r, const ep4_t p, const ep4_t q); /** * Adds to points represented in affine coordinates in an elliptic curve over a - * octic extension and returns the computed slope. + * quartic extension and returns the computed slope. * * @param[out] r - the result. * @param[out] s - the slope. @@ -2431,7 +2500,7 @@ void ep4_add_slp_basic(ep4_t r, fp4_t s, const ep4_t p, const ep4_t q); /** * Adds two points represented in projective coordinates in an elliptic curve - * over a octic extension. + * over a quartic extension. * * @param[out] r - the result. * @param[in] p - the first point to add. @@ -2439,8 +2508,18 @@ void ep4_add_slp_basic(ep4_t r, fp4_t s, const ep4_t p, const ep4_t q); */ void ep4_add_projc(ep4_t r, const ep4_t p, const ep4_t q); +/** + * Adds two points represented in Jacobian coordinates in an elliptic curve + * over a quartic extension. + * + * @param[out] r - the result. + * @param[in] p - the first point to add. + * @param[in] q - the second point to add. + */ +void ep4_add_jacob(ep4_t r, const ep4_t p, const ep4_t q); + /** - * Subtracts a point in an elliptic curve over a octic extension from + * Subtracts a point in an elliptic curve over a quartic extension from * another. * * @param[out] r - the result. @@ -2451,7 +2530,7 @@ void ep4_sub(ep4_t r, const ep4_t p, const ep4_t q); /** * Doubles a point represented in affine coordinates in an elliptic curve over - * a octic extension. + * a quartic extension. * * @param[out] r - the result. * @param[int] p - the point to double. @@ -2460,7 +2539,7 @@ void ep4_dbl_basic(ep4_t r, const ep4_t p); /** * Doubles a point represented in affine coordinates in an elliptic curve over - * a octic extension and returns the computed slope. + * a quartic extension and returns the computed slope. * * @param[out] r - the result. * @param[out] s - the slope. @@ -2470,13 +2549,22 @@ void ep4_dbl_slp_basic(ep4_t r, fp4_t s, const ep4_t p); /** * Doubles a point represented in projective coordinates in an elliptic curve - * over a octic extension. + * over a quartic extension. * * @param[out] r - the result. * @param[in] p - the point to double. */ void ep4_dbl_projc(ep4_t r, const ep4_t p); +/** + * Doubles a point represented in Jacobian coordinates in an elliptic curve + * over a quartic extension. + * + * @param[out] r - the result. + * @param[in] p - the point to double. + */ +void ep4_dbl_jacob(ep4_t r, const ep4_t p); + /** * Multiplies a prime elliptic point by an integer using the binary method. * @@ -2543,7 +2631,7 @@ void ep4_mul_dig(ep4_t r, const ep4_t p, const dig_t k); /** - * Multiplies a point in an elliptic curve over a octic extension field by + * Multiplies a point in an elliptic curve over a quartic extension field by * the curve cofactor or a small multiple for which a short vector exists. * In short, it takes a point in the curve to the large prime-order subgroup. * @@ -2765,7 +2853,7 @@ void ep4_norm(ep4_t r, const ep4_t p); void ep4_norm_sim(ep4_t *r, const ep4_t *t, int n); /** - * Maps a byte array to a point in an elliptic curve over a octic extension. + * Maps a byte array to a point in an elliptic curve over a quartic extension. * * @param[out] p - the result. * @param[in] msg - the byte array to map. @@ -2776,10 +2864,10 @@ void ep4_map(ep4_t p, const uint8_t *msg, size_t len); /** * Computes a power of the Gailbraith-Lin-Scott homomorphism of a point * represented in affine coordinates on a twisted elliptic curve over a - * octic exension. That is, Psi^i(P) = Twist(P)(Frob^i(unTwist(P)). - * On the trace-zero group of a octic twist, consists of a power of the + * quartic exension. That is, Psi^i(P) = Twist(P)(Frob^i(unTwist(P)). + * On the trace-zero group of a quartic twist, consists of a power of the * Frobenius map of a point represented in affine coordinates in an elliptic - * curve over a octic exension. Computes Frob^i(P) = (p^i)P. + * curve over a quartic exension. Computes Frob^i(P) = (p^i)P. * * @param[out] r - the result in affine coordinates. * @param[in] p - a point in affine coordinates. @@ -2788,7 +2876,7 @@ void ep4_map(ep4_t p, const uint8_t *msg, size_t len); void ep4_frb(ep4_t r, const ep4_t p, int i); /** - * Compresses a point in an elliptic curve over a octic extension. + * Compresses a point in an elliptic curve over a quartic extension. * * @param[out] r - the result. * @param[in] p - the point to compress. @@ -2796,7 +2884,7 @@ void ep4_frb(ep4_t r, const ep4_t p, int i); void ep4_pck(ep4_t r, const ep4_t p); /** - * Decompresses a point in an elliptic curve over a octic extension. + * Decompresses a point in an elliptic curve over a quartic extension. * * @param[out] r - the result. * @param[in] p - the point to decompress. @@ -2842,6 +2930,22 @@ int ep8_curve_opt_a(void); */ int ep8_curve_opt_b(void); +/** + * Multiplies a field element by the a-coefficient of the curve. + * + * @param[out] c - the result. + * @param[in] a - the field element to multiply. + */ +void ep8_curve_mul_a(fp8_t c, const fp8_t a); + +/** + * Multiplies a field element by the b-coefficient of the curve. + * + * @param[out] c - the result. + * @param[in] a - the field element to multiply. + */ +void ep8_curve_mul_b(fp8_t c, const fp8_t a); + /** * Tests if the configured elliptic curve is a twist. * @@ -2878,7 +2982,7 @@ void ep8_curve_get_ord(bn_t n); void ep8_curve_get_cof(bn_t h); /** - * Configures an elliptic curve over a octic extension by its coefficients. + * Configures an elliptic curve over an octic extension by its coefficients. * * @param[in] a - the 'a' coefficient of the curve. * @param[in] b - the 'b' coefficient of the curve. @@ -2944,13 +3048,13 @@ void ep8_rand(ep8_t p); void ep8_blind(ep8_t r, const ep8_t p); /** - * Computes the right-hand side of the elliptic curve equation at a certain - * elliptic curve point. + * Computes the right-hand side of the elliptic curve equation at the + * x-coordinate of a certain prime elliptic curve point. * * @param[out] rhs - the result. - * @param[in] p - the point. + * @param[in] x - the x-coordinate of the point. */ -void ep8_rhs(fp8_t rhs, const ep8_t p); +void ep8_rhs(fp8_t rhs, const fp8_t x); /** * Tests if a point is in the curve. @@ -2977,7 +3081,7 @@ void ep8_print(const ep8_t p); /** * Returns the number of bytes necessary to store a prime elliptic curve point - * over a octic extension with optional point compression. + * over an octic extension with optional point compression. * * @param[in] a - the prime field element. * @param[in] pack - the flag to indicate compression. @@ -2986,7 +3090,7 @@ void ep8_print(const ep8_t p); int ep8_size_bin(const ep8_t a, int pack); /** - * Reads a prime elliptic curve point over a octic extension from a byte + * Reads a prime elliptic curve point over an octic extension from a byte * vector in big-endian format. * * @param[out] a - the result. @@ -2998,7 +3102,7 @@ int ep8_size_bin(const ep8_t a, int pack); void ep8_read_bin(ep8_t a, const uint8_t *bin, size_t len); /** - * Writes a prime elliptic curve pointer over a octic extension to a byte + * Writes a prime elliptic curve pointer over an octic extension to a byte * vector in big-endian format with optional point compression. * * @param[out] bin - the byte vector. @@ -3011,7 +3115,7 @@ void ep8_write_bin(uint8_t *bin, size_t len, const ep8_t a, int pack); /** * Negates a point represented in affine coordinates in an elliptic curve over - * a octic extension. + * an octic extension. * * @param[out] r - the result. * @param[out] p - the point to negate. @@ -3041,7 +3145,7 @@ void ep8_add_slp_basic(ep8_t r, fp8_t s, const ep8_t p, const ep8_t q); /** * Adds two points represented in projective coordinates in an elliptic curve - * over a octic extension. + * over an octic extension. * * @param[out] r - the result. * @param[in] p - the first point to add. @@ -3049,8 +3153,18 @@ void ep8_add_slp_basic(ep8_t r, fp8_t s, const ep8_t p, const ep8_t q); */ void ep8_add_projc(ep8_t r, const ep8_t p, const ep8_t q); +/** + * Adds two points represented in Jacobian coordinates in an elliptic curve + * over an octic extension. + * + * @param[out] r - the result. + * @param[in] p - the first point to add. + * @param[in] q - the second point to add. + */ +void ep8_add_jacob(ep8_t r, const ep8_t p, const ep8_t q); + /** - * Subtracts a point in an elliptic curve over a octic extension from + * Subtracts a point in an elliptic curve over an octic extension from * another. * * @param[out] r - the result. @@ -3061,7 +3175,7 @@ void ep8_sub(ep8_t r, const ep8_t p, const ep8_t q); /** * Doubles a point represented in affine coordinates in an elliptic curve over - * a octic extension. + * an octic extension. * * @param[out] r - the result. * @param[int] p - the point to double. @@ -3070,7 +3184,7 @@ void ep8_dbl_basic(ep8_t r, const ep8_t p); /** * Doubles a point represented in affine coordinates in an elliptic curve over - * a octic extension and returns the computed slope. + * an octic extension and returns the computed slope. * * @param[out] r - the result. * @param[out] s - the slope. @@ -3080,13 +3194,22 @@ void ep8_dbl_slp_basic(ep8_t r, fp8_t s, const ep8_t p); /** * Doubles a point represented in projective coordinates in an elliptic curve - * over a octic extension. + * over an octic extension. * * @param[out] r - the result. * @param[in] p - the point to double. */ void ep8_dbl_projc(ep8_t r, const ep8_t p); +/** + * Doubles a point represented in Jacobian coordinates in an elliptic curve + * over an octic extension. + * + * @param[out] r - the result. + * @param[in] p - the point to double. + */ +void ep8_dbl_jacob(ep8_t r, const ep8_t p); + /** * Multiplies a prime elliptic point by an integer using the binary method. * @@ -3153,7 +3276,7 @@ void ep8_mul_dig(ep8_t r, const ep8_t p, const dig_t k); /** - * Multiplies a point in an elliptic curve over a octic extension field by + * Multiplies a point in an elliptic curve over an octic extension field by * the curve cofactor or a small multiple for which a short vector exists. * In short, it takes a point in the curve to the large prime-order subgroup. * @@ -3375,7 +3498,7 @@ void ep8_norm(ep8_t r, const ep8_t p); void ep8_norm_sim(ep8_t *r, const ep8_t *t, int n); /** - * Maps a byte array to a point in an elliptic curve over a octic extension. + * Maps a byte array to a point in an elliptic curve over an octic extension. * * @param[out] p - the result. * @param[in] msg - the byte array to map. @@ -3387,9 +3510,9 @@ void ep8_map(ep8_t p, const uint8_t *msg, size_t len); * Computes a power of the Gailbraith-Lin-Scott homomorphism of a point * represented in affine coordinates on a twisted elliptic curve over a * octic exension. That is, Psi^i(P) = Twist(P)(Frob^i(unTwist(P)). - * On the trace-zero group of a octic twist, consists of a power of the + * On the trace-zero group of an octic twist, consists of a power of the * Frobenius map of a point represented in affine coordinates in an elliptic - * curve over a octic exension. Computes Frob^i(P) = (p^i)P. + * curve over an octic exension. Computes Frob^i(P) = (p^i)P. * * @param[out] r - the result in affine coordinates. * @param[in] p - a point in affine coordinates. @@ -3398,7 +3521,7 @@ void ep8_map(ep8_t p, const uint8_t *msg, size_t len); void ep8_frb(ep8_t r, const ep8_t p, int i); /** - * Compresses a point in an elliptic curve over a octic extension. + * Compresses a point in an elliptic curve over an octic extension. * * @param[out] r - the result. * @param[in] p - the point to compress. @@ -3406,7 +3529,7 @@ void ep8_frb(ep8_t r, const ep8_t p, int i); void ep8_pck(ep8_t r, const ep8_t p); /** - * Decompresses a point in an elliptic curve over a octic extension. + * Decompresses a point in an elliptic curve over an octic extension. * * @param[out] r - the result. * @param[in] p - the point to decompress. diff --git a/include/relic_fpx.h b/include/relic_fpx.h index 65fcfd528..f06eb56fe 100644 --- a/include/relic_fpx.h +++ b/include/relic_fpx.h @@ -338,7 +338,7 @@ typedef fp18_t fp54_t[3]; /** * Multiplies a quadratic extension field by the quadratic/cubic non-residue. * Computes C = A * E, where E is a non-square/non-cube in the quadratic - * extension. + * extension field. * * @param[out] C - the result. * @param[in] A - the quadratic extension field element to multiply. @@ -2754,9 +2754,10 @@ void fp8_read_bin(fp8_t a, const uint8_t *bin, size_t len); * @param[out] bin - the byte vector. * @param[in] len - the buffer capacity. * @param[in] a - the extension field element to write. + * @param[in] pack - the flag to indicate compression. * @throw ERR_NO_BUFFER - if the buffer capacity is not correct. */ -void fp8_write_bin(uint8_t *bin, size_t len, const fp8_t a); +void fp8_write_bin(uint8_t *bin, size_t len, const fp8_t a, int pack); /** * Returns the result of a comparison between two octic extension field @@ -2983,6 +2984,18 @@ void fp8_exp_dig(fp8_t c, const fp8_t a, dig_t b); */ void fp8_exp_cyc(fp8_t c, const fp8_t a, const bn_t b); +/** + * Computes a power of two cyclotomic octic extension field elements. + * + * @param[out] e - the result. + * @param[in] a - the first element to exponentiate. + * @param[in] b - the first exponent. + * @param[in] c - the second element to exponentiate. + * @param[in] d - the second exponent. + */ +void fp8_exp_cyc_sim(fp8_t e, const fp8_t a, const bn_t b, const fp8_t c, + const bn_t d); + /** * Computes a power of the Frobenius endomorphism of an octic extension field * element. Computes c = a^p^i. diff --git a/include/relic_pc.h b/include/relic_pc.h index 45b8b85e0..2fe17788e 100644 --- a/include/relic_pc.h +++ b/include/relic_pc.h @@ -84,12 +84,15 @@ #elif FP_PRIME == 330 || FP_PRIME == 765 || FP_PRIME == 766 #define RLC_GT_LOWER fp16_ #define RLC_GT_EMBED 16 +#elif FP_PRIME == 544 +#define RLC_GT_LOWER fp8_ +#define RLC_GT_EMBED 8 #else #define RLC_GT_LOWER fp12_ #define RLC_GT_EMBED 12 #endif -#else +#else /* FP_PRIME >= 1536*/ #define RLC_G1_LOWER ep_ #define RLC_G1_UPPER EP #define RLC_G2_LOWER ep_ @@ -928,7 +931,7 @@ typedef RLC_CAT(RLC_GT_LOWER, t) gt_t; #if FP_PRIME <= 1536 #define gt_frb(C, A, I) RLC_CAT(RLC_GT_LOWER, frb)(C, A, I) #else -#define gt_frb(C, A, I) (A) +#define gt_frb(C, A, I) RLC_CAT(RLC_GT_LOWER, copy)(C, A) #endif /** diff --git a/include/relic_pp.h b/include/relic_pp.h index 2077bb691..541bf2953 100644 --- a/include/relic_pp.h +++ b/include/relic_pp.h @@ -493,7 +493,7 @@ #define pp_map_k1(R, P, Q) pp_map_tatep_k1(R, P, Q) #endif -/**pp_map +/** * Computes a pairing of two prime elliptic curve points defined on an elliptic * curves of embedding degree 2. Computes e(P, Q). * @@ -509,6 +509,16 @@ #define pp_map_k2(R, P, Q) pp_map_tatep_k2(R, P, Q) #endif +/** + * Computes a pairing of two prime elliptic curve points defined on an elliptic + * curves of embedding degree 8. Computes e(P, Q). + * + * @param[out] R - the result. + * @param[in] P - the first elliptic curve point. + * @param[in] Q - the second elliptic curve point. + */ +#define pp_map_k8(R, P, Q) pp_map_oatep_k8(R, P, Q) + /** * Computes a pairing of two prime elliptic curve points defined on an elliptic * curve of embedding degree 12. Computes e(P, Q). @@ -587,6 +597,17 @@ #define pp_map_sim_k2(R, P, Q, M) pp_map_sim_tatep_k2(R, P, Q, M) #endif +/** + * Computes a multi-pairing of elliptic curve points defined on an elliptic + * curve of embedding degree 8. Computes \prod e(P_i, Q_i). + * + * @param[out] R - the result. + * @param[in] P - the first pairing arguments. + * @param[in] Q - the second pairing arguments. + * @param[in] M - the number of pairings to evaluate. + */ +#define pp_map_sim_k8(R, P, Q, M) pp_map_sim_oatep_k8(R, P, Q, M) + /** * Computes a multi-pairing of elliptic curve points defined on an elliptic * curve of embedding degree 12. Computes \prod e(P_i, Q_i). @@ -1520,6 +1541,17 @@ void pp_map_sim_weilp_k2(fp2_t r, const ep_t *p, const ep_t *q, int m); */ void pp_map_oatep_k8(fp8_t r, const ep_t p, const ep2_t q); +/** + * Computes the optimal ate multi-pairing in a parameterized elliptic + * curve with embedding degree 8. + * + * @param[out] r - the result. + * @param[in] q - the first pairing arguments. + * @param[in] p - the second pairing arguments. + * @param[in] m - the number of pairings to evaluate. + */ +void pp_map_sim_oatep_k8(fp8_t r, const ep_t *p, const ep2_t *q, int m); + /** * Computes the Tate pairing of two points in a parameterized elliptic curve * with embedding degree 12. diff --git a/preset/gmp-pbc-k13072.sh b/preset/gmp-pbc-k13072.sh new file mode 100755 index 000000000..cc25fe6fd --- /dev/null +++ b/preset/gmp-pbc-k13072.sh @@ -0,0 +1,2 @@ +#!/bin/sh +cmake -DCHECK=off -DARITH=gmp -DBN_PRECI=3072 -DFP_PRIME=3072 -DFP_QNRES=off -DFP_METHD="BASIC;COMBA;COMBA;MONTY;JMPDS;JMPDS;SLIDE" -DFPX_METHD="INTEG;INTEG;LAZYR" -DPP_METHD="LAZYR;OATEP" -DCFLAGS="-O2 -funroll-loops -fomit-frame-pointer" $1 diff --git a/src/ep/relic_ep_curve.c b/src/ep/relic_ep_curve.c index 6f55f5761..e18fc093c 100644 --- a/src/ep/relic_ep_curve.c +++ b/src/ep/relic_ep_curve.c @@ -171,11 +171,14 @@ static void ep_curve_set_map(void) { fp_mul_dig(c3, c3, 4); /* c3 *= 4 */ } - /* If a = 0, precompute and store a square root of -3. */ - fp_set_dig(c4, 3); - fp_neg(c4, c4); - if (!fp_srt(c4, c4)) { - RLC_THROW(ERR_NO_VALID); + /* If curve is not supersingular, precompute and store sqrt(-3) + * neeed for hashing using the SwiftEC algorithm and variants. */ + if (!ep_curve_is_super()) { + fp_set_dig(c4, 3); + fp_neg(c4, c4); + if (!fp_srt(c4, c4)) { + RLC_THROW(ERR_NO_VALID); + } } } RLC_CATCH_ANY { diff --git a/src/ep/relic_ep_map.c b/src/ep/relic_ep_map.c index d69ea7f4b..669fbc6af 100644 --- a/src/ep/relic_ep_map.c +++ b/src/ep/relic_ep_map.c @@ -57,18 +57,17 @@ TMPL_MAP_ISOGENY_MAP(ep, fp, iso); #endif /* EP_CTMAP */ -#define EP_MAP_copy_sec(O, I, C) dv_copy_sec(O, I, RLC_FP_DIGS, C) /** * Simplified SWU mapping from Section 4 of * "Fast and simple constant-time hashing to the BLS12-381 Elliptic Curve" */ -TMPL_MAP_SSWU(ep, fp, dig_t, EP_MAP_copy_sec); +TMPL_MAP_SSWU(ep, fp, dig_t); /** * Shallue--van de Woestijne map, based on the definition from * draft-irtf-cfrg-hash-to-curve-06, Section 6.6.1 */ -TMPL_MAP_SVDW(ep, fp, dig_t, EP_MAP_copy_sec); +TMPL_MAP_SVDW(ep, fp, dig_t); #undef EP_MAP_copy_sec @@ -180,7 +179,7 @@ void ep_map_basic(ep_t p, const uint8_t *msg, size_t len) { fp_set_dig(p->z, 1); while (1) { - ep_rhs(t0, p); + ep_rhs(t0, p->x); if (fp_smb(t0) == 1) { fp_srt(p->y, t0); @@ -240,15 +239,29 @@ void ep_map_swift(ep_t p, const uint8_t *msg, size_t len) { /* enough space for two field elements plus extra bytes for uniformity */ const size_t len_per_elm = (FP_PRIME + ep_param_level() + 7) / 8; uint8_t s, *pseudo_random_bytes = RLC_ALLOCA(uint8_t, 2 * len_per_elm + 1); - fp_t a, b, c, d, t, u, v, w, y, x1, y1, z1, den[3]; + fp_t a, b, c, d, e, f, t, u, v, w, y, x1, y1, z1, den[3]; ctx_t *ctx = core_get(); bn_t k; + if (ep_curve_is_super()) { + RLC_FREE(pseudo_random_bytes); + RLC_THROW(ERR_NO_CONFIG); + return; + } + + if (ctx->mod18 % 3 == 2) { + RLC_FREE(pseudo_random_bytes); + RLC_THROW(ERR_NO_CONFIG); + return; + } + bn_null(k); fp_null(a); fp_null(b); fp_null(c); fp_null(d); + fp_null(e); + fp_null(f); fp_null(t); fp_null(u); fp_null(v); @@ -267,6 +280,8 @@ void ep_map_swift(ep_t p, const uint8_t *msg, size_t len) { fp_new(b); fp_new(c); fp_new(d); + fp_new(e); + fp_new(f); fp_new(t); fp_new(u); fp_new(v); @@ -288,39 +303,52 @@ void ep_map_swift(ep_t p, const uint8_t *msg, size_t len) { fp_prime_conv(t, k); s = pseudo_random_bytes[2 * len_per_elm] & 1; - fp_copy(a, ep_curve_get_a()); - if (ep_curve_opt_b() == RLC_ZERO) { fp_sqr(a, u); fp_sqr(b, a); fp_mul(c, b, a); - fp_add_dig(c, c, 64); + if (ep_curve_opt_a() == RLC_ONE) { + fp_add_dig(c, c, 64); + } else { + fp_dbl(f, ep_curve_get_a()); + fp_dbl(f, f); + fp_sqr(e, f); + fp_mul(e, e, f); + fp_add(c, c, e); + } fp_sqr(d, t); fp_mul(v, a, d); fp_mul(v, v, u); fp_mul_dig(v, v, 24); - fp_mul(v, v, core_get()->ep_map_c[4]); + fp_mul(v, v, ctx->ep_map_c[4]); - fp_sub_dig(p->x, core_get()->ep_map_c[4], 1); + fp_sub_dig(p->x, ctx->ep_map_c[4], 1); fp_hlv(p->x, p->x); fp_sqr(w, b); fp_mul(y, v, a); - fp_add(y, y, c); - fp_add(y, y, c); - fp_add(y, y, c); - fp_add(y, y, c); + if (ep_curve_opt_a() == RLC_ONE) { + fp_dbl(t, c); + fp_dbl(t, t); + } else { + fp_mul(t, f, c); + } + fp_add(y, y, t); fp_mul(y, y, p->x); fp_add(den[0], c, v); fp_mul(den[0], den[0], u); - fp_mul(den[0], den[0], core_get()->ep_map_c[4]); + fp_mul(den[0], den[0], ctx->ep_map_c[4]); fp_mul(den[0], den[0], p->x); fp_dbl(den[0], den[0]); fp_neg(den[0], den[0]); fp_mul(den[1], den[0], p->x); - fp_sub_dig(den[2], a, 4); + if (ep_curve_opt_a() == RLC_ONE) { + fp_sub_dig(den[2], a, 4); + } else { + fp_sub(den[2], a, f); + } fp_sqr(den[2], den[2]); fp_mul_dig(den[2], den[2], 216); fp_dbl(den[2], den[2]); @@ -332,64 +360,74 @@ void ep_map_swift(ep_t p, const uint8_t *msg, size_t len) { ep_set_infty(p); } else { fp_inv_sim(den, den, 3); - fp_dbl(a, a); - fp_dbl(a, a); - fp_dbl(a, a); - fp_dbl(a, a); - fp_add(y1, a, v); - fp_dbl(y1, y1); - fp_dbl(y1, y1); + if (ep_curve_opt_a() == RLC_ONE) { + fp_dbl(a, a); + fp_dbl(a, a); + fp_dbl(a, a); + fp_dbl(a, a); + fp_add(y1, a, v); + fp_dbl(y1, y1); + fp_dbl(y1, y1); + } else { + fp_mul(y1, f, v); + fp_mul(u, a, e); + fp_add(y1, y1, u); + } fp_add(y1, y1, w); fp_mul(z1, y, p->x); fp_add(x1, y1, z1); fp_add(y1, y1, y); - fp_add(z1, a, b); - fp_add(z1, z1, b); - fp_add(z1, z1, b); - fp_add(z1, z1, b); + if (ep_curve_opt_a() == RLC_ONE) { + fp_dbl(e, b); + fp_dbl(e, e); + fp_add(z1, a, e); + } else { + fp_mul(z1, f, a); + fp_add(z1, z1, b); + fp_mul(z1, z1, f); + } fp_dbl(t, z1); fp_add(z1, z1, t); fp_sub(z1, c, z1); fp_sub(z1, z1, v); fp_mul(z1, z1, v); - fp_dbl(a, a); - fp_dbl(a, a); - fp_dbl(a, a); + if (ep_curve_opt_a() == RLC_ONE) { + fp_dbl(a, a); + fp_dbl(a, a); + fp_dbl(a, a); + fp_set_dig(d, 64); + fp_sqr(d, d); + } else { + fp_dbl(a, u); + fp_sqr(d, e); + } fp_add(a, a, w); fp_mul(u, a, b); fp_sub(z1, u, z1); - fp_set_dig(d, 64); - fp_sqr(d, d); fp_add(z1, z1, d); fp_mul(x1, x1, den[0]); fp_mul(y1, y1, den[1]); fp_mul(z1, z1, den[2]); - fp_sqr(t, x1); - fp_add_dig(t, t, 1); - fp_mul(t, t, x1); - fp_sqr(u, y1); - fp_add_dig(u, u, 1); - fp_mul(u, u, y1); - fp_sqr(v, z1); - fp_add_dig(v, v, 1); - fp_mul(v, v, z1); + ep_rhs(t, x1); + ep_rhs(u, y1); + ep_rhs(v, z1); int c2 = fp_is_sqr(u); int c3 = fp_is_sqr(v); - dv_swap_sec(t, u, RLC_FP_DIGS, c2); - dv_swap_sec(x1, y1, RLC_FP_DIGS, c2); - dv_swap_sec(t, v, RLC_FP_DIGS, c3); - dv_swap_sec(x1, z1, RLC_FP_DIGS, c3); + fp_copy_sec(t, u, c2); + fp_copy_sec(x1, y1, c2); + fp_copy_sec(t, v, c3); + fp_copy_sec(x1, z1, c3); if (!fp_srt(t, t)) { RLC_THROW(ERR_NO_VALID); } fp_neg(u, t); - dv_swap_sec(t, u, RLC_FP_DIGS, fp_is_even(t) ^ s); + fp_copy_sec(t, u, fp_is_even(t) ^ s); fp_copy(p->x, x1); fp_copy(p->y, t); @@ -477,6 +515,8 @@ void ep_map_swift(ep_t p, const uint8_t *msg, size_t len) { fp_free(b); fp_free(c); fp_free(d); + fp_free(e); + fp_free(f); fp_free(t); fp_free(u); fp_free(v); diff --git a/src/ep/relic_ep_pck.c b/src/ep/relic_ep_pck.c index 2aac19239..54f3859b1 100644 --- a/src/ep/relic_ep_pck.c +++ b/src/ep/relic_ep_pck.c @@ -89,7 +89,7 @@ int ep_upk(ep_t r, const ep_t p) { bn_new(halfQ); bn_new(yValue); - ep_rhs(t, p); + ep_rhs(t, p->x); /* t0 = sqrt(x1^3 + a * x1 + b). */ result = fp_srt(t, t); diff --git a/src/ep/relic_ep_util.c b/src/ep/relic_ep_util.c index 0229fa228..a39269949 100644 --- a/src/ep/relic_ep_util.c +++ b/src/ep/relic_ep_util.c @@ -107,7 +107,7 @@ void ep_blind(ep_t r, const ep_t p) { } } -void ep_rhs(fp_t rhs, const ep_t p) { +void ep_rhs(fp_t rhs, const fp_t x) { fp_t t0; fp_null(t0); @@ -116,7 +116,7 @@ void ep_rhs(fp_t rhs, const ep_t p) { fp_new(t0); /* t0 = x1^2. */ - fp_sqr(t0, p->x); + fp_sqr(t0, x); /* t0 = x1^2 + a */ switch (ep_curve_opt_a()) { @@ -142,7 +142,7 @@ void ep_rhs(fp_t rhs, const ep_t p) { } /* t0 = x1^3 + a * x */ - fp_mul(t0, t0, p->x); + fp_mul(t0, t0, x); /* t0 = x1^3 + a * x + b */ switch (ep_curve_opt_b()) { @@ -185,7 +185,7 @@ int ep_on_curve(const ep_t p) { ep_new(t); ep_norm(t, p); - ep_rhs(t->x, t); + ep_rhs(t->x, t->x); fp_sqr(t->y, t->y); r = (fp_cmp(t->x, t->y) == RLC_EQ) || ep_is_infty(p); } RLC_CATCH_ANY { diff --git a/src/epx/relic_ep2_add.c b/src/epx/relic_ep2_add.c index bf5034ffe..599e73373 100644 --- a/src/epx/relic_ep2_add.c +++ b/src/epx/relic_ep2_add.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of addition on prime elliptic curves over quadratic - * extensions. + * Implementation of addition on prime elliptic curves over a quadratic + * extension field. * * @ingroup epx */ diff --git a/src/epx/relic_ep2_cmp.c b/src/epx/relic_ep2_cmp.c index 157e34bd9..1b265c6ab 100644 --- a/src/epx/relic_ep2_cmp.c +++ b/src/epx/relic_ep2_cmp.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of utilities for prime elliptic curves over quadratic - * extensions. + * Implementation of utilities for prime elliptic curves over a quadratic + * extension field. * * @ingroup epx */ diff --git a/src/epx/relic_ep2_curve.c b/src/epx/relic_ep2_curve.c index 9168e7341..137517733 100644 --- a/src/epx/relic_ep2_curve.c +++ b/src/epx/relic_ep2_curve.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of configuration of prime elliptic curves over quadratic - * extensions. + * Implementation of configuration of prime elliptic curves over a quadratic + * extension field. * * @ingroup epx */ @@ -53,8 +53,8 @@ #define BN_P158_Y1 "074866EA7BD0AB4C67C77F70E0467F1FF32D800D" #define BN_P158_R "240000006ED000007FE96000419F59800C9FFD81" #define BN_P158_H "240000006ED000007FEA200041A07F800CA06E0D" -#define BN_P158_MAPU0 "0" -#define BN_P158_MAPU1 "-1" +#define BN_P158_MAPU0 "0" +#define BN_P158_MAPU1 "-1" /** @} */ #endif @@ -70,8 +70,8 @@ #define BN_P254_Y1 "0EBB2B0E7C8B15268F6D4456F5F38D37B09006FFD739C9578A2D1AEC6B3ACE9B" #define BN_P254_R "2523648240000001BA344D8000000007FF9F800000000010A10000000000000D" #define BN_P254_H "2523648240000001BA344D8000000008C2A2800000000016AD00000000000019" -#define BN_P254_MAPU0 "0" -#define BN_P254_MAPU1 "-1" +#define BN_P254_MAPU0 "0" +#define BN_P254_MAPU1 "-1" /** @} */ #endif @@ -87,8 +87,8 @@ #define BN_P256_Y1 "A033144CA161E3E3271624B3F0CC1CE607ACD2CBCE9E9253C732CF3E1016DEE7" #define BN_P256_R "B64000000000FF2F2200000085FD547FD8001F44B6B7F4B7C2BC818F7B6BEF99" #define BN_P256_H "B64000000000FF2F2200000085FD548188001F44B6B9232AC2BC818FB05BDDC5" -#define BN_P256_MAPU0 "0" -#define BN_P256_MAPU1 "-1" +#define BN_P256_MAPU0 "0" +#define BN_P256_MAPU1 "-1" /** @} */ #endif @@ -104,8 +104,8 @@ #define SM9_P256_Y1 "17509B092E845C1266BA0D262CBEE6ED0736A96FA347C8BD856DC76B84EBEB96" #define SM9_P256_R "B640000002A3A6F1D603AB4FF58EC74449F2934B18EA8BEEE56EE19CD69ECF25" #define SM9_P256_H "B640000002A3A6F1D603AB4FF58EC745F9F2934B1C0B51C8E57054B2F003BBD5" -#define SM9_P256_MAPU0 "-1" -#define SM9_P256_MAPU1 "0" +#define SM9_P256_MAPU0 "-1" +#define SM9_P256_MAPU1 "0" /** @} */ #endif @@ -168,8 +168,8 @@ #define BN_P382_Y1 "09CAC2932F3C894CCE129ECBB49AFD4BFF94C10B5DF37AB469E3455F16EFDD304721F689BFF864A92ACB3F4DF52678ED" #define BN_P382_R "24009015183F94892D996CC179C6D1666F82CEFBE47879BB46E4CDA2E2E2281D08DC008E80108252004200000000000D" #define BN_P382_H "24009015183F94892D996CC179C6D1666F82CEFBE47879BC06E64DD9A31E2BEF09B400A08016832A005A000000000019" -#define BN_P382_MAPU0 "0" -#define BN_P382_MAPU1 "-1" +#define BN_P382_MAPU0 "0" +#define BN_P382_MAPU1 "-1" /** @} */ #endif @@ -185,8 +185,8 @@ #define B12_P383_Y1 "1459B073246773B35CBCBFC9318AC47DDC1C284887D1DA4BE13593ACC0E45B748AF4115011A3EA730A7DB21A83562411" #define B12_P383_R "1002001800C00B809C04401C81698B381DE05F095A120D3973B2099EBFEBC0001" #define B12_P383_H "1C78E45562AB68EE641721902ED56C9B45EB1276A5C15CB19C7BF81F7A7B6F22053BAC3422240A3F4A7E3C25D470E7BABDB8A2E1E1793BF9CEBDD1BF2EFE30E5" -#define B12_P383_MAPU0 "0" -#define B12_P383_MAPU1 "1" +#define B12_P383_MAPU0 "0" +#define B12_P383_MAPU1 "1" /** @} */ #endif @@ -202,8 +202,8 @@ #define BN_P446_Y1 "91F93BEB46071DEDF410DC5A7662DD8B4BBC8BE5D3A8662009A4C2C0577F82A2337D208379F21C65F90FE1D90482CC48DEC83BFB8AD8E45" #define BN_P446_R "2400000000000000002400000002D00000000D800000021C00000017A0000000870000000AD400000054C000000156000000126000000061" #define BN_P446_H "2400000000000000002400000002D00000000D800000021C0000001860000000870000000B340000005AC00000016200000013E00000006D" -#define BN_P446_MAPU0 "0" -#define BN_P446_MAPU1 "1" +#define BN_P446_MAPU0 "0" +#define BN_P446_MAPU1 "1" /** @} */ #endif @@ -219,8 +219,8 @@ #define B12_P446_Y1 "10ACDEBE52C1499796809283C38E100C4BA9FA0699696BF9F8647CB18EE632A4DDDAA771A78C031F868FAFA07E8642CCA633FE6F99B744C4" #define B12_P446_R "511B70539F27995B34995830FA4D04C98CCC4C050BC7BB9B0E8D8CA34610428001400040001" #define B12_P446_H "2DAEE3988A06F1BB3444D5780484639BF731D657742BF556901567CEFE5C3DD2555CB74C3560CED298D5147F8E24B79369C54C81E026DD55B51D6A75088593E6A92EFCE555594000638E5" -#define B12_P446_MAPU0 "0" -#define B12_P446_MAPU1 "1" +#define B12_P446_MAPU0 "0" +#define B12_P446_MAPU1 "1" /** @} */ #endif @@ -236,21 +236,21 @@ #define B12_P455_Y1 "24AC244C6F31FDAC1214CA62AB4DB7BA9B5F0D54D56A3D5C680044225C3AAF9815C272A15AA1D28FB6AC9EB7B0BE6916450794A617AFFB4EF9" #define B12_P455_R "10000080000380002E0000F10004F00025E000750001D1000A00000400001C00007FFFFC00001" #define B12_P455_H "1C71C8E38E4C71C8238726432AFAC33B9D05A24A43A2531F1F9DE9B4C688A0B7A2716C48868F7C7D716F8AB8D45A1B9B21F9EB90E83876E4E18AD09613EC7DCAF8B8E238558711C838C718E5" -#define B12_P455_MAPU0 "0" -#define B12_P455_MAPU1 "1" +#define B12_P455_MAPU0 "0" +#define B12_P455_MAPU1 "1" /** @} */ #endif #if defined(EP_ENDOM) && FP_PRIME == 544 /** @{ */ -#define GMT8_P544_A0 "0" -#define GMT8_P544_A1 "2" -#define GMT8_P544_B0 "0" -#define GMT8_P544_B1 "0" -#define GMT8_P544_X0 "1EE84A939A46EAE4287805E2FFABBCED3605B74F5FD7E545F390D2E3D34AD0C60851EA65A18CF92396B9318FCAB3205D79C8D03E928590F719E4086A1C42AF2D40EB6C3A" -#define GMT8_P544_X1 "065F721D63381C07D1BF556D65A3A82B0E969BA031FB3812F49709926D5159324DFB685048D4EAFC914F893A0D5382868D1D252619AA964149258DCC1A86B68775F36A82" -#define GMT8_P544_Y0 "9B80046683050F459F48D7CE4C83992D5B044718326AEE2DD86F3E5557BD240441A763BC40030F21FC3A64483B450330930BAFA62E7B573B565404A9DB2A5BFF7D6994D4" -#define GMT8_P544_Y1 "AB457A64E140799D3D9B77ECEA828957F3AEADB3278FEE32E9AD77F068DD2F95B469607E8CE3CE5705276F869CFB18270C0A8FC8D8AD0D17545F4DCCC9E599853A7BAB76" +#define GMT8_P544_A0 "0" +#define GMT8_P544_A1 "2" +#define GMT8_P544_B0 "0" +#define GMT8_P544_B1 "0" +#define GMT8_P544_X0 "1EE84A939A46EAE4287805E2FFABBCED3605B74F5FD7E545F390D2E3D34AD0C60851EA65A18CF92396B9318FCAB3205D79C8D03E928590F719E4086A1C42AF2D40EB6C3A" +#define GMT8_P544_X1 "065F721D63381C07D1BF556D65A3A82B0E969BA031FB3812F49709926D5159324DFB685048D4EAFC914F893A0D5382868D1D252619AA964149258DCC1A86B68775F36A82" +#define GMT8_P544_Y0 "9B80046683050F459F48D7CE4C83992D5B044718326AEE2DD86F3E5557BD240441A763BC40030F21FC3A64483B450330930BAFA62E7B573B565404A9DB2A5BFF7D6994D4" +#define GMT8_P544_Y1 "AB457A64E140799D3D9B77ECEA828957F3AEADB3278FEE32E9AD77F068DD2F95B469607E8CE3CE5705276F869CFB18270C0A8FC8D8AD0D17545F4DCCC9E599853A7BAB76" #define GMT8_P544_R "FF0060739E18D7594A978B0AB6AE4CE3DBFD52A9D00197603FFFDF0000000101" #define GMT8_P544_H "8A0A079FC7C3D421A4986FAAA10A6593F60BC94E32AEE8067E35551B86D59F0F61BD6A750917746236DD39CB4710BD44B7BE86F0A8E5324DFE802973060F04BF37C018705B78072182D139A8D2BD338A630511676FDAFD47C60DB5393F9DFC96089B1FFB60B78FB2" #define GMT8_P544_MAPU0 "0" @@ -270,8 +270,8 @@ #define BN_P638_Y1 "1A650343ACEF6895FE4EC59B49F40E043DEB05DEF170DFD71B44CAB9496E2EADD034EC0E9238544556902D2D51AB93D224DC757AD720F4DE8ED3BFA4E22DB0ECE92369F681543F23A908A9B319D5FAEF" #define BN_P638_R "23FFFFFDC000000D7FFFFFB8000001D3FFFFF942D000165E3FFF94870000D52FFFFDD0E00008DE55600086550021E555FFFFF54FFFF4EAC000000049800154D9FFFFFFFFFFFFEDA00000000000000061" #define BN_P638_H "23FFFFFDC000000D7FFFFFB8000001D3FFFFF942D000165E3FFF94870000D52FFFFDD0E00008DE562000864F0021E561FFFFF4EFFFF4EC400000004F800160C1FFFFFFFFFFFFEC20000000000000006D" -#define BN_P638_MAPU0 "0" -#define BN_P638_MAPU1 "1" +#define BN_P638_MAPU0 "0" +#define BN_P638_MAPU1 "1" /** @} */ #endif @@ -287,8 +287,8 @@ #define B12_1150_Y1 "3795191221DB4917EEE4B7B85BC7D7CA0C60E82116064463FED0892BA82ACECF905E6DB8083C5F589F04DB80E3203C1B2BEB52ACDED6DF96FC515F36761E7152AEED13369A504FE38C4FF93860B89550" #define B12_1150_R "50F94035FF4000FFFFFFFFFFF9406BFDC0040000000000000035FB801DFFBFFFFFFFFFFFFFFF401BFF80000000000000000000FFC01" #define B12_1150_H "2D88688DBA18275F5801BFFD4DDE93725697788C46C7B4BC8050639BA17EA2158B6784CCACDDECE490643943E5376D29C71C96B894056CCCC13C3DC6AAAAAA0F89601DC2979B3721C71C71C8B38CB8AEFEEB9E1C71C71C71C4B9FED17AE51B8E38E38E38E38FC954E8C65" -#define B12_1150_MAPU0 "0" -#define B12_1150_MAPU1 "1" +#define B12_1150_MAPU0 "0" +#define B12_1150_MAPU1 "1" /** @} */ #endif @@ -304,8 +304,8 @@ #define B12_P638_Y1 "3795191221DB4917EEE4B7B85BC7D7CA0C60E82116064463FED0892BA82ACECF905E6DB8083C5F589F04DB80E3203C1B2BEB52ACDED6DF96FC515F36761E7152AEED13369A504FE38C4FF93860B89550" #define B12_P638_R "50F94035FF4000FFFFFFFFFFF9406BFDC0040000000000000035FB801DFFBFFFFFFFFFFFFFFF401BFF80000000000000000000FFC01" #define B12_P638_H "2D88688DBA18275F5801BFFD4DDE93725697788C46C7B4BC8050639BA17EA2158B6784CCACDDECE490643943E5376D29C71C96B894056CCCC13C3DC6AAAAAA0F89601DC2979B3721C71C71C8B38CB8AEFEEB9E1C71C71C71C4B9FED17AE51B8E38E38E38E38FC954E8C65" -#define B12_P638_MAPU0 "0" -#define B12_P638_MAPU1 "1" +#define B12_P638_MAPU0 "0" +#define B12_P638_MAPU1 "1" /** @} */ #endif @@ -659,24 +659,44 @@ int ep2_curve_opt_b(void) { void ep2_curve_mul_a(fp2_t c, const fp2_t a) { ctx_t *ctx = core_get(); - switch (ctx->ep2_opt_a) { - case RLC_ZERO: - fp2_zero(c); - break; - case RLC_ONE: - fp2_copy(c, a); - break; - case RLC_TWO: - fp2_dbl(c, a); - break; + + if (ep2_curve_is_twist() == RLC_EP_MTYPE) { + switch (ep_curve_opt_a()) { + case RLC_ZERO: + fp2_zero(c); + break; + case RLC_ONE: + fp2_copy(c, a); + break; + case RLC_TWO: + fp2_dbl(c, a); + break; + default: + fp_mul(c[0], a[0], ep_curve_get_a()); + fp_mul(c[1], a[1], ep_curve_get_a()); + break; + } + fp2_mul_art(c, c); + } else { + switch (ctx->ep2_opt_a) { + case RLC_ZERO: + fp2_zero(c); + break; + case RLC_ONE: + fp2_copy(c, a); + break; + case RLC_TWO: + fp2_dbl(c, a); + break; #if FP_RDC != MONTY - case RLC_TINY: - fp2_mul_dig(c, a, ctx->ep2_a[0]); - break; + case RLC_TINY: + fp2_mul_dig(c, a, ctx->ep2_a[0]); + break; #endif - default: - fp2_mul(c, a, ctx->ep2_a); - break; + default: + fp2_mul(c, a, ctx->ep2_a); + break; + } } } diff --git a/src/epx/relic_ep2_dbl.c b/src/epx/relic_ep2_dbl.c index ca5bec8db..b8ab8f07b 100644 --- a/src/epx/relic_ep2_dbl.c +++ b/src/epx/relic_ep2_dbl.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of doubling on elliptic prime curves over quadratic - * extensions. + * Implementation of doubling on elliptic prime curves over a quadratic + * extension field. * * @ingroup epx */ diff --git a/src/epx/relic_ep2_frb.c b/src/epx/relic_ep2_frb.c index b43695e04..2b330927f 100644 --- a/src/epx/relic_ep2_frb.c +++ b/src/epx/relic_ep2_frb.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of frobenius action on prime elliptic curves over - * quadratic extensions. + * Implementation of frobenius action on prime elliptic curves over a quadratic + * extension field. * * @ingroup epx */ diff --git a/src/epx/relic_ep2_map.c b/src/epx/relic_ep2_map.c index de5aa5c34..db7a8694f 100644 --- a/src/epx/relic_ep2_map.c +++ b/src/epx/relic_ep2_map.c @@ -25,7 +25,7 @@ * @file * * Implementation of hashing to a prime elliptic curve over a quadratic - * extension. + * extension field. * * @ingroup epx */ @@ -58,19 +58,12 @@ TMPL_MAP_ISOGENY_MAP(ep2, fp2, iso2) /** * Simplified SWU mapping. */ -#define EP2_MAP_copy_sec(O, I, C) \ - do { \ - fp_copy_sec(O[0], I[0], C); \ - fp_copy_sec(O[1], I[1], C); \ - } while (0) -TMPL_MAP_SSWU(ep2, fp2, fp_t, EP2_MAP_copy_sec) +TMPL_MAP_SSWU(ep2, fp2, fp_t) /** * Shallue--van de Woestijne map. */ -TMPL_MAP_SVDW(ep2, fp2, fp_t, EP2_MAP_copy_sec) - -#undef EP2_MAP_copy_sec +TMPL_MAP_SVDW(ep2, fp2, fp_t) /* caution: this function overwrites k, which it uses as an auxiliary variable */ static inline int fp2_sgn0(const fp2_t t, bn_t k) { @@ -200,7 +193,7 @@ void ep2_map_basic(ep2_t p, const uint8_t *msg, size_t len) { fp2_set_dig(p->z, 1); while (1) { - ep2_rhs(t0, p); + ep2_rhs(t0, p->x); if (fp2_is_sqr(t0) == 1) { fp2_srt(p->y, t0); @@ -256,12 +249,18 @@ void ep2_map_swift(ep2_t p, const uint8_t *msg, size_t len) { /* enough space for two field elements plus extra bytes for uniformity */ const size_t elm = (FP_PRIME + ep_param_level() + 7) / 8; uint8_t t0z, t0, t1, sign, *r = RLC_ALLOCA(uint8_t, 4 * elm + 1); - fp2_t t, u, v, w, y, x1, y1, z1; + fp2_t a, b, c, d, e, f, t, u, v, w, y, x1, y1, z1, den[3]; ctx_t *ctx = core_get(); dig_t c2, c3; bn_t k; bn_null(k); + fp2_null(a); + fp2_null(b); + fp2_null(c); + fp2_null(d); + fp2_null(e); + fp2_null(f); fp2_null(t); fp2_null(u); fp2_null(v); @@ -273,6 +272,12 @@ void ep2_map_swift(ep2_t p, const uint8_t *msg, size_t len) { RLC_TRY { bn_new(k); + fp2_new(a); + fp2_new(b); + fp2_new(c); + fp2_new(d); + fp2_new(e); + fp2_new(f); fp2_new(t); fp2_new(u); fp2_new(v); @@ -295,85 +300,227 @@ void ep2_map_swift(ep2_t p, const uint8_t *msg, size_t len) { sign = r[0] & 1; r -= 4 * elm; - /* Assume that a = 0. */ - fp2_sqr(x1, u); - fp2_mul(x1, x1, u); - fp2_sqr(y1, t); - fp2_add(x1, x1, ctx->ep2_b); - fp2_sub(x1, x1, y1); - fp2_dbl(y1, y1); - fp2_add(y1, y1, x1); - fp2_copy(z1, u); - fp_mul(z1[0], z1[0], ctx->ep_map_c[4]); - fp_mul(z1[1], z1[1], ctx->ep_map_c[4]); - fp2_mul(x1, x1, z1); - fp2_mul(z1, z1, t); - fp2_dbl(z1, z1); - - fp2_dbl(y, y1); - fp2_sqr(y, y); - fp2_mul(v, y1, u); - fp2_sub(v, x1, v); - fp2_mul(v, v, z1); - fp2_mul(w, y1, z1); - fp2_dbl(w, w); - - if (fp2_is_zero(w)) { - ep2_set_infty(p); - } else { - fp2_inv(w, w); - fp2_mul(x1, v, w); - fp2_add(y1, u, x1); - fp2_neg(y1, y1); - fp2_mul(z1, y, w); - fp2_sqr(z1, z1); - fp2_add(z1, z1, u); - - fp2_sqr(t, x1); - fp2_mul(t, t, x1); - fp2_add(t, t, ctx->ep2_b); - - fp2_sqr(u, y1); - fp2_mul(u, u, y1); - fp2_add(u, u, ctx->ep2_b); - - fp2_sqr(v, z1); - fp2_mul(v, v, z1); - fp2_add(v, v, ctx->ep2_b); - - c2 = fp2_is_sqr(u); - c3 = fp2_is_sqr(v); - - for (int i = 0; i < 2; i++) { - dv_swap_sec(x1[i], y1[i], RLC_FP_DIGS, c2); - dv_swap_sec(t[i], u[i], RLC_FP_DIGS, c2); - dv_swap_sec(x1[i], z1[i], RLC_FP_DIGS, c3); - dv_swap_sec(t[i], v[i], RLC_FP_DIGS, c3); + if (ep2_curve_opt_b() == RLC_ZERO) { + fp2_sqr(a, u); + fp2_sqr(b, a); + fp2_mul(c, b, a); + if (ep2_curve_opt_a() == RLC_ONE) { + fp2_add_dig(c, c, 64); + } else { + fp2_dbl(f, ep2_curve_get_a()); + fp2_dbl(f, f); + fp2_sqr(e, f); + fp2_mul(e, e, f); + fp2_add(c, c, e); } - - if (!fp2_srt(t, t)) { + fp2_sqr(d, t); + + fp2_mul(v, a, d); + fp2_mul(v, v, u); + fp2_mul_dig(v, v, 24); + fp_mul(v[0], v[0], ctx->ep_map_c[4]); + fp_mul(v[1], v[1], ctx->ep_map_c[4]); + + fp_sub_dig(p->x[0], ctx->ep_map_c[4], 1); + fp_hlv(p->x[0], p->x[0]); + + fp2_sqr(w, b); + fp2_mul(y, v, a); + if (ep2_curve_opt_a() == RLC_ONE) { + fp2_dbl(t, c); + fp2_dbl(t, t); + } else { + fp2_mul(t, f, c); + } + fp2_add(y, y, t); + fp_mul(y[0], y[0], p->x[0]); + fp_mul(y[1], y[1], p->x[0]); + + fp2_add(den[0], c, v); + fp2_mul(den[0], den[0], u); + fp_mul(den[0][0], den[0][0], ctx->ep_map_c[4]); + fp_mul(den[0][1], den[0][1], ctx->ep_map_c[4]); + fp_mul(den[0][0], den[0][0], p->x[0]); + fp_mul(den[0][1], den[0][1], p->x[0]); + fp2_dbl(den[0], den[0]); + fp2_neg(den[0], den[0]); + fp_mul(den[1][0], den[0][0], p->x[0]); + fp_mul(den[1][1], den[0][1], p->x[0]); + if (ep_curve_opt_a() == RLC_ONE) { + fp2_sub_dig(den[2], a, 4); + } else { + fp2_sub(den[2], a, f); + } + fp2_sqr(den[2], den[2]); + fp2_mul_dig(den[2], den[2], 216); + fp2_dbl(den[2], den[2]); + fp2_neg(den[2], den[2]); + fp2_mul(den[2], den[2], b); + fp2_mul(den[2], den[2], d); + + if (fp2_is_zero(den[0]) || fp2_is_zero(den[1]) || fp2_is_zero(den[2])) { + ep2_set_infty(p); + } else { + fp2_inv_sim(den, den, 3); + if (ep2_curve_opt_a() == RLC_ONE) { + fp2_dbl(a, a); + fp2_dbl(a, a); + fp2_dbl(a, a); + fp2_dbl(a, a); + fp2_add(y1, a, v); + fp2_dbl(y1, y1); + fp2_dbl(y1, y1); + } else { + fp2_mul(y1, f, v); + fp2_mul(u, a, e); + fp2_add(y1, y1, u); + } + fp2_add(y1, y1, w); + fp_mul(z1[0], y[0], p->x[0]); + fp_mul(z1[1], y[1], p->x[0]); + fp2_add(x1, y1, z1); + fp2_add(y1, y1, y); + + if (ep2_curve_opt_a() == RLC_ONE) { + fp2_dbl(e, b); + fp2_dbl(e, e); + fp2_add(z1, a, e); + } else { + fp2_mul(z1, f, a); + fp2_add(z1, z1, b); + fp2_mul(z1, z1, f); + } + fp2_dbl(t, z1); + fp2_add(z1, z1, t); + fp2_sub(z1, c, z1); + fp2_sub(z1, z1, v); + fp2_mul(z1, z1, v); + if (ep2_curve_opt_a() == RLC_ONE) { + fp2_dbl(a, a); + fp2_dbl(a, a); + fp2_dbl(a, a); + fp2_set_dig(d, 64); + fp2_sqr(d, d); + } else { + fp2_dbl(a, u); + fp2_sqr(d, e); + } + fp2_add(a, a, w); + fp2_mul(u, a, b); + fp2_sub(z1, u, z1); + fp2_add(z1, z1, d); + + fp2_mul(x1, x1, den[0]); + fp2_mul(y1, y1, den[1]); + fp2_mul(z1, z1, den[2]); + + ep2_rhs(t, x1); + ep2_rhs(u, y1); + ep2_rhs(v, z1); + + int c2 = fp2_is_sqr(u); + int c3 = fp2_is_sqr(v); + + fp2_copy_sec(t, u, c2); + fp2_copy_sec(x1, y1, c2); + fp2_copy_sec(t, v, c3); + fp2_copy_sec(x1, z1, c3); + + if (!fp2_srt(t, t)) { + RLC_THROW(ERR_NO_VALID); + } + fp2_neg(u, t); + fp2_copy_sec(t, u, fp_is_even(t[0]) ^ sign); + + fp2_copy(p->x, x1); + fp2_copy(p->y, t); + fp2_set_dig(p->z, 1); + p->coord = BASIC; + } + } else { + if (ep2_curve_opt_a() != RLC_ZERO) { RLC_THROW(ERR_NO_VALID); + } else { + /* Assume that a = 0. */ + fp2_sqr(x1, u); + fp2_mul(x1, x1, u); + fp2_sqr(y1, t); + fp2_add(x1, x1, ctx->ep2_b); + fp2_sub(x1, x1, y1); + fp2_dbl(y1, y1); + fp2_add(y1, y1, x1); + fp2_copy(z1, u); + fp_mul(z1[0], z1[0], ctx->ep_map_c[4]); + fp_mul(z1[1], z1[1], ctx->ep_map_c[4]); + fp2_mul(x1, x1, z1); + fp2_mul(z1, z1, t); + fp2_dbl(z1, z1); + + fp2_dbl(y, y1); + fp2_sqr(y, y); + fp2_mul(v, y1, u); + fp2_sub(v, x1, v); + fp2_mul(v, v, z1); + fp2_mul(w, y1, z1); + fp2_dbl(w, w); + + if (fp2_is_zero(w)) { + ep2_set_infty(p); + } else { + fp2_inv(w, w); + fp2_mul(x1, v, w); + fp2_add(y1, u, x1); + fp2_neg(y1, y1); + fp2_mul(z1, y, w); + fp2_sqr(z1, z1); + fp2_add(z1, z1, u); + + fp2_sqr(t, x1); + fp2_mul(t, t, x1); + fp2_add(t, t, ctx->ep2_b); + + fp2_sqr(u, y1); + fp2_mul(u, u, y1); + fp2_add(u, u, ctx->ep2_b); + + fp2_sqr(v, z1); + fp2_mul(v, v, z1); + fp2_add(v, v, ctx->ep2_b); + + c2 = fp2_is_sqr(u); + c3 = fp2_is_sqr(v); + + for (int i = 0; i < 2; i++) { + dv_swap_sec(x1[i], y1[i], RLC_FP_DIGS, c2); + dv_swap_sec(t[i], u[i], RLC_FP_DIGS, c2); + dv_swap_sec(x1[i], z1[i], RLC_FP_DIGS, c3); + dv_swap_sec(t[i], v[i], RLC_FP_DIGS, c3); + } + + if (!fp2_srt(t, t)) { + RLC_THROW(ERR_NO_VALID); + } + + t0z = fp_is_zero(t[0]); + fp_prime_back(k, t[0]); + t0 = bn_get_bit(k, 0); + fp_prime_back(k, t[1]); + t1 = bn_get_bit(k, 0); + /* t[0] == 0 ? sgn0(t[1]) : sgn0(t[0]) */ + sign ^= (t0 | (t0z & t1)); + + fp2_neg(u, t); + dv_swap_sec(t[0], u[0], RLC_FP_DIGS, sign); + dv_swap_sec(t[1], u[1], RLC_FP_DIGS, sign); + + fp2_copy(p->x, x1); + fp2_copy(p->y, t); + fp2_set_dig(p->z, 1); + p->coord = BASIC; + } } - - t0z = fp_is_zero(t[0]); - fp_prime_back(k, t[0]); - t0 = bn_get_bit(k, 0); - fp_prime_back(k, t[1]); - t1 = bn_get_bit(k, 0); - /* t[0] == 0 ? sgn0(t[1]) : sgn0(t[0]) */ - sign ^= (t0 | (t0z & t1)); - - fp2_neg(u, t); - dv_swap_sec(t[0], u[0], RLC_FP_DIGS, sign); - dv_swap_sec(t[1], u[1], RLC_FP_DIGS, sign); - - fp2_copy(p->x, x1); - fp2_copy(p->y, t); - fp2_set_dig(p->z, 1); - p->coord = BASIC; - - ep2_mul_cof(p, p); } + ep2_mul_cof(p, p); } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); diff --git a/src/epx/relic_ep2_mul.c b/src/epx/relic_ep2_mul.c index fe6dd6226..68904d8f5 100644 --- a/src/epx/relic_ep2_mul.c +++ b/src/epx/relic_ep2_mul.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of point multiplication on prime elliptic curves over - * quadratic extensions. + * Implementation of point multiplication on prime elliptic curves over a + * quadratic extension field. * * @ingroup epx */ @@ -36,10 +36,10 @@ /* Private definitions */ /*============================================================================*/ -#if EP_MUL == LWNAF || !defined(STRIP) - #if defined(EP_ENDOM) +#if EP_MUL == LWNAF || !defined(STRIP) + static void ep2_mul_gls_imp(ep2_t r, const ep2_t p, const bn_t k) { size_t l, _l[4]; bn_t n, _k[4], u; @@ -110,64 +110,10 @@ static void ep2_mul_gls_imp(ep2_t r, const ep2_t p, const bn_t k) { } } -#endif /* EP_ENDOM */ - -#if defined(EP_PLAIN) || defined(EP_SUPER) - -static void ep2_mul_naf_imp(ep2_t r, const ep2_t p, const bn_t k) { - size_t l; - int8_t n, naf[RLC_FP_BITS + 1]; - ep2_t t[1 << (RLC_WIDTH - 2)]; - - RLC_TRY { - /* Prepare the precomputation table. */ - for (int i = 0; i < (1 << (RLC_WIDTH - 2)); i++) { - ep2_null(t[i]); - ep2_new(t[i]); - } - /* Compute the precomputation table. */ - ep2_tab(t, p, RLC_WIDTH); - - /* Compute the w-NAF representation of k. */ - l = sizeof(naf); - bn_rec_naf(naf, &l, k, RLC_WIDTH); - - ep2_set_infty(r); - for (int 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 (int i = 0; i < (1 << (RLC_WIDTH - 2)); i++) { - ep2_free(t[i]); - } - } -} - -#endif /* EP_PLAIN || EP_SUPER */ #endif /* EP_MUL == LWNAF */ #if EP_MUL == LWREG || !defined(STRIP) -#if defined(EP_ENDOM) - static void ep2_mul_reg_gls(ep2_t r, const ep2_t p, const bn_t k) { int8_t reg[4][RLC_FP_BITS + 1], b[4], s[4], c0, n0; ep2_t q, w, t[4][1 << (RLC_WIDTH - 2)]; @@ -284,10 +230,64 @@ static void ep2_mul_reg_gls(ep2_t r, const ep2_t p, const bn_t k) { } } +#endif /* EP_MUL == LWREG */ #endif /* EP_ENDOM */ #if defined(EP_PLAIN) || defined(EP_SUPER) +#if EP_MUL == LWNAF || !defined(STRIP) + +static void ep2_mul_naf_imp(ep2_t r, const ep2_t p, const bn_t k) { + size_t l; + int8_t n, naf[RLC_FP_BITS + 1]; + ep2_t t[1 << (RLC_WIDTH - 2)]; + + RLC_TRY { + /* Prepare the precomputation table. */ + for (int i = 0; i < (1 << (RLC_WIDTH - 2)); i++) { + ep2_null(t[i]); + ep2_new(t[i]); + } + /* Compute the precomputation table. */ + ep2_tab(t, p, RLC_WIDTH); + + /* Compute the w-NAF representation of k. */ + l = sizeof(naf); + bn_rec_naf(naf, &l, k, RLC_WIDTH); + + ep2_set_infty(r); + for (int 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 (int i = 0; i < (1 << (RLC_WIDTH - 2)); i++) { + ep2_free(t[i]); + } + } +} + +#endif /* EP_MUL == LWNAF */ + +#if EP_MUL == LWREG || !defined(STRIP) + static void ep2_mul_reg_imp(ep2_t r, const ep2_t p, const bn_t k) { bn_t _k; int8_t s, reg[1 + RLC_CEIL(RLC_FP_BITS + 1, RLC_WIDTH - 1)]; @@ -370,8 +370,8 @@ static void ep2_mul_reg_imp(ep2_t r, const ep2_t p, const bn_t k) { } } -#endif /* EP_PLAIN || EP_SUPER */ #endif /* EP_MUL == LWREG */ +#endif /* EP_PLAIN || EP_SUPER */ /*============================================================================*/ /* Public definitions */ diff --git a/src/epx/relic_ep2_mul_cof.c b/src/epx/relic_ep2_mul_cof.c index 64f54df20..edc10dd2b 100644 --- a/src/epx/relic_ep2_mul_cof.c +++ b/src/epx/relic_ep2_mul_cof.c @@ -25,7 +25,7 @@ * @file * * Implementation of point multiplication of a prime elliptic curve over a - * quadratic extension by the curve cofactor. + * quadratic extension field by the curve cofactor. * * @ingroup epx */ diff --git a/src/epx/relic_ep2_mul_fix.c b/src/epx/relic_ep2_mul_fix.c index 075c9227a..200db4c89 100644 --- a/src/epx/relic_ep2_mul_fix.c +++ b/src/epx/relic_ep2_mul_fix.c @@ -25,7 +25,7 @@ * @file * * Implementation of fixed point multiplication on a prime elliptic curve over - * a quadratic extension. + * a quadratic extension field. * * @ingroup epx */ diff --git a/src/epx/relic_ep2_mul_sim.c b/src/epx/relic_ep2_mul_sim.c index 51f9f8df6..c39c432f6 100644 --- a/src/epx/relic_ep2_mul_sim.c +++ b/src/epx/relic_ep2_mul_sim.c @@ -25,7 +25,7 @@ * @file * * Implementation of simultaneous point multiplication on a prime elliptic - * curve over a quadratic extension. + * curve over a quadratic extension field. * * @ingroup epx */ diff --git a/src/epx/relic_ep2_neg.c b/src/epx/relic_ep2_neg.c index bde5c68de..16225e986 100644 --- a/src/epx/relic_ep2_neg.c +++ b/src/epx/relic_ep2_neg.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of point negation on elliptic prime curves over quadratic - * extensions. + * Implementation of point negation on elliptic prime curves over a quadratic + * extension field. * * @ingroup epx */ diff --git a/src/epx/relic_ep2_norm.c b/src/epx/relic_ep2_norm.c index d623c8c35..0010b976a 100644 --- a/src/epx/relic_ep2_norm.c +++ b/src/epx/relic_ep2_norm.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of point normalization on prime elliptic curves over quadratic - * extensions. + * Implementation of point normalization on prime elliptic curves over a + * quadratic extension. * * @ingroup epx */ diff --git a/src/epx/relic_ep2_pck.c b/src/epx/relic_ep2_pck.c index 699ebde41..6fd8e2abd 100644 --- a/src/epx/relic_ep2_pck.c +++ b/src/epx/relic_ep2_pck.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of point compression on prime elliptic curves over quadratic - * extensions. + * Implementation of point compression on prime elliptic curves over a quadratic + * extension field. * * @ingroup epx */ @@ -85,7 +85,7 @@ int ep2_upk(ep2_t r, const ep2_t p) { bn_new(halfQ); bn_new(yValue); - ep2_rhs(t, p); + ep2_rhs(t, p->x); /* t0 = sqrt(x1^3 + a * x1 + b). */ result = fp2_srt(t, t); diff --git a/src/epx/relic_ep2_util.c b/src/epx/relic_ep2_util.c index b49fdd4bc..4ac588532 100644 --- a/src/epx/relic_ep2_util.c +++ b/src/epx/relic_ep2_util.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of comparison for points on prime elliptic curves over - * quadratic extensions. + * Implementation of comparison for points on prime elliptic curves over a + * quadratic extension field. * * @ingroup epx */ @@ -109,7 +109,7 @@ void ep2_blind(ep2_t r, const ep2_t p) { } } -void ep2_rhs(fp2_t rhs, const ep2_t p) { +void ep2_rhs(fp2_t rhs, const fp2_t x) { fp2_t t0; fp2_null(t0); @@ -117,7 +117,7 @@ void ep2_rhs(fp2_t rhs, const ep2_t p) { RLC_TRY { fp2_new(t0); - fp2_sqr(t0, p->x); /* x1^2 */ + fp2_sqr(t0, x); /* x1^2 */ switch (ep2_curve_opt_a()) { case RLC_ZERO: @@ -141,7 +141,7 @@ void ep2_rhs(fp2_t rhs, const ep2_t p) { break; } - fp2_mul(t0, t0, p->x); /* x1^3 + a * x */ + fp2_mul(t0, t0, x); /* x1^3 + a * x */ switch (ep2_curve_opt_b()) { case RLC_ZERO: @@ -185,7 +185,7 @@ int ep2_on_curve(const ep2_t p) { ep2_norm(t, p); - ep2_rhs(t->x, t); + ep2_rhs(t->x, t->x); fp2_sqr(t->y, t->y); r = (fp2_cmp(t->x, t->y) == RLC_EQ) || ep2_is_infty(p); diff --git a/src/epx/relic_ep3_add.c b/src/epx/relic_ep3_add.c index 628b48bf4..d8a7667a6 100644 --- a/src/epx/relic_ep3_add.c +++ b/src/epx/relic_ep3_add.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of addition on prime elliptic curves over quadratic - * extensions. + * Implementation of addition on prime elliptic curves over a cubic extension + * field. * * @ingroup epx */ @@ -52,243 +52,53 @@ TMPL_ADD_BASIC_IMP(ep3, fp3); #endif /* EP_ADD == BASIC */ -#if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) +#if EP_ADD == PROJC || !defined(STRIP) -#if defined(EP_MIXED) || !defined(STRIP) +/** + * Adds a point represented in homogeneous coordinates to a point represented in + * affine coordinates on an ordinary prime elliptic curve. + * + * @param[out] r - the result. + * @param[in] p - the projective point. + * @param[in] q - the affine point. + */ +TMPL_ADD_PROJC_MIX(ep3, fp3); /** - * Adds a point represented in affine coordinates to a point represented in - * projective coordinates. + * Adds two points represented in homogeneous coordinates on an ordinary prime + * elliptic curve. * - * @param r - the result. - * @param s - the slope. - * @param p - the affine point. - * @param q - the projective point. + * @param[out] r - the result. + * @param[in] p - the first point to add. + * @param[in] q - the second point to add. */ -static void ep3_add_projc_mix(ep3_t r, const ep3_t p, const ep3_t q) { - fp3_t t0, t1, t2, t3, t4, t5, t6; +TMPL_ADD_PROJC_IMP(ep3, fp3); - fp3_null(t0); - fp3_null(t1); - fp3_null(t2); - fp3_null(t3); - fp3_null(t4); - fp3_null(t5); - fp3_null(t6); +#endif /* EP_ADD == PROJC */ - RLC_TRY { - fp3_new(t0); - fp3_new(t1); - fp3_new(t2); - fp3_new(t3); - fp3_new(t4); - fp3_new(t5); - fp3_new(t6); - - if (p->coord != BASIC) { - /* t0 = z1^2. */ - fp3_sqr(t0, p->z); - - /* t3 = U2 = x2 * z1^2. */ - fp3_mul(t3, q->x, t0); - - /* t1 = S2 = y2 * z1^3. */ - fp3_mul(t1, t0, p->z); - fp3_mul(t1, t1, q->y); - - /* t3 = H = U2 - x1. */ - fp3_sub(t3, t3, p->x); - - /* t1 = R = 2 * (S2 - y1). */ - fp3_sub(t1, t1, p->y); - } else { - /* H = x2 - x1. */ - fp3_sub(t3, q->x, p->x); - - /* t1 = R = 2 * (y2 - y1). */ - fp3_sub(t1, q->y, p->y); - } - - /* t2 = HH = H^2. */ - fp3_sqr(t2, t3); - - /* If E is zero. */ - if (fp3_is_zero(t3)) { - if (fp3_is_zero(t1)) { - /* If I is zero, p = q, should have doubled. */ - ep3_dbl_projc(r, p); - } else { - /* If I is not zero, q = -p, r = infinity. */ - ep3_set_infty(r); - } - } else { - /* t5 = J = H * HH. */ - fp3_mul(t5, t3, t2); - - /* t4 = V = x1 * HH. */ - fp3_mul(t4, p->x, t2); - - /* x3 = R^2 - J - 2 * V. */ - fp3_sqr(r->x, t1); - fp3_sub(r->x, r->x, t5); - fp3_dbl(t6, t4); - fp3_sub(r->x, r->x, t6); - - /* y3 = R * (V - x3) - Y1 * J. */ - fp3_sub(t4, t4, r->x); - fp3_mul(t4, t4, t1); - fp3_mul(t1, p->y, t5); - fp3_sub(r->y, t4, t1); - - if (p->coord != BASIC) { - /* z3 = z1 * H. */ - fp3_mul(r->z, p->z, t3); - } else { - /* z3 = H. */ - fp3_copy(r->z, t3); - } - } - r->coord = PROJC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp3_free(t0); - fp3_free(t1); - fp3_free(t2); - fp3_free(t3); - fp3_free(t4); - fp3_free(t5); - fp3_free(t6); - } -} +#if EP_ADD == JACOB || !defined(STRIP) -#endif +/** + * Adds a point represented in Jacobian coordinates to a point represented in + * affine coordinates on an ordinary prime elliptic curve. + * + * @param[out] r - the result. + * @param[in] p - the projective point. + * @param[in] q - the affine point. + */ +TMPL_ADD_JACOB_MIX(ep3, fp3); /** - * Adds two points represented in projective coordinates on an ordinary prime + * Adds two points represented in Jacobian coordinates on an ordinary prime * elliptic curve. * - * @param r - the result. - * @param p - the first point to add. - * @param q - the second point to add. + * @param[out] r - the result. + * @param[in] p - the first point to add. + * @param[in] q - the second point to add. */ -static void ep3_add_projc_imp(ep3_t r, const ep3_t p, const ep3_t q) { -#if defined(EP_MIXED) && defined(STRIP) - ep3_add_projc_mix(r, p, q); -#else /* General addition. */ - fp3_t t0, t1, t2, t3, t4, t5, t6; - - fp3_null(t0); - fp3_null(t1); - fp3_null(t2); - fp3_null(t3); - fp3_null(t4); - fp3_null(t5); - fp3_null(t6); - - RLC_TRY { - fp3_new(t0); - fp3_new(t1); - fp3_new(t2); - fp3_new(t3); - fp3_new(t4); - fp3_new(t5); - fp3_new(t6); - - if (q->coord == BASIC) { - ep3_add_projc_mix(r, p, q); - } else { - /* t0 = z1^2. */ - fp3_sqr(t0, p->z); - - /* t1 = z2^2. */ - fp3_sqr(t1, q->z); - - /* t2 = U1 = x1 * z2^2. */ - fp3_mul(t2, p->x, t1); - - /* t3 = U2 = x2 * z1^2. */ - fp3_mul(t3, q->x, t0); - - /* t6 = z1^2 + z2^2. */ - fp3_add(t6, t0, t1); - - /* t0 = S2 = y2 * z1^3. */ - fp3_mul(t0, t0, p->z); - fp3_mul(t0, t0, q->y); - - /* t1 = S1 = y1 * z2^3. */ - fp3_mul(t1, t1, q->z); - fp3_mul(t1, t1, p->y); - - /* t3 = H = U2 - U1. */ - fp3_sub(t3, t3, t2); - - /* t0 = R = 2 * (S2 - S1). */ - fp3_sub(t0, t0, t1); - - fp3_dbl(t0, t0); - - /* If E is zero. */ - if (fp3_is_zero(t3)) { - if (fp3_is_zero(t0)) { - /* If I is zero, p = q, should have doubled. */ - ep3_dbl_projc(r, p); - } else { - /* If I is not zero, q = -p, r = infinity. */ - ep3_set_infty(r); - } - } else { - /* t4 = I = (2*H)^2. */ - fp3_dbl(t4, t3); - fp3_sqr(t4, t4); - - /* t5 = J = H * I. */ - fp3_mul(t5, t3, t4); - - /* t4 = V = U1 * I. */ - fp3_mul(t4, t2, t4); - - /* x3 = R^2 - J - 2 * V. */ - fp3_sqr(r->x, t0); - fp3_sub(r->x, r->x, t5); - fp3_dbl(t2, t4); - fp3_sub(r->x, r->x, t2); - - /* y3 = R * (V - x3) - 2 * S1 * J. */ - fp3_sub(t4, t4, r->x); - fp3_mul(t4, t4, t0); - fp3_mul(t1, t1, t5); - fp3_dbl(t1, t1); - fp3_sub(r->y, t4, t1); - - /* z3 = ((z1 + z2)^2 - z1^2 - z2^2) * H. */ - fp3_add(r->z, p->z, q->z); - fp3_sqr(r->z, r->z); - fp3_sub(r->z, r->z, t6); - fp3_mul(r->z, r->z, t3); - } - } - r->coord = PROJC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp3_free(t0); - fp3_free(t1); - fp3_free(t2); - fp3_free(t3); - fp3_free(t4); - fp3_free(t5); - fp3_free(t6); - } -#endif -} +TMPL_ADD_JACOB_IMP(ep3, fp3); -#endif /* EP_ADD == PROJC */ +#endif /* EP_ADD == JACOB */ /*============================================================================*/ /* Public definitions */ @@ -326,7 +136,7 @@ void ep3_add_slp_basic(ep3_t r, fp3_t s, const ep3_t p, const ep3_t q) { #endif -#if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) +#if EP_ADD == PROJC || !defined(STRIP) void ep3_add_projc(ep3_t r, const ep3_t p, const ep3_t q) { if (ep3_is_infty(p)) { @@ -339,13 +149,25 @@ void ep3_add_projc(ep3_t r, const ep3_t p, const ep3_t q) { return; } - if (p == q) { - /* TODO: This is a quick hack. Should we fix this? */ - ep3_dbl(r, p); + ep3_add_projc_imp(r, p, q); +} + +#endif + +#if EP_ADD == JACOB || !defined(STRIP) + +void ep3_add_jacob(ep3_t r, const ep3_t p, const ep3_t q) { + if (ep3_is_infty(p)) { + ep3_copy(r, q); return; } - ep3_add_projc_imp(r, p, q); + if (ep3_is_infty(q)) { + ep3_copy(r, p); + return; + } + + ep3_add_jacob_imp(r, p, q); } #endif @@ -362,7 +184,6 @@ void ep3_sub(ep3_t r, const ep3_t p, const ep3_t q) { RLC_TRY { ep3_new(t); - ep3_neg(t, q); ep3_add(r, p, t); } diff --git a/src/epx/relic_ep3_cmp.c b/src/epx/relic_ep3_cmp.c index 265a65c98..10078af81 100644 --- a/src/epx/relic_ep3_cmp.c +++ b/src/epx/relic_ep3_cmp.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of utilities for prime elliptic curves over quadratic - * extensions. + * Implementation of utilities for prime elliptic curves over a cubic + * extension field. * * @ingroup epx */ @@ -37,46 +37,68 @@ /*============================================================================*/ int ep3_cmp(const ep3_t p, const ep3_t q) { - ep3_t r, s; - int result = RLC_NE; + ep3_t r, s; + int result = RLC_NE; if (ep3_is_infty(p) && ep3_is_infty(q)) { return RLC_EQ; } - ep3_null(r); - ep3_null(s); + ep3_null(r); + ep3_null(s); - RLC_TRY { - ep3_new(r); - ep3_new(s); + RLC_TRY { + ep3_new(r); + ep3_new(s); - if ((p->coord != BASIC) && (q->coord != BASIC)) { - /* If the two points are not normalized, it is faster to compare - * x1 * z2^2 == x2 * z1^2 and y1 * z2^3 == y2 * z1^3. */ - fp3_sqr(r->z, p->z); - fp3_sqr(s->z, q->z); - fp3_mul(r->x, p->x, s->z); - fp3_mul(s->x, q->x, r->z); - fp3_mul(r->z, r->z, p->z); - fp3_mul(s->z, s->z, q->z); - fp3_mul(r->y, p->y, s->z); - fp3_mul(s->y, q->y, r->z); - } else { - ep3_norm(r, p); - ep3_norm(s, q); - } + switch (q->coord) { + case PROJC: + /* If q is in homogeneous projective coordinates, compute + * x1 * z2 and y1 * z2. */ + fp3_mul(r->x, p->x, q->z); + fp3_mul(r->y, p->y, q->z); + break; + case JACOB: + /* If q is in Jacobian projective coordinates, compute + * x2 * z1^2 and y2 * z1^3. */ + fp3_sqr(r->z, q->z); + fp3_mul(r->x, p->x, r->z); + fp3_mul(r->z, r->z, q->z); + fp3_mul(r->y, p->y, r->z); + break; + default: + ep3_copy(r, p); + break; + } - if ((fp3_cmp(r->x, s->x) == RLC_EQ) && - (fp3_cmp(r->y, s->y) == RLC_EQ)) { - result = RLC_EQ; - } - } RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } RLC_FINALLY { - ep3_free(r); - ep3_free(s); - } + switch (p->coord) { + /* Now do the same for the other point. */ + case PROJC: + fp3_mul(s->x, q->x, p->z); + fp3_mul(s->y, q->y, p->z); + break; + case JACOB: + fp3_sqr(s->z, p->z); + fp3_mul(s->x, q->x, s->z); + fp3_mul(s->z, s->z, p->z); + fp3_mul(s->y, q->y, s->z); + break; + default: + ep3_copy(s, q); + break; + } - return result; + if ((fp3_cmp(r->x, s->x) == RLC_EQ) && (fp3_cmp(r->y, s->y) == RLC_EQ)) { + result = RLC_EQ; + } + } + RLC_CATCH_ANY { + RLC_THROW(ERR_CAUGHT); + } + RLC_FINALLY { + ep3_free(r); + ep3_free(s); + } + + return result; } diff --git a/src/epx/relic_ep3_curve.c b/src/epx/relic_ep3_curve.c index 2d1788530..ff9ba52a1 100644 --- a/src/epx/relic_ep3_curve.c +++ b/src/epx/relic_ep3_curve.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of configuration of prime elliptic curves over quartic - * extensions. + * Implementation of configuration of prime elliptic curves over a cubic + * extension field. * * @ingroup epx */ @@ -241,6 +241,49 @@ int ep3_curve_opt_b(void) { return core_get()->ep3_opt_b; } +void ep3_curve_mul_a(fp3_t c, const fp3_t a) { + ctx_t *ctx = core_get(); + switch (ctx->ep3_opt_a) { + case RLC_ZERO: + fp3_zero(c); + break; + case RLC_ONE: + fp3_copy(c, a); + break; + case RLC_TWO: + fp3_dbl(c, a); + break; +#if FP_RDC != MONTY + case RLC_TINY: + fp3_mul_dig(c, a, ctx->ep3_a[0]); + break; +#endif + default: + fp3_mul(c, a, ctx->ep3_a); + break; + } +} + +void ep3_curve_mul_b(fp3_t c, const fp3_t a) { + ctx_t *ctx = core_get(); + switch (ctx->ep3_opt_b) { + case RLC_ZERO: + fp3_zero(c); + break; + case RLC_ONE: + fp3_copy(c, a); + break; +#if FP_RDC != MONTY + case RLC_TINY: + fp3_mul_dig(c, a, ctx->ep3_b[0]); + break; +#endif + default: + fp3_mul(c, a, ctx->ep3_b); + break; + } +} + int ep3_curve_is_twist(void) { return core_get()->ep3_is_twist; } diff --git a/src/epx/relic_ep3_dbl.c b/src/epx/relic_ep3_dbl.c index 28d535dc6..fa4da39b6 100644 --- a/src/epx/relic_ep3_dbl.c +++ b/src/epx/relic_ep3_dbl.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of doubling on elliptic prime curves over quartic - * extensions. + * Implementation of doubling on elliptic prime curves over a cubic extension + * field. * * @ingroup epx */ @@ -44,141 +44,41 @@ * elliptic curve. * * @param[out] r - the result. - * @param[out] s - the resulting slope. + * @param[out] s - the slope. * @param[in] p - the point to double. */ TMPL_DBL_BASIC_IMP(ep3, fp3); #endif /* EP_ADD == BASIC */ -#if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) +#if EP_ADD == PROJC || !defined(STRIP) /** - * Doubles a point represented in affine coordinates on an ordinary prime + * Doubles a point represented in projective coordinates on an ordinary prime * elliptic curve. * - * @param[out] r - the result. - * @param[in] p - the point to double. + * @param r - the result. + * @param p - the point to double. */ -static void ep3_dbl_projc_imp(ep3_t r, const ep3_t p) { - fp3_t t0, t1, t2, t3, t4, t5; - - fp3_null(t0); - fp3_null(t1); - fp3_null(t2); - fp3_null(t3); - fp3_null(t4); - fp3_null(t5); - - RLC_TRY { - fp3_new(t0); - fp3_new(t1); - fp3_new(t2); - fp3_new(t3); - fp3_new(t4); - fp3_new(t5); - - if (ep_curve_opt_a() == RLC_ZERO) { - fp3_sqr(t0, p->x); - fp3_add(t2, t0, t0); - fp3_add(t0, t2, t0); - - fp3_sqr(t3, p->y); - fp3_mul(t1, t3, p->x); - fp3_add(t1, t1, t1); - fp3_add(t1, t1, t1); - fp3_sqr(r->x, t0); - fp3_add(t2, t1, t1); - fp3_sub(r->x, r->x, t2); - fp3_mul(r->z, p->z, p->y); - fp3_add(r->z, r->z, r->z); - fp3_add(t3, t3, t3); - - fp3_sqr(t3, t3); - fp3_add(t3, t3, t3); - fp3_sub(t1, t1, r->x); - fp3_mul(r->y, t0, t1); - fp3_sub(r->y, r->y, t3); - } else { - /* dbl-2007-bl formulas: 1M + 8S + 1*a + 10add + 1*8 + 2*2 + 1*3 */ - /* http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-2007-bl */ - - /* t0 = x1^2, t1 = y1^2, t2 = y1^4. */ - fp3_sqr(t0, p->x); - fp3_sqr(t1, p->y); - fp3_sqr(t2, t1); - - if (p->coord != BASIC) { - /* t3 = z1^2. */ - fp3_sqr(t3, p->z); - - if (ep_curve_opt_a() == RLC_ZERO) { - /* z3 = 2 * y1 * z1. */ - fp3_mul(r->z, p->y, p->z); - fp3_dbl(r->z, r->z); - } else { - /* z3 = (y1 + z1)^2 - y1^2 - z1^2. */ - fp3_add(r->z, p->y, p->z); - fp3_sqr(r->z, r->z); - fp3_sub(r->z, r->z, t1); - fp3_sub(r->z, r->z, t3); - } - } else { - /* z3 = 2 * y1. */ - fp3_dbl(r->z, p->y); - } - - /* t4 = S = 2*((x1 + y1^2)^2 - x1^2 - y1^4). */ - fp3_add(t4, p->x, t1); - fp3_sqr(t4, t4); - fp3_sub(t4, t4, t0); - fp3_sub(t4, t4, t2); - fp3_dbl(t4, t4); - - /* t5 = M = 3 * x1^2 + a * z1^4. */ - fp3_dbl(t5, t0); - fp3_add(t5, t5, t0); - if (p->coord != BASIC) { - fp3_sqr(t3, t3); - fp3_mul(t1, t3, ep3_curve_get_a()); - fp3_add(t5, t5, t1); - } else { - fp3_add(t5, t5, ep3_curve_get_a()); - } - - /* x3 = T = M^2 - 2 * S. */ - fp3_sqr(r->x, t5); - fp3_dbl(t1, t4); - fp3_sub(r->x, r->x, t1); - - /* y3 = M * (S - T) - 8 * y1^4. */ - fp3_dbl(t2, t2); - fp3_dbl(t2, t2); - fp3_dbl(t2, t2); - fp3_sub(t4, t4, r->x); - fp3_mul(t5, t5, t4); - fp3_sub(r->y, t5, t2); - } - - r->coord = PROJC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp3_free(t0); - fp3_free(t1); - fp3_free(t2); - fp3_free(t3); - fp3_free(t4); - fp3_free(t5); - } -} +TMPL_DBL_PROJC_IMP(ep3, fp3); #endif /* EP_ADD == PROJC */ +#if EP_ADD == JACOB || !defined(STRIP) + +/** + * Doubles a point represented in Jacobian coordinates on an ordinary prime + * elliptic curve. + * + * @param r - the result. + * @param p - the point to double. + */ +TMPL_DBL_JACOB_IMP(ep3, fp3); + +#endif /* EP_ADD == JACOB */ + /*============================================================================*/ - /* Public definitions */ +/* Public definitions */ /*============================================================================*/ #if EP_ADD == BASIC || !defined(STRIP) @@ -188,7 +88,6 @@ void ep3_dbl_basic(ep3_t r, const ep3_t p) { ep3_set_infty(r); return; } - ep3_dbl_basic_imp(r, NULL, p); } @@ -203,7 +102,7 @@ void ep3_dbl_slp_basic(ep3_t r, fp3_t s, const ep3_t p) { #endif -#if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) +#if EP_ADD == PROJC || !defined(STRIP) void ep3_dbl_projc(ep3_t r, const ep3_t p) { if (ep3_is_infty(p)) { @@ -215,3 +114,16 @@ void ep3_dbl_projc(ep3_t r, const ep3_t p) { } #endif + +#if EP_ADD == JACOB || !defined(STRIP) + +void ep3_dbl_jacob(ep3_t r, const ep3_t p) { + if (ep3_is_infty(p)) { + ep3_set_infty(r); + return; + } + + ep3_dbl_jacob_imp(r, p); +} + +#endif diff --git a/src/epx/relic_ep3_frb.c b/src/epx/relic_ep3_frb.c index 1384425f1..ecff8a278 100644 --- a/src/epx/relic_ep3_frb.c +++ b/src/epx/relic_ep3_frb.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of frobenius action on prime elliptic curves over - * quadratic extensions. + * Implementation of frobenius action on prime elliptic curves over a cubic + * extension field. * * @ingroup epx */ diff --git a/src/epx/relic_ep3_map.c b/src/epx/relic_ep3_map.c index 47c3e0e29..87bf57315 100644 --- a/src/epx/relic_ep3_map.c +++ b/src/epx/relic_ep3_map.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of hashing to a prime elliptic curve over a quadratic - * extension. + * Implementation of hashing to a prime elliptic curve over a cubic extension + * field. * * @ingroup epx */ diff --git a/src/epx/relic_ep3_mul.c b/src/epx/relic_ep3_mul.c index 6c9f515d6..a814664fb 100644 --- a/src/epx/relic_ep3_mul.c +++ b/src/epx/relic_ep3_mul.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of point multiplication on prime elliptic curves over - * quadratic extensions. + * Implementation of point multiplication on prime elliptic curves over a + * cubic extensions. * * @ingroup epx */ @@ -36,8 +36,6 @@ /* Private definitions */ /*============================================================================*/ -#if EP_MUL == LWNAF || !defined(STRIP) - #if defined(EP_ENDOM) static void ep3_psi(ep3_t r, const ep3_t p) { @@ -85,6 +83,8 @@ static void ep3_psi(ep3_t r, const ep3_t p) { } } +#if EP_MUL == LWNAF || !defined(STRIP) + static void ep3_mul_glv_imp(ep3_t r, const ep3_t p, const bn_t k) { int i, j; size_t l, _l[6]; @@ -165,10 +165,133 @@ static void ep3_mul_glv_imp(ep3_t r, const ep3_t p, const bn_t k) { } } +#endif /* EP_MUL == LWNAF */ + +#if EP_MUL == LWREG || !defined(STRIP) + +static void ep3_mul_reg_gls(ep3_t r, const ep3_t p, const bn_t k) { + int8_t reg[6][RLC_FP_BITS + 1], b[6], s[6], c0, n0; + ep3_t q, w, t[6][1 << (RLC_WIDTH - 2)]; + bn_t n, _k[6], u; + size_t l, len, _l[6]; + + bn_null(n); + bn_null(u); + ep3_null(q); + ep3_null(w); + + RLC_TRY { + bn_new(n); + bn_new(u); + ep3_new(q); + ep3_new(w); + for (size_t i = 0; i < 6; i++) { + bn_null(_k[i]); + bn_new(_k[i]); + for (size_t j = 0; j < (1 << (RLC_WIDTH - 2)); j++) { + ep3_null(t[i][j]); + ep3_new(t[i][j]); + } + } + + ep3_curve_get_ord(n); + fp_prime_get_par(u); + bn_mod(_k[0], k, n); + bn_rec_frb(_k, 6, _k[0], u, n, ep_curve_is_pairf() == EP_BN); + + l = 0; + /* Make some extra room for BN curves that grow subscalars by 1. */ + len = bn_bits(u) + (ep_curve_is_pairf() == EP_BN); + ep3_norm(t[0][0], p); + for (size_t i = 0; i < 6; i++) { + s[i] = bn_sign(_k[i]); + bn_abs(_k[i], _k[i]); + b[i] = bn_is_even(_k[i]); + _k[i]->dp[0] |= b[i]; + + _l[i] = RLC_FP_BITS + 1; + bn_rec_reg(reg[i], &_l[i], _k[i], len, RLC_WIDTH); + l = RLC_MAX(l, _l[i]); + + /* Apply Frobenius before flipping sign to build table. */ + if (i > 0) { + ep3_psi(t[i][0], t[i - 1][0]); + } + } + + for (size_t i = 0; i < 6; i++) { + ep3_neg(q, t[i][0]); + fp3_copy_sec(q->y, t[i][0]->y, s[i] == RLC_POS); + ep3_tab(t[i], q, RLC_WIDTH); + } + +#if defined(EP_MIXED) + fp3_set_dig(w->z, 1); + w->coord = BASIC; +#else + w->coord = = EP_ADD; +#endif + + ep3_set_infty(r); + for (int j = l - 1; j >= 0; j--) { + for (size_t i = 0; i < RLC_WIDTH - 1; i++) { + ep3_dbl(r, r); + } + + for (size_t i = 0; i < 6; i++) { + n0 = reg[i][j]; + c0 = (n0 >> 7); + n0 = ((n0 ^ c0) - c0) >> 1; + + for (size_t m = 0; m < (1 << (RLC_WIDTH - 2)); m++) { + fp3_copy_sec(w->x, t[i][m]->x, m == n0); + fp3_copy_sec(w->y, t[i][m]->y, m == n0); + #if !defined(EP_MIXED) + fp3_copy_sec(w->z, t[i][m]->z, m == n0); + #endif + } + + ep3_neg(q, w); + fp3_copy_sec(q->y, w->y, c0 == 0); + ep3_add(r, r, q); + } + } + + for (size_t i = 0; i < 6; i++) { + /* Tables are built with points already negated, so no need here. */ + ep3_sub(q, r, t[i][0]); + fp3_copy_sec(r->x, q->x, b[i]); + fp3_copy_sec(r->y, q->y, b[i]); + fp3_copy_sec(r->z, q->z, b[i]); + } + + /* Convert r to affine coordinates. */ + ep3_norm(r, r); + } + RLC_CATCH_ANY { + RLC_THROW(ERR_CAUGHT); + } + RLC_FINALLY { + bn_free(n); + bn_free(u); + ep3_free(q); + ep3_free(w); + for (int i = 0; i < 6; i++) { + bn_free(_k[i]); + for (size_t j = 0; j < (1 << (RLC_WIDTH - 2)); j++) { + ep3_free(t[i][j]); + } + } + } +} + +#endif /* EP_MUL == LWREG */ #endif /* EP_ENDOM */ #if defined(EP_PLAIN) || defined(EP_SUPER) +#if EP_MUL == LWNAF || !defined(STRIP) + static void ep3_mul_naf_imp(ep3_t r, const ep3_t p, const bn_t k) { int i, n; int8_t naf[RLC_FP_BITS + 1]; @@ -217,9 +340,95 @@ static void ep3_mul_naf_imp(ep3_t r, const ep3_t p, const bn_t k) { } } -#endif /* EP_PLAIN || EP_SUPER */ #endif /* EP_MUL == LWNAF */ +#if EP_MUL == LWREG || !defined(STRIP) + +static void ep3_mul_reg_imp(ep3_t r, const ep3_t p, const bn_t k) { + bn_t _k; + int8_t s, reg[1 + RLC_CEIL(RLC_FP_BITS + 1, RLC_WIDTH - 1)]; + ep3_t t[1 << (RLC_WIDTH - 2)], u, v; + size_t l, n; + + bn_null(_k); + + RLC_TRY { + bn_new(_k); + ep3_new(u); + ep3_new(v); + /* Prepare the precomputation table. */ + for (size_t i = 0; i < (1 << (RLC_WIDTH - 2)); i++) { + ep3_null(t[i]); + ep3_new(t[i]); + } + /* Compute the precomputation table. */ + ep3_tab(t, p, RLC_WIDTH); + + ep3_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, RLC_WIDTH - 1) + 1; + bn_rec_reg(reg, &l, _k, n, RLC_WIDTH); + +#if defined(EP_MIXED) + fp3_set_dig(u->z, 1); + u->coord = BASIC; +#else + u->coord = EP_ADD; +#endif + ep3_set_infty(r); + for (int i = l - 1; i >= 0; i--) { + for (size_t j = 0; j < RLC_WIDTH - 1; j++) { + ep3_dbl(r, r); + } + + n = reg[i]; + s = (n >> 7); + n = ((n ^ s) - s) >> 1; + + for (size_t j = 0; j < (1 << (RLC_WIDTH - 2)); j++) { + fp3_copy_sec(u->x, t[j]->x, j == n); + fp3_copy_sec(u->y, t[j]->y, j == n); +#if !defined(EP_MIXED) + fp_copy_sec(u->z, t[j]->z, j == n); +#endif + } + ep3_neg(v, u); + fp3_copy_sec(u->y, v->y, s != 0); + ep3_add(r, r, u); + } + /* t[0] has an unmodified copy of p. */ + ep3_sub(u, r, t[0]); + fp3_copy_sec(r->x, u->x, bn_is_even(k)); + fp3_copy_sec(r->y, u->y, bn_is_even(k)); + fp3_copy_sec(r->z, u->z, bn_is_even(k)); + /* Convert r to affine coordinates. */ + ep3_norm(r, r); + ep3_neg(u, r); + fp3_copy_sec(r->y, u->y, bn_sign(k) == RLC_NEG); + } + RLC_CATCH_ANY { + RLC_THROW(ERR_CAUGHT); + } + RLC_FINALLY { + /* Free the precomputation table. */ + for (size_t i = 0; i < (1 << (RLC_WIDTH - 2)); i++) { + ep3_free(t[i]); + } + bn_free(_k); + ep3_free(u); + ep3_free(v); + } +} + +#endif /* EP_MUL == LWREG */ +#endif /* EP_PLAIN || EP_SUPER */ + /*============================================================================*/ /* Public definitions */ /*============================================================================*/ @@ -455,6 +664,28 @@ void ep3_mul_lwnaf(ep3_t r, const ep3_t p, const bn_t k) { #endif +#if EP_MUL == LWREG || !defined(STRIP) + +void ep3_mul_lwreg(ep3_t r, const ep3_t p, const bn_t k) { + if (bn_is_zero(k) || ep3_is_infty(p)) { + ep3_set_infty(r); + return; + } + +#if defined(EP_ENDOM) + if (ep_curve_is_endom()) { + ep3_mul_reg_gls(r, p, k); + return; + } +#endif + +#if defined(EP_PLAIN) || defined(EP_SUPER) + ep3_mul_reg_imp(r, p, k); +#endif +} + +#endif + void ep3_mul_gen(ep3_t r, const bn_t k) { if (bn_is_zero(k)) { ep3_set_infty(r); diff --git a/src/epx/relic_ep3_mul_cof.c b/src/epx/relic_ep3_mul_cof.c index cd3bc1909..8314a44c7 100644 --- a/src/epx/relic_ep3_mul_cof.c +++ b/src/epx/relic_ep3_mul_cof.c @@ -25,7 +25,7 @@ * @file * * Implementation of point multiplication of a prime elliptic curve over a - * quadratic extension by the curve cofactor. + * cubic extension field by the curve cofactor. * * @ingroup epx */ diff --git a/src/epx/relic_ep3_mul_fix.c b/src/epx/relic_ep3_mul_fix.c index 66276d5a2..efde195e1 100644 --- a/src/epx/relic_ep3_mul_fix.c +++ b/src/epx/relic_ep3_mul_fix.c @@ -25,7 +25,7 @@ * @file * * Implementation of fixed point multiplication on a prime elliptic curve over - * a quadratic extension. + * a cubic extension field. * * @ingroup epx */ diff --git a/src/epx/relic_ep3_mul_sim.c b/src/epx/relic_ep3_mul_sim.c index 408232a57..625948fa4 100644 --- a/src/epx/relic_ep3_mul_sim.c +++ b/src/epx/relic_ep3_mul_sim.c @@ -25,7 +25,7 @@ * @file * * Implementation of simultaneous point multiplication on a prime elliptic - * curve over a quartic extension. + * curve over a cubic extension field. * * @ingroup epx */ diff --git a/src/epx/relic_ep3_neg.c b/src/epx/relic_ep3_neg.c index 1c076cda8..56594da96 100644 --- a/src/epx/relic_ep3_neg.c +++ b/src/epx/relic_ep3_neg.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of point negation on elliptic prime curves over quadratic - * extensions. + * Implementation of point negation on elliptic prime curves over a cubic + * extension field. * * @ingroup epx */ diff --git a/src/epx/relic_ep3_norm.c b/src/epx/relic_ep3_norm.c index 0015c2711..5f8b853e6 100644 --- a/src/epx/relic_ep3_norm.c +++ b/src/epx/relic_ep3_norm.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of point normalization on prime elliptic curves over quadratic - * extensions. + * Implementation of point normalization on prime elliptic curves over a cubic + * extension field. * * @ingroup epx */ @@ -43,36 +43,45 @@ * * @param r - the result. * @param p - the point to normalize. + * @param inv - the flag to indicate if z is already inverted. */ -static void ep3_norm_imp(ep3_t r, const ep3_t p, int inverted) { +static void ep3_norm_imp(ep3_t r, const ep3_t p, int inv) { if (p->coord != BASIC) { - fp3_t t0, t1; + fp3_t t; - fp3_null(t0); - fp3_null(t1); + fp3_null(t); RLC_TRY { + fp3_new(t); - fp3_new(t0); - fp3_new(t1); - - if (inverted) { - fp3_copy(t1, p->z); + if (inv) { + fp3_copy(r->z, p->z); } else { - fp3_inv(t1, p->z); + fp3_inv(r->z, p->z); + } + + switch (p->coord) { + case PROJC: + fp3_mul(r->x, p->x, r->z); + fp3_mul(r->y, p->y, r->z); + break; + case JACOB: + fp3_sqr(t, r->z); + fp3_mul(r->x, p->x, t); + fp3_mul(t, t, r->z); + fp3_mul(r->y, p->y, t); + break; + default: + ep3_copy(r, p); + break; } - fp3_sqr(t0, t1); - fp3_mul(r->x, p->x, t0); - fp3_mul(t0, t0, t1); - fp3_mul(r->y, p->y, t0); fp3_set_dig(r->z, 1); } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); } RLC_FINALLY { - fp3_free(t0); - fp3_free(t1); + fp3_free(t); } } @@ -92,17 +101,18 @@ void ep3_norm(ep3_t r, const ep3_t p) { } if (p->coord == BASIC) { - /* If the point is represented in affine coordinates, we just copy it. */ + /* If the point is represented in affine coordinates, just copy it. */ ep3_copy(r, p); + return; } #if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) ep3_norm_imp(r, p, 0); -#endif +#endif /* EP_ADD == PROJC */ } void ep3_norm_sim(ep3_t *r, const ep3_t *t, int n) { int i; - fp3_t *a = RLC_ALLOCA(fp3_t, n); + fp3_t* a = RLC_ALLOCA(fp3_t, n); RLC_TRY { if (a == NULL) { @@ -114,18 +124,20 @@ void ep3_norm_sim(ep3_t *r, const ep3_t *t, int n) { fp3_copy(a[i], t[i]->z); } - fp3_inv_sim(a, a, n); + fp3_inv_sim(a, (const fp3_t *)a, n); for (i = 0; i < n; i++) { fp3_copy(r[i]->x, t[i]->x); fp3_copy(r[i]->y, t[i]->y); - fp3_copy(r[i]->z, a[i]); + if (!ep3_is_infty(t[i])) { + fp3_copy(r[i]->z, a[i]); + } } #if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) for (i = 0; i < n; i++) { ep3_norm_imp(r[i], r[i], 1); } -#endif +#endif /* EP_ADD == PROJC */ } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); diff --git a/src/epx/relic_ep3_util.c b/src/epx/relic_ep3_util.c index e75de6b2e..a3b9676b2 100644 --- a/src/epx/relic_ep3_util.c +++ b/src/epx/relic_ep3_util.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of comparison for points on prime elliptic curves over - * quartic extensions. + * Implementation of comparison for points on prime elliptic curves over a + * cubic extension field. * * @ingroup epx */ @@ -89,13 +89,18 @@ void ep3_blind(ep3_t r, const ep3_t p) { #if EP_ADD == BASIC (void)rand; ep3_copy(r, p); -#else +#elif EP_ADD == PROJC + fp3_mul(r->x, p->x, rand); + fp3_mul(r->y, p->y, rand); + fp3_mul(r->z, p->z, rand); + r->coord = PROJC; +#elif EP_ADD == JACOB fp3_mul(r->z, p->z, rand); fp3_mul(r->y, p->y, rand); fp3_sqr(rand, rand); fp3_mul(r->x, r->x, rand); fp3_mul(r->y, r->y, rand); - r->coord = EP_ADD; + r->coord = JACOB; #endif } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); @@ -104,7 +109,7 @@ void ep3_blind(ep3_t r, const ep3_t p) { } } -void ep3_rhs(fp3_t rhs, const ep3_t p) { +void ep3_rhs(fp3_t rhs, const fp3_t x) { fp3_t t0; fp3_null(t0); @@ -112,7 +117,7 @@ void ep3_rhs(fp3_t rhs, const ep3_t p) { RLC_TRY { fp3_new(t0); - fp3_sqr(t0, p->x); /* x1^2 */ + fp3_sqr(t0, x); /* x1^2 */ switch (ep3_curve_opt_a()) { case RLC_ZERO: @@ -136,7 +141,7 @@ void ep3_rhs(fp3_t rhs, const ep3_t p) { break; } - fp3_mul(t0, t0, p->x); /* x1^3 + a * x */ + fp3_mul(t0, t0, x); /* x1^3 + a * x */ switch (ep3_curve_opt_b()) { case RLC_ZERO: @@ -179,7 +184,7 @@ int ep3_on_curve(const ep3_t p) { ep3_norm(t, p); - ep3_rhs(t->x, t); + ep3_rhs(t->x, t->x); fp3_sqr(t->y, t->y); r = (fp3_cmp(t->x, t->y) == RLC_EQ) || ep3_is_infty(p); diff --git a/src/epx/relic_ep4_add.c b/src/epx/relic_ep4_add.c index 3ff25328a..4c9c3ff1e 100644 --- a/src/epx/relic_ep4_add.c +++ b/src/epx/relic_ep4_add.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of addition on prime elliptic curves over quartic - * extensions. + * Implementation of addition on prime elliptic curves over a quartic + * extension field. * * @ingroup epx */ @@ -52,243 +52,53 @@ TMPL_ADD_BASIC_IMP(ep4, fp4); #endif /* EP_ADD == BASIC */ -#if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) +#if EP_ADD == PROJC || !defined(STRIP) -#if defined(EP_MIXED) || !defined(STRIP) +/** + * Adds a point represented in homogeneous coordinates to a point represented in + * affine coordinates on an ordinary prime elliptic curve. + * + * @param[out] r - the result. + * @param[in] p - the projective point. + * @param[in] q - the affine point. + */ +TMPL_ADD_PROJC_MIX(ep4, fp4); /** - * Adds a point represented in affine coordinates to a point represented in - * projective coordinates. + * Adds two points represented in homogeneous coordinates on an ordinary prime + * elliptic curve. * - * @param r - the result. - * @param s - the slope. - * @param p - the affine point. - * @param q - the projective point. + * @param[out] r - the result. + * @param[in] p - the first point to add. + * @param[in] q - the second point to add. */ -static void ep4_add_projc_mix(ep4_t r, const ep4_t p, const ep4_t q) { - fp4_t t0, t1, t2, t3, t4, t5, t6; +TMPL_ADD_PROJC_IMP(ep4, fp4); - fp4_null(t0); - fp4_null(t1); - fp4_null(t2); - fp4_null(t3); - fp4_null(t4); - fp4_null(t5); - fp4_null(t6); +#endif /* EP_ADD == PROJC */ - RLC_TRY { - fp4_new(t0); - fp4_new(t1); - fp4_new(t2); - fp4_new(t3); - fp4_new(t4); - fp4_new(t5); - fp4_new(t6); - - if (p->coord != BASIC) { - /* t0 = z1^2. */ - fp4_sqr(t0, p->z); - - /* t3 = U2 = x2 * z1^2. */ - fp4_mul(t3, q->x, t0); - - /* t1 = S2 = y2 * z1^3. */ - fp4_mul(t1, t0, p->z); - fp4_mul(t1, t1, q->y); - - /* t3 = H = U2 - x1. */ - fp4_sub(t3, t3, p->x); - - /* t1 = R = 2 * (S2 - y1). */ - fp4_sub(t1, t1, p->y); - } else { - /* H = x2 - x1. */ - fp4_sub(t3, q->x, p->x); - - /* t1 = R = 2 * (y2 - y1). */ - fp4_sub(t1, q->y, p->y); - } - - /* t2 = HH = H^2. */ - fp4_sqr(t2, t3); - - /* If E is zero. */ - if (fp4_is_zero(t3)) { - if (fp4_is_zero(t1)) { - /* If I is zero, p = q, should have doubled. */ - ep4_dbl_projc(r, p); - } else { - /* If I is not zero, q = -p, r = infinity. */ - ep4_set_infty(r); - } - } else { - /* t5 = J = H * HH. */ - fp4_mul(t5, t3, t2); - - /* t4 = V = x1 * HH. */ - fp4_mul(t4, p->x, t2); - - /* x3 = R^2 - J - 2 * V. */ - fp4_sqr(r->x, t1); - fp4_sub(r->x, r->x, t5); - fp4_dbl(t6, t4); - fp4_sub(r->x, r->x, t6); - - /* y3 = R * (V - x3) - Y1 * J. */ - fp4_sub(t4, t4, r->x); - fp4_mul(t4, t4, t1); - fp4_mul(t1, p->y, t5); - fp4_sub(r->y, t4, t1); - - if (p->coord != BASIC) { - /* z3 = z1 * H. */ - fp4_mul(r->z, p->z, t3); - } else { - /* z3 = H. */ - fp4_copy(r->z, t3); - } - } - r->coord = PROJC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp4_free(t0); - fp4_free(t1); - fp4_free(t2); - fp4_free(t3); - fp4_free(t4); - fp4_free(t5); - fp4_free(t6); - } -} +#if EP_ADD == JACOB || !defined(STRIP) -#endif +/** + * Adds a point represented in Jacobian coordinates to a point represented in + * affine coordinates on an ordinary prime elliptic curve. + * + * @param[out] r - the result. + * @param[in] p - the projective point. + * @param[in] q - the affine point. + */ +TMPL_ADD_JACOB_MIX(ep4, fp4); /** - * Adds two points represented in projective coordinates on an ordinary prime + * Adds two points represented in Jacobian coordinates on an ordinary prime * elliptic curve. * - * @param r - the result. - * @param p - the first point to add. - * @param q - the second point to add. + * @param[out] r - the result. + * @param[in] p - the first point to add. + * @param[in] q - the second point to add. */ -static void ep4_add_projc_imp(ep4_t r, const ep4_t p, const ep4_t q) { -#if defined(EP_MIXED) && defined(STRIP) - ep4_add_projc_mix(r, p, q); -#else /* General addition. */ - fp4_t t0, t1, t2, t3, t4, t5, t6; - - fp4_null(t0); - fp4_null(t1); - fp4_null(t2); - fp4_null(t3); - fp4_null(t4); - fp4_null(t5); - fp4_null(t6); - - RLC_TRY { - fp4_new(t0); - fp4_new(t1); - fp4_new(t2); - fp4_new(t3); - fp4_new(t4); - fp4_new(t5); - fp4_new(t6); - - if (q->coord == BASIC) { - ep4_add_projc_mix(r, p, q); - } else { - /* t0 = z1^2. */ - fp4_sqr(t0, p->z); - - /* t1 = z2^2. */ - fp4_sqr(t1, q->z); - - /* t2 = U1 = x1 * z2^2. */ - fp4_mul(t2, p->x, t1); - - /* t3 = U2 = x2 * z1^2. */ - fp4_mul(t3, q->x, t0); - - /* t6 = z1^2 + z2^2. */ - fp4_add(t6, t0, t1); - - /* t0 = S2 = y2 * z1^3. */ - fp4_mul(t0, t0, p->z); - fp4_mul(t0, t0, q->y); - - /* t1 = S1 = y1 * z2^3. */ - fp4_mul(t1, t1, q->z); - fp4_mul(t1, t1, p->y); - - /* t3 = H = U2 - U1. */ - fp4_sub(t3, t3, t2); - - /* t0 = R = 2 * (S2 - S1). */ - fp4_sub(t0, t0, t1); - - fp4_dbl(t0, t0); - - /* If E is zero. */ - if (fp4_is_zero(t3)) { - if (fp4_is_zero(t0)) { - /* If I is zero, p = q, should have doubled. */ - ep4_dbl_projc(r, p); - } else { - /* If I is not zero, q = -p, r = infinity. */ - ep4_set_infty(r); - } - } else { - /* t4 = I = (2*H)^2. */ - fp4_dbl(t4, t3); - fp4_sqr(t4, t4); - - /* t5 = J = H * I. */ - fp4_mul(t5, t3, t4); - - /* t4 = V = U1 * I. */ - fp4_mul(t4, t2, t4); - - /* x3 = R^2 - J - 2 * V. */ - fp4_sqr(r->x, t0); - fp4_sub(r->x, r->x, t5); - fp4_dbl(t2, t4); - fp4_sub(r->x, r->x, t2); - - /* y3 = R * (V - x3) - 2 * S1 * J. */ - fp4_sub(t4, t4, r->x); - fp4_mul(t4, t4, t0); - fp4_mul(t1, t1, t5); - fp4_dbl(t1, t1); - fp4_sub(r->y, t4, t1); - - /* z3 = ((z1 + z2)^2 - z1^2 - z2^2) * H. */ - fp4_add(r->z, p->z, q->z); - fp4_sqr(r->z, r->z); - fp4_sub(r->z, r->z, t6); - fp4_mul(r->z, r->z, t3); - } - } - r->coord = PROJC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp4_free(t0); - fp4_free(t1); - fp4_free(t2); - fp4_free(t3); - fp4_free(t4); - fp4_free(t5); - fp4_free(t6); - } -#endif -} +TMPL_ADD_JACOB_IMP(ep4, fp4); -#endif /* EP_ADD == PROJC */ +#endif /* EP_ADD == JACOB */ /*============================================================================*/ /* Public definitions */ @@ -326,7 +136,7 @@ void ep4_add_slp_basic(ep4_t r, fp4_t s, const ep4_t p, const ep4_t q) { #endif -#if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) +#if EP_ADD == PROJC || !defined(STRIP) void ep4_add_projc(ep4_t r, const ep4_t p, const ep4_t q) { if (ep4_is_infty(p)) { @@ -339,13 +149,25 @@ void ep4_add_projc(ep4_t r, const ep4_t p, const ep4_t q) { return; } - if (p == q) { - /* TODO: This is a quick hack. Should we fix this? */ - ep4_dbl(r, p); + ep4_add_projc_imp(r, p, q); +} + +#endif + +#if EP_ADD == JACOB || !defined(STRIP) + +void ep4_add_jacob(ep4_t r, const ep4_t p, const ep4_t q) { + if (ep4_is_infty(p)) { + ep4_copy(r, q); return; } - ep4_add_projc_imp(r, p, q); + if (ep4_is_infty(q)) { + ep4_copy(r, p); + return; + } + + ep4_add_jacob_imp(r, p, q); } #endif @@ -362,7 +184,6 @@ void ep4_sub(ep4_t r, const ep4_t p, const ep4_t q) { RLC_TRY { ep4_new(t); - ep4_neg(t, q); ep4_add(r, p, t); } diff --git a/src/epx/relic_ep4_cmp.c b/src/epx/relic_ep4_cmp.c index d98f37a35..9df11b6a1 100644 --- a/src/epx/relic_ep4_cmp.c +++ b/src/epx/relic_ep4_cmp.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of utilities for prime elliptic curves over quadratic - * extensions. + * Implementation of utilities for prime elliptic curves over a quartic + * extension field. * * @ingroup epx */ @@ -37,46 +37,68 @@ /*============================================================================*/ int ep4_cmp(const ep4_t p, const ep4_t q) { - ep4_t r, s; - int result = RLC_NE; + ep4_t r, s; + int result = RLC_NE; if (ep4_is_infty(p) && ep4_is_infty(q)) { return RLC_EQ; } - ep4_null(r); - ep4_null(s); + ep4_null(r); + ep4_null(s); - RLC_TRY { - ep4_new(r); - ep4_new(s); + RLC_TRY { + ep4_new(r); + ep4_new(s); - if ((p->coord != BASIC) && (q->coord != BASIC)) { - /* If the two points are not normalized, it is faster to compare - * x1 * z2^2 == x2 * z1^2 and y1 * z2^3 == y2 * z1^3. */ - fp4_sqr(r->z, p->z); - fp4_sqr(s->z, q->z); - fp4_mul(r->x, p->x, s->z); - fp4_mul(s->x, q->x, r->z); - fp4_mul(r->z, r->z, p->z); - fp4_mul(s->z, s->z, q->z); - fp4_mul(r->y, p->y, s->z); - fp4_mul(s->y, q->y, r->z); - } else { - ep4_norm(r, p); - ep4_norm(s, q); - } + switch (q->coord) { + case PROJC: + /* If q is in homogeneous projective coordinates, compute + * x1 * z2 and y1 * z2. */ + fp4_mul(r->x, p->x, q->z); + fp4_mul(r->y, p->y, q->z); + break; + case JACOB: + /* If q is in Jacobian projective coordinates, compute + * x2 * z1^2 and y2 * z1^3. */ + fp4_sqr(r->z, q->z); + fp4_mul(r->x, p->x, r->z); + fp4_mul(r->z, r->z, q->z); + fp4_mul(r->y, p->y, r->z); + break; + default: + ep4_copy(r, p); + break; + } - if ((fp4_cmp(r->x, s->x) == RLC_EQ) && - (fp4_cmp(r->y, s->y) == RLC_EQ)) { - result = RLC_EQ; - } - } RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } RLC_FINALLY { - ep4_free(r); - ep4_free(s); - } + switch (p->coord) { + /* Now do the same for the other point. */ + case PROJC: + fp4_mul(s->x, q->x, p->z); + fp4_mul(s->y, q->y, p->z); + break; + case JACOB: + fp4_sqr(s->z, p->z); + fp4_mul(s->x, q->x, s->z); + fp4_mul(s->z, s->z, p->z); + fp4_mul(s->y, q->y, s->z); + break; + default: + ep4_copy(s, q); + break; + } - return result; + if ((fp4_cmp(r->x, s->x) == RLC_EQ) && (fp4_cmp(r->y, s->y) == RLC_EQ)) { + result = RLC_EQ; + } + } + RLC_CATCH_ANY { + RLC_THROW(ERR_CAUGHT); + } + RLC_FINALLY { + ep4_free(r); + ep4_free(s); + } + + return result; } diff --git a/src/epx/relic_ep4_curve.c b/src/epx/relic_ep4_curve.c index df4488451..21e8a87fe 100644 --- a/src/epx/relic_ep4_curve.c +++ b/src/epx/relic_ep4_curve.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of configuration of prime elliptic curves over quartic - * extensions. + * Implementation of configuration of prime elliptic curves over a quartic + * extension field. * * @ingroup epx */ @@ -335,6 +335,67 @@ int ep4_curve_opt_b(void) { return core_get()->ep4_opt_b; } +void ep4_curve_mul_a(fp4_t c, const fp4_t a) { + ctx_t *ctx = core_get(); + if (ep4_curve_is_twist() == RLC_EP_MTYPE) { + switch (ctx->ep_opt_a) { + case RLC_ZERO: + fp4_zero(c); + break; + case RLC_ONE: + fp4_copy(c, a); + break; + case RLC_TWO: + fp4_dbl(c, a); + break; + default: + fp4_mul(c, a, ctx->ep4_a); + break; + } + fp4_mul_art(c, c); + } else { + switch (ctx->ep8_opt_a) { + case RLC_ZERO: + fp4_zero(c); + break; + case RLC_ONE: + fp4_copy(c, a); + break; + case RLC_TWO: + fp4_dbl(c, a); + break; +#if FP_RDC != MONTY + case RLC_TINY: + fp4_mul_dig(c, a, ctx->ep4_a[0]); + break; +#endif + default: + fp4_mul(c, a, ctx->ep4_a); + break; + } + } +} + +void ep4_curve_mul_b(fp4_t c, const fp4_t a) { + ctx_t *ctx = core_get(); + switch (ctx->ep4_opt_b) { + case RLC_ZERO: + fp4_zero(c); + break; + case RLC_ONE: + fp4_copy(c, a); + break; +#if FP_RDC != MONTY + case RLC_TINY: + fp4_mul_dig(c, a, ctx->ep4_b[0]); + break; +#endif + default: + fp4_mul(c, a, ctx->ep4_b); + break; + } +} + int ep4_curve_is_twist(void) { return core_get()->ep4_is_twist; } diff --git a/src/epx/relic_ep4_dbl.c b/src/epx/relic_ep4_dbl.c index abe004128..d3978bba1 100644 --- a/src/epx/relic_ep4_dbl.c +++ b/src/epx/relic_ep4_dbl.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of doubling on elliptic prime curves over quartic - * extensions. + * Implementation of doubling on elliptic prime curves over a quartic extension + * field. * * @ingroup epx */ @@ -44,141 +44,41 @@ * elliptic curve. * * @param[out] r - the result. - * @param[out] s - the resulting slope. + * @param[out] s - the slope. * @param[in] p - the point to double. */ TMPL_DBL_BASIC_IMP(ep4, fp4); #endif /* EP_ADD == BASIC */ -#if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) +#if EP_ADD == PROJC || !defined(STRIP) /** - * Doubles a point represented in affine coordinates on an ordinary prime + * Doubles a point represented in projective coordinates on an ordinary prime * elliptic curve. * - * @param[out] r - the result. - * @param[in] p - the point to double. + * @param r - the result. + * @param p - the point to double. */ -static void ep4_dbl_projc_imp(ep4_t r, const ep4_t p) { - fp4_t t0, t1, t2, t3, t4, t5; - - fp4_null(t0); - fp4_null(t1); - fp4_null(t2); - fp4_null(t3); - fp4_null(t4); - fp4_null(t5); - - RLC_TRY { - fp4_new(t0); - fp4_new(t1); - fp4_new(t2); - fp4_new(t3); - fp4_new(t4); - fp4_new(t5); - - if (ep_curve_opt_a() == RLC_ZERO) { - fp4_sqr(t0, p->x); - fp4_add(t2, t0, t0); - fp4_add(t0, t2, t0); - - fp4_sqr(t3, p->y); - fp4_mul(t1, t3, p->x); - fp4_add(t1, t1, t1); - fp4_add(t1, t1, t1); - fp4_sqr(r->x, t0); - fp4_add(t2, t1, t1); - fp4_sub(r->x, r->x, t2); - fp4_mul(r->z, p->z, p->y); - fp4_add(r->z, r->z, r->z); - fp4_add(t3, t3, t3); - - fp4_sqr(t3, t3); - fp4_add(t3, t3, t3); - fp4_sub(t1, t1, r->x); - fp4_mul(r->y, t0, t1); - fp4_sub(r->y, r->y, t3); - } else { - /* dbl-2007-bl formulas: 1M + 8S + 1*a + 10add + 1*8 + 2*2 + 1*3 */ - /* http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-2007-bl */ - - /* t0 = x1^2, t1 = y1^2, t2 = y1^4. */ - fp4_sqr(t0, p->x); - fp4_sqr(t1, p->y); - fp4_sqr(t2, t1); - - if (p->coord != BASIC) { - /* t3 = z1^2. */ - fp4_sqr(t3, p->z); - - if (ep_curve_opt_a() == RLC_ZERO) { - /* z3 = 2 * y1 * z1. */ - fp4_mul(r->z, p->y, p->z); - fp4_dbl(r->z, r->z); - } else { - /* z3 = (y1 + z1)^2 - y1^2 - z1^2. */ - fp4_add(r->z, p->y, p->z); - fp4_sqr(r->z, r->z); - fp4_sub(r->z, r->z, t1); - fp4_sub(r->z, r->z, t3); - } - } else { - /* z3 = 2 * y1. */ - fp4_dbl(r->z, p->y); - } - - /* t4 = S = 2*((x1 + y1^2)^2 - x1^2 - y1^4). */ - fp4_add(t4, p->x, t1); - fp4_sqr(t4, t4); - fp4_sub(t4, t4, t0); - fp4_sub(t4, t4, t2); - fp4_dbl(t4, t4); - - /* t5 = M = 3 * x1^2 + a * z1^4. */ - fp4_dbl(t5, t0); - fp4_add(t5, t5, t0); - if (p->coord != BASIC) { - fp4_sqr(t3, t3); - fp4_mul(t1, t3, ep4_curve_get_a()); - fp4_add(t5, t5, t1); - } else { - fp4_add(t5, t5, ep4_curve_get_a()); - } - - /* x3 = T = M^2 - 2 * S. */ - fp4_sqr(r->x, t5); - fp4_dbl(t1, t4); - fp4_sub(r->x, r->x, t1); - - /* y3 = M * (S - T) - 8 * y1^4. */ - fp4_dbl(t2, t2); - fp4_dbl(t2, t2); - fp4_dbl(t2, t2); - fp4_sub(t4, t4, r->x); - fp4_mul(t5, t5, t4); - fp4_sub(r->y, t5, t2); - } - - r->coord = PROJC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp4_free(t0); - fp4_free(t1); - fp4_free(t2); - fp4_free(t3); - fp4_free(t4); - fp4_free(t5); - } -} +TMPL_DBL_PROJC_IMP(ep4, fp4); #endif /* EP_ADD == PROJC */ +#if EP_ADD == JACOB || !defined(STRIP) + +/** + * Doubles a point represented in Jacobian coordinates on an ordinary prime + * elliptic curve. + * + * @param r - the result. + * @param p - the point to double. + */ +TMPL_DBL_JACOB_IMP(ep4, fp4); + +#endif /* EP_ADD == JACOB */ + /*============================================================================*/ - /* Public definitions */ +/* Public definitions */ /*============================================================================*/ #if EP_ADD == BASIC || !defined(STRIP) @@ -188,7 +88,6 @@ void ep4_dbl_basic(ep4_t r, const ep4_t p) { ep4_set_infty(r); return; } - ep4_dbl_basic_imp(r, NULL, p); } @@ -203,7 +102,7 @@ void ep4_dbl_slp_basic(ep4_t r, fp4_t s, const ep4_t p) { #endif -#if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) +#if EP_ADD == PROJC || !defined(STRIP) void ep4_dbl_projc(ep4_t r, const ep4_t p) { if (ep4_is_infty(p)) { @@ -215,3 +114,16 @@ void ep4_dbl_projc(ep4_t r, const ep4_t p) { } #endif + +#if EP_ADD == JACOB || !defined(STRIP) + +void ep4_dbl_jacob(ep4_t r, const ep4_t p) { + if (ep4_is_infty(p)) { + ep4_set_infty(r); + return; + } + + ep4_dbl_jacob_imp(r, p); +} + +#endif diff --git a/src/epx/relic_ep4_frb.c b/src/epx/relic_ep4_frb.c index 0cea9557d..601aef677 100644 --- a/src/epx/relic_ep4_frb.c +++ b/src/epx/relic_ep4_frb.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of frobenius action on prime elliptic curves over - * quartic extensions. + * Implementation of frobenius action on prime elliptic curves over a quartic + * extension field. * * @ingroup epx */ diff --git a/src/epx/relic_ep4_map.c b/src/epx/relic_ep4_map.c index cb01375af..bbcfba746 100644 --- a/src/epx/relic_ep4_map.c +++ b/src/epx/relic_ep4_map.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of hashing to a prime elliptic curve over a quadratic - * extension. + * Implementation of hashing to a prime elliptic curve over a quartic extension + * field. * * @ingroup epx */ diff --git a/src/epx/relic_ep4_mul.c b/src/epx/relic_ep4_mul.c index d8b605e40..a6cb8a5ba 100644 --- a/src/epx/relic_ep4_mul.c +++ b/src/epx/relic_ep4_mul.c @@ -24,20 +24,17 @@ /** * @file * - * Implementation of point multiplication on prime elliptic curves over - * quadratic extensions. + * Implementation of point multiplication on prime elliptic curves over a + * quartic extension field. * * @ingroup epx */ - #include "relic_core.h" /*============================================================================*/ /* Private definitions */ /*============================================================================*/ -#if EP_MUL == LWNAF || !defined(STRIP) - #if defined(EP_ENDOM) static void ep4_psi(ep4_t r, const ep4_t p) { @@ -53,6 +50,8 @@ static void ep4_psi(ep4_t r, const ep4_t p) { RLC_TRY { ep4_new(q); + ep4_copy(r, p); + switch (ep_curve_is_pairf()) { case EP_K16: /* u = (2*p^5 - p) mod r */ @@ -81,6 +80,8 @@ static void ep4_psi(ep4_t r, const ep4_t p) { } } +#if EP_MUL == LWNAF || !defined(STRIP) + static void ep4_mul_glv_imp(ep4_t r, const ep4_t p, const bn_t k) { size_t l, _l[8]; bn_t n, _k[8], u; @@ -154,10 +155,133 @@ static void ep4_mul_glv_imp(ep4_t r, const ep4_t p, const bn_t k) { } } +#endif /* EP_MUL == LWNAF */ + +#if EP_MUL == LWREG || !defined(STRIP) + +static void ep4_mul_reg_gls(ep4_t r, const ep4_t p, const bn_t k) { + int8_t reg[8][RLC_FP_BITS + 1], b[8], s[8], c0, n0; + ep4_t q, w, t[8][1 << (RLC_WIDTH - 2)]; + bn_t n, _k[8], u; + size_t l, len, _l[8]; + + bn_null(n); + bn_null(u); + ep4_null(q); + ep4_null(w); + + RLC_TRY { + bn_new(n); + bn_new(u); + ep4_new(q); + ep4_new(w); + for (size_t i = 0; i < 8; i++) { + bn_null(_k[i]); + bn_new(_k[i]); + for (size_t j = 0; j < (1 << (RLC_WIDTH - 2)); j++) { + ep4_null(t[i][j]); + ep4_new(t[i][j]); + } + } + + ep4_curve_get_ord(n); + fp_prime_get_par(u); + bn_mod(_k[0], k, n); + bn_rec_frb(_k, 8, _k[0], u, n, ep_curve_is_pairf() == EP_BN); + + l = 0; + /* Make some extra room for BN curves that grow subscalars by 1. */ + len = bn_bits(u) + (ep_curve_is_pairf() == EP_BN); + ep4_norm(t[0][0], p); + for (size_t i = 0; i < 8; i++) { + s[i] = bn_sign(_k[i]); + bn_abs(_k[i], _k[i]); + b[i] = bn_is_even(_k[i]); + _k[i]->dp[0] |= b[i]; + + _l[i] = RLC_FP_BITS + 1; + bn_rec_reg(reg[i], &_l[i], _k[i], len, RLC_WIDTH); + l = RLC_MAX(l, _l[i]); + + /* Apply Frobenius before flipping sign to build table. */ + if (i > 0) { + ep4_psi(t[i][0], t[i - 1][0]); + } + } + + for (size_t i = 0; i < 8; i++) { + ep4_neg(q, t[i][0]); + fp4_copy_sec(q->y, t[i][0]->y, s[i] == RLC_POS); + ep4_tab(t[i], q, RLC_WIDTH); + } + +#if defined(EP_MIXED) + fp4_set_dig(w->z, 1); + w->coord = BASIC; +#else + w->coord = = EP_ADD; +#endif + + ep4_set_infty(r); + for (int j = l - 1; j >= 0; j--) { + for (size_t i = 0; i < RLC_WIDTH - 1; i++) { + ep4_dbl(r, r); + } + + for (size_t i = 0; i < 8; i++) { + n0 = reg[i][j]; + c0 = (n0 >> 7); + n0 = ((n0 ^ c0) - c0) >> 1; + + for (size_t m = 0; m < (1 << (RLC_WIDTH - 2)); m++) { + fp4_copy_sec(w->x, t[i][m]->x, m == n0); + fp4_copy_sec(w->y, t[i][m]->y, m == n0); + #if !defined(EP_MIXED) + fp4_copy_sec(w->z, t[i][m]->z, m == n0); + #endif + } + + ep4_neg(q, w); + fp4_copy_sec(q->y, w->y, c0 == 0); + ep4_add(r, r, q); + } + } + + for (size_t i = 0; i < 8; i++) { + /* Tables are built with points already negated, so no need here. */ + ep4_sub(q, r, t[i][0]); + fp4_copy_sec(r->x, q->x, b[i]); + fp4_copy_sec(r->y, q->y, b[i]); + fp4_copy_sec(r->z, q->z, b[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); + ep4_free(q); + ep4_free(w); + for (int i = 0; i < 4; i++) { + bn_free(_k[i]); + for (size_t j = 0; j < (1 << (RLC_WIDTH - 2)); j++) { + ep4_free(t[i][j]); + } + } + } +} + +#endif /* EP_MUL == LWREG */ #endif /* EP_ENDOM */ #if defined(EP_PLAIN) || defined(EP_SUPER) +#if EP_MUL == LWNAF || !defined(STRIP) + static void ep4_mul_naf_imp(ep4_t r, const ep4_t p, const bn_t k) { int i, n; int8_t naf[RLC_FP_BITS + 1]; @@ -206,9 +330,95 @@ static void ep4_mul_naf_imp(ep4_t r, const ep4_t p, const bn_t k) { } } -#endif /* EP_PLAIN || EP_SUPER */ #endif /* EP_MUL == LWNAF */ +#if EP_MUL == LWREG || !defined(STRIP) + +static void ep4_mul_reg_imp(ep4_t r, const ep4_t p, const bn_t k) { + bn_t _k; + int8_t s, reg[1 + RLC_CEIL(RLC_FP_BITS + 1, RLC_WIDTH - 1)]; + ep4_t t[1 << (RLC_WIDTH - 2)], u, v; + size_t l, n; + + bn_null(_k); + + RLC_TRY { + bn_new(_k); + ep4_new(u); + ep4_new(v); + /* Prepare the precomputation table. */ + for (size_t i = 0; i < (1 << (RLC_WIDTH - 2)); i++) { + ep4_null(t[i]); + ep4_new(t[i]); + } + /* Compute the precomputation table. */ + ep4_tab(t, p, RLC_WIDTH); + + ep4_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, RLC_WIDTH - 1) + 1; + bn_rec_reg(reg, &l, _k, n, RLC_WIDTH); + +#if defined(EP_MIXED) + fp4_set_dig(u->z, 1); + u->coord = BASIC; +#else + u->coord = EP_ADD; +#endif + ep4_set_infty(r); + for (int i = l - 1; i >= 0; i--) { + for (size_t j = 0; j < RLC_WIDTH - 1; j++) { + ep4_dbl(r, r); + } + + n = reg[i]; + s = (n >> 7); + n = ((n ^ s) - s) >> 1; + + for (size_t j = 0; j < (1 << (RLC_WIDTH - 2)); j++) { + fp4_copy_sec(u->x, t[j]->x, j == n); + fp4_copy_sec(u->y, t[j]->y, j == n); +#if !defined(EP_MIXED) + fp_copy_sec(u->z, t[j]->z, j == n); +#endif + } + ep4_neg(v, u); + fp4_copy_sec(u->y, v->y, s != 0); + ep4_add(r, r, u); + } + /* t[0] has an unmodified copy of p. */ + ep4_sub(u, r, t[0]); + fp4_copy_sec(r->x, u->x, bn_is_even(k)); + fp4_copy_sec(r->y, u->y, bn_is_even(k)); + fp4_copy_sec(r->z, u->z, bn_is_even(k)); + /* Convert r to affine coordinates. */ + ep4_norm(r, r); + ep4_neg(u, r); + fp4_copy_sec(r->y, u->y, bn_sign(k) == RLC_NEG); + } + RLC_CATCH_ANY { + RLC_THROW(ERR_CAUGHT); + } + RLC_FINALLY { + /* Free the precomputation table. */ + for (size_t i = 0; i < (1 << (RLC_WIDTH - 2)); i++) { + ep4_free(t[i]); + } + bn_free(_k); + ep4_free(u); + ep4_free(v); + } +} + +#endif /* EP_MUL == LWREG */ +#endif /* EP_PLAIN || EP_SUPER */ + /*============================================================================*/ /* Public definitions */ /*============================================================================*/ @@ -449,6 +659,28 @@ void ep4_mul_lwnaf(ep4_t r, const ep4_t p, const bn_t k) { #endif +#if EP_MUL == LWREG || !defined(STRIP) + +void ep4_mul_lwreg(ep4_t r, const ep4_t p, const bn_t k) { + if (bn_is_zero(k) || ep4_is_infty(p)) { + ep4_set_infty(r); + return; + } + +#if defined(EP_ENDOM) + if (ep_curve_is_endom()) { + ep4_mul_reg_gls(r, p, k); + return; + } +#endif + +#if defined(EP_PLAIN) || defined(EP_SUPER) + ep4_mul_reg_imp(r, p, k); +#endif +} + +#endif + void ep4_mul_gen(ep4_t r, const bn_t k) { if (bn_is_zero(k)) { ep4_set_infty(r); diff --git a/src/epx/relic_ep4_mul_cof.c b/src/epx/relic_ep4_mul_cof.c index 31fa4a6af..459a64f87 100644 --- a/src/epx/relic_ep4_mul_cof.c +++ b/src/epx/relic_ep4_mul_cof.c @@ -25,7 +25,7 @@ * @file * * Implementation of point multiplication of a prime elliptic curve over a - * quartic extension by the curve cofactor. + * quartic extension field by the curve cofactor. * * @ingroup epx */ diff --git a/src/epx/relic_ep4_mul_fix.c b/src/epx/relic_ep4_mul_fix.c index 33b8dac4c..9aa789bbb 100644 --- a/src/epx/relic_ep4_mul_fix.c +++ b/src/epx/relic_ep4_mul_fix.c @@ -25,7 +25,7 @@ * @file * * Implementation of fixed point multiplication on a prime elliptic curve over - * a quartic extension. + * a quartic extension field. * * @ingroup epx */ diff --git a/src/epx/relic_ep4_mul_sim.c b/src/epx/relic_ep4_mul_sim.c index 719332f38..ff4c62c3b 100644 --- a/src/epx/relic_ep4_mul_sim.c +++ b/src/epx/relic_ep4_mul_sim.c @@ -25,7 +25,7 @@ * @file * * Implementation of simultaneous point multiplication on a prime elliptic - * curve over a quartic extension. + * curve over a quartic extension field. * * @ingroup epx */ diff --git a/src/epx/relic_ep4_neg.c b/src/epx/relic_ep4_neg.c index 604fc116e..f2cf6b952 100644 --- a/src/epx/relic_ep4_neg.c +++ b/src/epx/relic_ep4_neg.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of point negation on elliptic prime curves over quartic - * extensions. + * Implementation of point negation on elliptic prime curves over a quartic + * extension field. * * @ingroup epx */ diff --git a/src/epx/relic_ep4_norm.c b/src/epx/relic_ep4_norm.c index ca2646dd5..6576d3ace 100644 --- a/src/epx/relic_ep4_norm.c +++ b/src/epx/relic_ep4_norm.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of point normalization on prime elliptic curves over quartic - * extensions. + * Implementation of point normalization on prime elliptic curves over a quartic + * extension field. * * @ingroup epx */ @@ -43,36 +43,45 @@ * * @param r - the result. * @param p - the point to normalize. + * @param inv - the flag to indicate if z is already inverted. */ -static void ep4_norm_imp(ep4_t r, const ep4_t p, int inverted) { +static void ep4_norm_imp(ep4_t r, const ep4_t p, int inv) { if (p->coord != BASIC) { - fp4_t t0, t1; + fp4_t t; - fp4_null(t0); - fp4_null(t1); + fp4_null(t); RLC_TRY { + fp4_new(t); - fp4_new(t0); - fp4_new(t1); - - if (inverted) { - fp4_copy(t1, p->z); + if (inv) { + fp4_copy(r->z, p->z); } else { - fp4_inv(t1, p->z); + fp4_inv(r->z, p->z); + } + + switch (p->coord) { + case PROJC: + fp4_mul(r->x, p->x, r->z); + fp4_mul(r->y, p->y, r->z); + break; + case JACOB: + fp4_sqr(t, r->z); + fp4_mul(r->x, p->x, t); + fp4_mul(t, t, r->z); + fp4_mul(r->y, p->y, t); + break; + default: + ep4_copy(r, p); + break; } - fp4_sqr(t0, t1); - fp4_mul(r->x, p->x, t0); - fp4_mul(t0, t0, t1); - fp4_mul(r->y, p->y, t0); fp4_set_dig(r->z, 1); } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); } RLC_FINALLY { - fp4_free(t0); - fp4_free(t1); + fp4_free(t); } } @@ -92,17 +101,18 @@ void ep4_norm(ep4_t r, const ep4_t p) { } if (p->coord == BASIC) { - /* If the point is represented in affine coordinates, we just copy it. */ + /* If the point is represented in affine coordinates, just copy it. */ ep4_copy(r, p); + return; } #if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) ep4_norm_imp(r, p, 0); -#endif +#endif /* EP_ADD == PROJC */ } void ep4_norm_sim(ep4_t *r, const ep4_t *t, int n) { int i; - fp4_t *a = RLC_ALLOCA(fp4_t, n); + fp4_t* a = RLC_ALLOCA(fp4_t, n); RLC_TRY { if (a == NULL) { @@ -114,18 +124,20 @@ void ep4_norm_sim(ep4_t *r, const ep4_t *t, int n) { fp4_copy(a[i], t[i]->z); } - fp4_inv_sim(a, a, n); + fp4_inv_sim(a, (const fp4_t *)a, n); for (i = 0; i < n; i++) { fp4_copy(r[i]->x, t[i]->x); fp4_copy(r[i]->y, t[i]->y); - fp4_copy(r[i]->z, a[i]); + if (!ep4_is_infty(t[i])) { + fp4_copy(r[i]->z, a[i]); + } } #if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) for (i = 0; i < n; i++) { ep4_norm_imp(r[i], r[i], 1); } -#endif +#endif /* EP_ADD == PROJC */ } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); diff --git a/src/epx/relic_ep4_util.c b/src/epx/relic_ep4_util.c index 9bbd4f4b9..8564d9cca 100644 --- a/src/epx/relic_ep4_util.c +++ b/src/epx/relic_ep4_util.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of comparison for points on prime elliptic curves over - * quartic extensions. + * Implementation of comparison for points on prime elliptic curves over a + * quartic extension field. * * @ingroup epx */ @@ -89,13 +89,18 @@ void ep4_blind(ep4_t r, const ep4_t p) { #if EP_ADD == BASIC (void)rand; ep4_copy(r, p); -#else +#elif EP_ADD == PROJC + fp4_mul(r->x, p->x, rand); + fp4_mul(r->y, p->y, rand); + fp4_mul(r->z, p->z, rand); + r->coord = PROJC; +#elif EP_ADD == JACOB fp4_mul(r->z, p->z, rand); fp4_mul(r->y, p->y, rand); fp4_sqr(rand, rand); fp4_mul(r->x, r->x, rand); fp4_mul(r->y, r->y, rand); - r->coord = EP_ADD; + r->coord = JACOB; #endif } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); @@ -104,7 +109,7 @@ void ep4_blind(ep4_t r, const ep4_t p) { } } -void ep4_rhs(fp4_t rhs, const ep4_t p) { +void ep4_rhs(fp4_t rhs, const fp4_t x) { fp4_t t0; fp4_null(t0); @@ -112,7 +117,7 @@ void ep4_rhs(fp4_t rhs, const ep4_t p) { RLC_TRY { fp4_new(t0); - fp4_sqr(t0, p->x); /* x1^2 */ + fp4_sqr(t0, x); switch (ep4_curve_opt_a()) { case RLC_ZERO: @@ -136,7 +141,7 @@ void ep4_rhs(fp4_t rhs, const ep4_t p) { break; } - fp4_mul(t0, t0, p->x); /* x1^3 + a * x */ + fp4_mul(t0, t0, x); switch (ep4_curve_opt_b()) { case RLC_ZERO: @@ -179,7 +184,7 @@ int ep4_on_curve(const ep4_t p) { ep4_norm(t, p); - ep4_rhs(t->x, t); + ep4_rhs(t->x, t->x); fp4_sqr(t->y, t->y); r = (fp4_cmp(t->x, t->y) == RLC_EQ) || ep4_is_infty(p); diff --git a/src/epx/relic_ep8_add.c b/src/epx/relic_ep8_add.c index e0316eaff..687998124 100644 --- a/src/epx/relic_ep8_add.c +++ b/src/epx/relic_ep8_add.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of addition on prime elliptic curves over quartic - * extensions. + * Implementation of addition on prime elliptic curves over an octic extension + * field. * * @ingroup epx */ @@ -52,243 +52,53 @@ TMPL_ADD_BASIC_IMP(ep8, fp8); #endif /* EP_ADD == BASIC */ -#if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) +#if EP_ADD == PROJC || !defined(STRIP) -#if defined(EP_MIXED) || !defined(STRIP) +/** + * Adds a point represented in homogeneous coordinates to a point represented in + * affine coordinates on an ordinary prime elliptic curve. + * + * @param[out] r - the result. + * @param[in] p - the projective point. + * @param[in] q - the affine point. + */ +TMPL_ADD_PROJC_MIX(ep8, fp8); /** - * Adds a point represented in affine coordinates to a point represented in - * projective coordinates. + * Adds two points represented in homogeneous coordinates on an ordinary prime + * elliptic curve. * - * @param r - the result. - * @param s - the slope. - * @param p - the affine point. - * @param q - the projective point. + * @param[out] r - the result. + * @param[in] p - the first point to add. + * @param[in] q - the second point to add. */ -static void ep8_add_projc_mix(ep8_t r, const ep8_t p, const ep8_t q) { - fp8_t t0, t1, t2, t3, t4, t5, t6; +TMPL_ADD_PROJC_IMP(ep8, fp8); - fp8_null(t0); - fp8_null(t1); - fp8_null(t2); - fp8_null(t3); - fp8_null(t4); - fp8_null(t5); - fp8_null(t6); +#endif /* EP_ADD == PROJC */ - RLC_TRY { - fp8_new(t0); - fp8_new(t1); - fp8_new(t2); - fp8_new(t3); - fp8_new(t4); - fp8_new(t5); - fp8_new(t6); - - if (p->coord != BASIC) { - /* t0 = z1^2. */ - fp8_sqr(t0, p->z); - - /* t3 = U2 = x2 * z1^2. */ - fp8_mul(t3, q->x, t0); - - /* t1 = S2 = y2 * z1^3. */ - fp8_mul(t1, t0, p->z); - fp8_mul(t1, t1, q->y); - - /* t3 = H = U2 - x1. */ - fp8_sub(t3, t3, p->x); - - /* t1 = R = 2 * (S2 - y1). */ - fp8_sub(t1, t1, p->y); - } else { - /* H = x2 - x1. */ - fp8_sub(t3, q->x, p->x); - - /* t1 = R = 2 * (y2 - y1). */ - fp8_sub(t1, q->y, p->y); - } - - /* t2 = HH = H^2. */ - fp8_sqr(t2, t3); - - /* If E is zero. */ - if (fp8_is_zero(t3)) { - if (fp8_is_zero(t1)) { - /* If I is zero, p = q, should have doubled. */ - ep8_dbl_projc(r, p); - } else { - /* If I is not zero, q = -p, r = infinity. */ - ep8_set_infty(r); - } - } else { - /* t5 = J = H * HH. */ - fp8_mul(t5, t3, t2); - - /* t4 = V = x1 * HH. */ - fp8_mul(t4, p->x, t2); - - /* x3 = R^2 - J - 2 * V. */ - fp8_sqr(r->x, t1); - fp8_sub(r->x, r->x, t5); - fp8_dbl(t6, t4); - fp8_sub(r->x, r->x, t6); - - /* y3 = R * (V - x3) - Y1 * J. */ - fp8_sub(t4, t4, r->x); - fp8_mul(t4, t4, t1); - fp8_mul(t1, p->y, t5); - fp8_sub(r->y, t4, t1); - - if (p->coord != BASIC) { - /* z3 = z1 * H. */ - fp8_mul(r->z, p->z, t3); - } else { - /* z3 = H. */ - fp8_copy(r->z, t3); - } - } - r->coord = PROJC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp8_free(t0); - fp8_free(t1); - fp8_free(t2); - fp8_free(t3); - fp8_free(t4); - fp8_free(t5); - fp8_free(t6); - } -} +#if EP_ADD == JACOB || !defined(STRIP) -#endif +/** + * Adds a point represented in Jacobian coordinates to a point represented in + * affine coordinates on an ordinary prime elliptic curve. + * + * @param[out] r - the result. + * @param[in] p - the projective point. + * @param[in] q - the affine point. + */ +TMPL_ADD_JACOB_MIX(ep8, fp8); /** - * Adds two points represented in projective coordinates on an ordinary prime + * Adds two points represented in Jacobian coordinates on an ordinary prime * elliptic curve. * - * @param r - the result. - * @param p - the first point to add. - * @param q - the second point to add. + * @param[out] r - the result. + * @param[in] p - the first point to add. + * @param[in] q - the second point to add. */ -static void ep8_add_projc_imp(ep8_t r, const ep8_t p, const ep8_t q) { -#if defined(EP_MIXED) && defined(STRIP) - ep8_add_projc_mix(r, p, q); -#else /* General addition. */ - fp8_t t0, t1, t2, t3, t4, t5, t6; - - fp8_null(t0); - fp8_null(t1); - fp8_null(t2); - fp8_null(t3); - fp8_null(t4); - fp8_null(t5); - fp8_null(t6); - - RLC_TRY { - fp8_new(t0); - fp8_new(t1); - fp8_new(t2); - fp8_new(t3); - fp8_new(t4); - fp8_new(t5); - fp8_new(t6); - - if (q->coord == BASIC) { - ep8_add_projc_mix(r, p, q); - } else { - /* t0 = z1^2. */ - fp8_sqr(t0, p->z); - - /* t1 = z2^2. */ - fp8_sqr(t1, q->z); - - /* t2 = U1 = x1 * z2^2. */ - fp8_mul(t2, p->x, t1); - - /* t3 = U2 = x2 * z1^2. */ - fp8_mul(t3, q->x, t0); - - /* t6 = z1^2 + z2^2. */ - fp8_add(t6, t0, t1); - - /* t0 = S2 = y2 * z1^3. */ - fp8_mul(t0, t0, p->z); - fp8_mul(t0, t0, q->y); - - /* t1 = S1 = y1 * z2^3. */ - fp8_mul(t1, t1, q->z); - fp8_mul(t1, t1, p->y); - - /* t3 = H = U2 - U1. */ - fp8_sub(t3, t3, t2); - - /* t0 = R = 2 * (S2 - S1). */ - fp8_sub(t0, t0, t1); - - fp8_dbl(t0, t0); - - /* If E is zero. */ - if (fp8_is_zero(t3)) { - if (fp8_is_zero(t0)) { - /* If I is zero, p = q, should have doubled. */ - ep8_dbl_projc(r, p); - } else { - /* If I is not zero, q = -p, r = infinity. */ - ep8_set_infty(r); - } - } else { - /* t4 = I = (2*H)^2. */ - fp8_dbl(t4, t3); - fp8_sqr(t4, t4); - - /* t5 = J = H * I. */ - fp8_mul(t5, t3, t4); - - /* t4 = V = U1 * I. */ - fp8_mul(t4, t2, t4); - - /* x3 = R^2 - J - 2 * V. */ - fp8_sqr(r->x, t0); - fp8_sub(r->x, r->x, t5); - fp8_dbl(t2, t4); - fp8_sub(r->x, r->x, t2); - - /* y3 = R * (V - x3) - 2 * S1 * J. */ - fp8_sub(t4, t4, r->x); - fp8_mul(t4, t4, t0); - fp8_mul(t1, t1, t5); - fp8_dbl(t1, t1); - fp8_sub(r->y, t4, t1); - - /* z3 = ((z1 + z2)^2 - z1^2 - z2^2) * H. */ - fp8_add(r->z, p->z, q->z); - fp8_sqr(r->z, r->z); - fp8_sub(r->z, r->z, t6); - fp8_mul(r->z, r->z, t3); - } - } - r->coord = PROJC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp8_free(t0); - fp8_free(t1); - fp8_free(t2); - fp8_free(t3); - fp8_free(t4); - fp8_free(t5); - fp8_free(t6); - } -#endif -} +TMPL_ADD_JACOB_IMP(ep8, fp8); -#endif /* EP_ADD == PROJC */ +#endif /* EP_ADD == JACOB */ /*============================================================================*/ /* Public definitions */ @@ -326,7 +136,7 @@ void ep8_add_slp_basic(ep8_t r, fp8_t s, const ep8_t p, const ep8_t q) { #endif -#if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) +#if EP_ADD == PROJC || !defined(STRIP) void ep8_add_projc(ep8_t r, const ep8_t p, const ep8_t q) { if (ep8_is_infty(p)) { @@ -339,13 +149,25 @@ void ep8_add_projc(ep8_t r, const ep8_t p, const ep8_t q) { return; } - if (p == q) { - /* TODO: This is a quick hack. Should we fix this? */ - ep8_dbl(r, p); + ep8_add_projc_imp(r, p, q); +} + +#endif + +#if EP_ADD == JACOB || !defined(STRIP) + +void ep8_add_jacob(ep8_t r, const ep8_t p, const ep8_t q) { + if (ep8_is_infty(p)) { + ep8_copy(r, q); return; } - ep8_add_projc_imp(r, p, q); + if (ep8_is_infty(q)) { + ep8_copy(r, p); + return; + } + + ep8_add_jacob_imp(r, p, q); } #endif @@ -362,7 +184,6 @@ void ep8_sub(ep8_t r, const ep8_t p, const ep8_t q) { RLC_TRY { ep8_new(t); - ep8_neg(t, q); ep8_add(r, p, t); } diff --git a/src/epx/relic_ep8_cmp.c b/src/epx/relic_ep8_cmp.c index cf00e38a6..4e57b8a80 100644 --- a/src/epx/relic_ep8_cmp.c +++ b/src/epx/relic_ep8_cmp.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of utilities for prime elliptic curves over quadratic - * extensions. + * Implementation of utilities for prime elliptic curves over an octic + * extension field. * * @ingroup epx */ @@ -37,46 +37,68 @@ /*============================================================================*/ int ep8_cmp(const ep8_t p, const ep8_t q) { - ep8_t r, s; - int result = RLC_NE; + ep8_t r, s; + int result = RLC_NE; if (ep8_is_infty(p) && ep8_is_infty(q)) { return RLC_EQ; } - ep8_null(r); - ep8_null(s); + ep8_null(r); + ep8_null(s); - RLC_TRY { - ep8_new(r); - ep8_new(s); + RLC_TRY { + ep8_new(r); + ep8_new(s); - if ((p->coord != BASIC) && (q->coord != BASIC)) { - /* If the two points are not normalized, it is faster to compare - * x1 * z2^2 == x2 * z1^2 and y1 * z2^3 == y2 * z1^3. */ - fp8_sqr(r->z, p->z); - fp8_sqr(s->z, q->z); - fp8_mul(r->x, p->x, s->z); - fp8_mul(s->x, q->x, r->z); - fp8_mul(r->z, r->z, p->z); - fp8_mul(s->z, s->z, q->z); - fp8_mul(r->y, p->y, s->z); - fp8_mul(s->y, q->y, r->z); - } else { - ep8_norm(r, p); - ep8_norm(s, q); - } + switch (q->coord) { + case PROJC: + /* If q is in homogeneous projective coordinates, compute + * x1 * z2 and y1 * z2. */ + fp8_mul(r->x, p->x, q->z); + fp8_mul(r->y, p->y, q->z); + break; + case JACOB: + /* If q is in Jacobian projective coordinates, compute + * x2 * z1^2 and y2 * z1^3. */ + fp8_sqr(r->z, q->z); + fp8_mul(r->x, p->x, r->z); + fp8_mul(r->z, r->z, q->z); + fp8_mul(r->y, p->y, r->z); + break; + default: + ep8_copy(r, p); + break; + } - if ((fp8_cmp(r->x, s->x) == RLC_EQ) && - (fp8_cmp(r->y, s->y) == RLC_EQ)) { - result = RLC_EQ; - } - } RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } RLC_FINALLY { - ep8_free(r); - ep8_free(s); - } + switch (p->coord) { + /* Now do the same for the other point. */ + case PROJC: + fp8_mul(s->x, q->x, p->z); + fp8_mul(s->y, q->y, p->z); + break; + case JACOB: + fp8_sqr(s->z, p->z); + fp8_mul(s->x, q->x, s->z); + fp8_mul(s->z, s->z, p->z); + fp8_mul(s->y, q->y, s->z); + break; + default: + ep8_copy(s, q); + break; + } - return result; + if ((fp8_cmp(r->x, s->x) == RLC_EQ) && (fp8_cmp(r->y, s->y) == RLC_EQ)) { + result = RLC_EQ; + } + } + RLC_CATCH_ANY { + RLC_THROW(ERR_CAUGHT); + } + RLC_FINALLY { + ep8_free(r); + ep8_free(s); + } + + return result; } diff --git a/src/epx/relic_ep8_curve.c b/src/epx/relic_ep8_curve.c index c7c1dcda5..02dae23b6 100644 --- a/src/epx/relic_ep8_curve.c +++ b/src/epx/relic_ep8_curve.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of configuration of prime elliptic curves over octic - * extensions. + * Implementation of configuration of prime elliptic curves over an octic + * extension field. * * @ingroup epx */ @@ -221,6 +221,49 @@ int ep8_curve_opt_b(void) { return core_get()->ep8_opt_b; } +void ep8_curve_mul_a(fp8_t c, const fp8_t a) { + ctx_t *ctx = core_get(); + switch (ctx->ep8_opt_a) { + case RLC_ZERO: + fp8_zero(c); + break; + case RLC_ONE: + fp8_copy(c, a); + break; + case RLC_TWO: + fp8_dbl(c, a); + break; +#if FP_RDC != MONTY + case RLC_TINY: + fp8_mul_dig(c, a, ctx->ep8_a[0]); + break; +#endif + default: + fp8_mul(c, a, ctx->ep8_a); + break; + } +} + +void ep8_curve_mul_b(fp8_t c, const fp8_t a) { + ctx_t *ctx = core_get(); + switch (ctx->ep8_opt_b) { + case RLC_ZERO: + fp8_zero(c); + break; + case RLC_ONE: + fp8_copy(c, a); + break; +#if FP_RDC != MONTY + case RLC_TINY: + fp8_mul_dig(c, a, ctx->ep8_b[0]); + break; +#endif + default: + fp8_mul(c, a, ctx->ep8_b); + break; + } +} + int ep8_curve_is_twist(void) { return core_get()->ep8_is_twist; } diff --git a/src/epx/relic_ep8_dbl.c b/src/epx/relic_ep8_dbl.c index 532f15a41..76e62b41d 100644 --- a/src/epx/relic_ep8_dbl.c +++ b/src/epx/relic_ep8_dbl.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of doubling on elliptic prime curves over quartic - * extensions. + * Implementation of doubling on elliptic prime curves over an octic extension + * field. * * @ingroup epx */ @@ -44,141 +44,41 @@ * elliptic curve. * * @param[out] r - the result. - * @param[out] s - the resulting slope. + * @param[out] s - the slope. * @param[in] p - the point to double. */ TMPL_DBL_BASIC_IMP(ep8, fp8); #endif /* EP_ADD == BASIC */ -#if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) +#if EP_ADD == PROJC || !defined(STRIP) /** - * Doubles a point represented in affine coordinates on an ordinary prime + * Doubles a point represented in projective coordinates on an ordinary prime * elliptic curve. * - * @param[out] r - the result. - * @param[in] p - the point to double. + * @param r - the result. + * @param p - the point to double. */ -static void ep8_dbl_projc_imp(ep8_t r, const ep8_t p) { - fp8_t t0, t1, t2, t3, t4, t5; - - fp8_null(t0); - fp8_null(t1); - fp8_null(t2); - fp8_null(t3); - fp8_null(t4); - fp8_null(t5); - - RLC_TRY { - fp8_new(t0); - fp8_new(t1); - fp8_new(t2); - fp8_new(t3); - fp8_new(t4); - fp8_new(t5); - - if (ep_curve_opt_a() == RLC_ZERO) { - fp8_sqr(t0, p->x); - fp8_add(t2, t0, t0); - fp8_add(t0, t2, t0); - - fp8_sqr(t3, p->y); - fp8_mul(t1, t3, p->x); - fp8_add(t1, t1, t1); - fp8_add(t1, t1, t1); - fp8_sqr(r->x, t0); - fp8_add(t2, t1, t1); - fp8_sub(r->x, r->x, t2); - fp8_mul(r->z, p->z, p->y); - fp8_add(r->z, r->z, r->z); - fp8_add(t3, t3, t3); - - fp8_sqr(t3, t3); - fp8_add(t3, t3, t3); - fp8_sub(t1, t1, r->x); - fp8_mul(r->y, t0, t1); - fp8_sub(r->y, r->y, t3); - } else { - /* dbl-2007-bl formulas: 1M + 8S + 1*a + 10add + 1*8 + 2*2 + 1*3 */ - /* http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-2007-bl */ - - /* t0 = x1^2, t1 = y1^2, t2 = y1^4. */ - fp8_sqr(t0, p->x); - fp8_sqr(t1, p->y); - fp8_sqr(t2, t1); - - if (p->coord != BASIC) { - /* t3 = z1^2. */ - fp8_sqr(t3, p->z); - - if (ep_curve_opt_a() == RLC_ZERO) { - /* z3 = 2 * y1 * z1. */ - fp8_mul(r->z, p->y, p->z); - fp8_dbl(r->z, r->z); - } else { - /* z3 = (y1 + z1)^2 - y1^2 - z1^2. */ - fp8_add(r->z, p->y, p->z); - fp8_sqr(r->z, r->z); - fp8_sub(r->z, r->z, t1); - fp8_sub(r->z, r->z, t3); - } - } else { - /* z3 = 2 * y1. */ - fp8_dbl(r->z, p->y); - } - - /* t4 = S = 2*((x1 + y1^2)^2 - x1^2 - y1^4). */ - fp8_add(t4, p->x, t1); - fp8_sqr(t4, t4); - fp8_sub(t4, t4, t0); - fp8_sub(t4, t4, t2); - fp8_dbl(t4, t4); - - /* t5 = M = 3 * x1^2 + a * z1^4. */ - fp8_dbl(t5, t0); - fp8_add(t5, t5, t0); - if (p->coord != BASIC) { - fp8_sqr(t3, t3); - fp8_mul(t1, t3, ep8_curve_get_a()); - fp8_add(t5, t5, t1); - } else { - fp8_add(t5, t5, ep8_curve_get_a()); - } - - /* x3 = T = M^2 - 2 * S. */ - fp8_sqr(r->x, t5); - fp8_dbl(t1, t4); - fp8_sub(r->x, r->x, t1); - - /* y3 = M * (S - T) - 8 * y1^4. */ - fp8_dbl(t2, t2); - fp8_dbl(t2, t2); - fp8_dbl(t2, t2); - fp8_sub(t4, t4, r->x); - fp8_mul(t5, t5, t4); - fp8_sub(r->y, t5, t2); - } - - r->coord = PROJC; - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp8_free(t0); - fp8_free(t1); - fp8_free(t2); - fp8_free(t3); - fp8_free(t4); - fp8_free(t5); - } -} +TMPL_DBL_PROJC_IMP(ep8, fp8); #endif /* EP_ADD == PROJC */ +#if EP_ADD == JACOB || !defined(STRIP) + +/** + * Doubles a point represented in Jacobian coordinates on an ordinary prime + * elliptic curve. + * + * @param r - the result. + * @param p - the point to double. + */ +TMPL_DBL_JACOB_IMP(ep8, fp8); + +#endif /* EP_ADD == JACOB */ + /*============================================================================*/ - /* Public definitions */ +/* Public definitions */ /*============================================================================*/ #if EP_ADD == BASIC || !defined(STRIP) @@ -188,7 +88,6 @@ void ep8_dbl_basic(ep8_t r, const ep8_t p) { ep8_set_infty(r); return; } - ep8_dbl_basic_imp(r, NULL, p); } @@ -203,7 +102,7 @@ void ep8_dbl_slp_basic(ep8_t r, fp8_t s, const ep8_t p) { #endif -#if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) +#if EP_ADD == PROJC || !defined(STRIP) void ep8_dbl_projc(ep8_t r, const ep8_t p) { if (ep8_is_infty(p)) { @@ -215,3 +114,16 @@ void ep8_dbl_projc(ep8_t r, const ep8_t p) { } #endif + +#if EP_ADD == JACOB || !defined(STRIP) + +void ep8_dbl_jacob(ep8_t r, const ep8_t p) { + if (ep8_is_infty(p)) { + ep8_set_infty(r); + return; + } + + ep8_dbl_jacob_imp(r, p); +} + +#endif diff --git a/src/epx/relic_ep8_frb.c b/src/epx/relic_ep8_frb.c index 1cd8c4f72..4d3e4ed43 100644 --- a/src/epx/relic_ep8_frb.c +++ b/src/epx/relic_ep8_frb.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of frobenius action on prime elliptic curves over - * quartic extensions. + * Implementation of frobenius action on prime elliptic curves over an octic + * extension field. * * @ingroup epx */ diff --git a/src/epx/relic_ep8_map.c b/src/epx/relic_ep8_map.c index d309316c2..094154325 100644 --- a/src/epx/relic_ep8_map.c +++ b/src/epx/relic_ep8_map.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of hashing to a prime elliptic curve over a quadratic - * extension. + * Implementation of hashing to a prime elliptic curve over an octic extension + * field. * * @ingroup epx */ diff --git a/src/epx/relic_ep8_mul.c b/src/epx/relic_ep8_mul.c index 646df48e4..5300e8933 100644 --- a/src/epx/relic_ep8_mul.c +++ b/src/epx/relic_ep8_mul.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of point multiplication on prime elliptic curves over - * quadratic extensions. + * Implementation of point multiplication on prime elliptic curves over an + * octic extension field. * * @ingroup epx */ @@ -36,10 +36,10 @@ /* Private definitions */ /*============================================================================*/ -#if EP_MUL == LWNAF || !defined(STRIP) - #if defined(EP_ENDOM) +#if EP_MUL == LWNAF || !defined(STRIP) + static void ep8_mul_glv_imp(ep8_t r, const ep8_t p, const bn_t k) { size_t l, _l[16]; bn_t n, _k[16], u; @@ -106,14 +106,136 @@ static void ep8_mul_glv_imp(ep8_t r, const ep8_t p, const bn_t k) { bn_free(_k[i]); ep8_free(q[i]); } + } +} + +#endif /* EP_MUL == LWNAF */ + +#if EP_MUL == LWREG || !defined(STRIP) + +static void ep8_mul_reg_gls(ep8_t r, const ep8_t p, const bn_t k) { + int8_t reg[16][RLC_FP_BITS + 1], b[16], s[16], c0, n0; + ep8_t q, w, t[16][1 << (RLC_WIDTH - 2)]; + bn_t n, _k[16], u; + size_t l, len, _l[16]; + + bn_null(n); + bn_null(u); + ep8_null(q); + ep8_null(w); + + RLC_TRY { + bn_new(n); + bn_new(u); + ep8_new(q); + ep8_new(w); + for (size_t i = 0; i < 16; i++) { + bn_null(_k[i]); + bn_new(_k[i]); + for (size_t j = 0; j < (1 << (RLC_WIDTH - 2)); j++) { + ep8_null(t[i][j]); + ep8_new(t[i][j]); + } + } + + ep8_curve_get_ord(n); + fp_prime_get_par(u); + bn_mod(_k[0], k, n); + bn_rec_frb(_k, 16, _k[0], u, n, ep_curve_is_pairf() == EP_BN); + + l = 0; + /* Make some extra room for BN curves that grow subscalars by 1. */ + len = bn_bits(u) + (ep_curve_is_pairf() == EP_BN); + ep8_norm(t[0][0], p); + for (size_t i = 0; i < 16; i++) { + s[i] = bn_sign(_k[i]); + bn_abs(_k[i], _k[i]); + b[i] = bn_is_even(_k[i]); + _k[i]->dp[0] |= b[i]; + + _l[i] = RLC_FP_BITS + 1; + bn_rec_reg(reg[i], &_l[i], _k[i], len, RLC_WIDTH); + l = RLC_MAX(l, _l[i]); + + /* Apply Frobenius before flipping sign to build table. */ + if (i > 0) { + ep8_frb(t[i][0], t[i - 1][0], 1); + } + } + + for (size_t i = 0; i < 16; i++) { + ep8_neg(q, t[i][0]); + fp8_copy_sec(q->y, t[i][0]->y, s[i] == RLC_POS); + ep8_tab(t[i], q, RLC_WIDTH); + } + +#if defined(EP_MIXED) + fp8_set_dig(w->z, 1); + w->coord = BASIC; +#else + w->coord = = EP_ADD; +#endif + + ep8_set_infty(r); + for (int j = l - 1; j >= 0; j--) { + for (size_t i = 0; i < RLC_WIDTH - 1; i++) { + ep8_dbl(r, r); + } + + for (size_t i = 0; i < 16; i++) { + n0 = reg[i][j]; + c0 = (n0 >> 7); + n0 = ((n0 ^ c0) - c0) >> 1; + + for (size_t m = 0; m < (1 << (RLC_WIDTH - 2)); m++) { + fp8_copy_sec(w->x, t[i][m]->x, m == n0); + fp8_copy_sec(w->y, t[i][m]->y, m == n0); + #if !defined(EP_MIXED) + fp8_copy_sec(w->z, t[i][m]->z, m == n0); + #endif + } + + ep8_neg(q, w); + fp8_copy_sec(q->y, w->y, c0 == 0); + ep8_add(r, r, q); + } + } + for (size_t i = 0; i < 16; i++) { + /* Tables are built with points already negated, so no need here. */ + ep8_sub(q, r, t[i][0]); + fp8_copy_sec(r->x, q->x, b[i]); + fp8_copy_sec(r->y, q->y, b[i]); + fp8_copy_sec(r->z, q->z, b[i]); + } + + /* Convert r to affine coordinates. */ + ep8_norm(r, r); + } + RLC_CATCH_ANY { + RLC_THROW(ERR_CAUGHT); + } + RLC_FINALLY { + bn_free(n); + bn_free(u); + ep8_free(q); + ep8_free(w); + for (int i = 0; i < 16; i++) { + bn_free(_k[i]); + for (size_t j = 0; j < (1 << (RLC_WIDTH - 2)); j++) { + ep8_free(t[i][j]); + } + } } } +#endif /* EP_MUL == LWREG */ #endif /* EP_ENDOM */ #if defined(EP_PLAIN) || defined(EP_SUPER) +#if EP_MUL == LWNAF || !defined(STRIP) + static void ep8_mul_naf_imp(ep8_t r, const ep8_t p, const bn_t k) { int i, n; int8_t naf[RLC_FP_BITS + 1]; @@ -162,9 +284,95 @@ static void ep8_mul_naf_imp(ep8_t r, const ep8_t p, const bn_t k) { } } -#endif /* EP_PLAIN || EP_SUPER */ #endif /* EP_MUL == LWNAF */ +#if EP_MUL == LWREG || !defined(STRIP) + +static void ep8_mul_reg_imp(ep8_t r, const ep8_t p, const bn_t k) { + bn_t _k; + int8_t s, reg[1 + RLC_CEIL(RLC_FP_BITS + 1, RLC_WIDTH - 1)]; + ep8_t t[1 << (RLC_WIDTH - 2)], u, v; + size_t l, n; + + bn_null(_k); + + RLC_TRY { + bn_new(_k); + ep8_new(u); + ep8_new(v); + /* Prepare the precomputation table. */ + for (size_t i = 0; i < (1 << (RLC_WIDTH - 2)); i++) { + ep8_null(t[i]); + ep8_new(t[i]); + } + /* Compute the precomputation table. */ + ep8_tab(t, p, RLC_WIDTH); + + ep8_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, RLC_WIDTH - 1) + 1; + bn_rec_reg(reg, &l, _k, n, RLC_WIDTH); + +#if defined(EP_MIXED) + fp8_set_dig(u->z, 1); + u->coord = BASIC; +#else + u->coord = EP_ADD; +#endif + ep8_set_infty(r); + for (int i = l - 1; i >= 0; i--) { + for (size_t j = 0; j < RLC_WIDTH - 1; j++) { + ep8_dbl(r, r); + } + + n = reg[i]; + s = (n >> 7); + n = ((n ^ s) - s) >> 1; + + for (size_t j = 0; j < (1 << (RLC_WIDTH - 2)); j++) { + fp8_copy_sec(u->x, t[j]->x, j == n); + fp8_copy_sec(u->y, t[j]->y, j == n); +#if !defined(EP_MIXED) + fp_copy_sec(u->z, t[j]->z, j == n); +#endif + } + ep8_neg(v, u); + fp8_copy_sec(u->y, v->y, s != 0); + ep8_add(r, r, u); + } + /* t[0] has an unmodified copy of p. */ + ep8_sub(u, r, t[0]); + fp8_copy_sec(r->x, u->x, bn_is_even(k)); + fp8_copy_sec(r->y, u->y, bn_is_even(k)); + fp8_copy_sec(r->z, u->z, bn_is_even(k)); + /* Convert r to affine coordinates. */ + ep8_norm(r, r); + ep8_neg(u, r); + fp8_copy_sec(r->y, u->y, bn_sign(k) == RLC_NEG); + } + RLC_CATCH_ANY { + RLC_THROW(ERR_CAUGHT); + } + RLC_FINALLY { + /* Free the precomputation table. */ + for (size_t i = 0; i < (1 << (RLC_WIDTH - 2)); i++) { + ep8_free(t[i]); + } + bn_free(_k); + ep8_free(u); + ep8_free(v); + } +} + +#endif /* EP_MUL == LWREG */ +#endif /* EP_PLAIN || EP_SUPER */ + /*============================================================================*/ /* Public definitions */ /*============================================================================*/ @@ -399,6 +607,28 @@ void ep8_mul_lwnaf(ep8_t r, const ep8_t p, const bn_t k) { #endif +#if EP_MUL == LWREG || !defined(STRIP) + +void ep8_mul_lwreg(ep8_t r, const ep8_t p, const bn_t k) { + if (bn_is_zero(k) || ep8_is_infty(p)) { + ep8_set_infty(r); + return; + } + +#if defined(EP_ENDOM) + if (ep_curve_is_endom()) { + ep8_mul_reg_gls(r, p, k); + return; + } +#endif + +#if defined(EP_PLAIN) || defined(EP_SUPER) + ep8_mul_reg_imp(r, p, k); +#endif +} + +#endif + void ep8_mul_gen(ep8_t r, const bn_t k) { if (bn_is_zero(k)) { ep8_set_infty(r); diff --git a/src/epx/relic_ep8_mul_cof.c b/src/epx/relic_ep8_mul_cof.c index 16b818a53..2d43beef6 100644 --- a/src/epx/relic_ep8_mul_cof.c +++ b/src/epx/relic_ep8_mul_cof.c @@ -25,7 +25,7 @@ * @file * * Implementation of point multiplication of a prime elliptic curve over an - * octic extension by the curve cofactor. + * octic extension field by the curve cofactor. * * @ingroup epx */ diff --git a/src/epx/relic_ep8_mul_fix.c b/src/epx/relic_ep8_mul_fix.c index 94e80748e..f11327a91 100644 --- a/src/epx/relic_ep8_mul_fix.c +++ b/src/epx/relic_ep8_mul_fix.c @@ -25,7 +25,7 @@ * @file * * Implementation of fixed point multiplication on a prime elliptic curve over - * a quartic extension. + * an octic extension field. * * @ingroup epx */ diff --git a/src/epx/relic_ep8_mul_sim.c b/src/epx/relic_ep8_mul_sim.c index db08c8cbc..e6febc105 100644 --- a/src/epx/relic_ep8_mul_sim.c +++ b/src/epx/relic_ep8_mul_sim.c @@ -25,7 +25,7 @@ * @file * * Implementation of simultaneous point multiplication on a prime elliptic - * curve over a quartic extension. + * curve over an octic extension field. * * @ingroup epx */ diff --git a/src/epx/relic_ep8_neg.c b/src/epx/relic_ep8_neg.c index 3f0f3061e..accaa0670 100644 --- a/src/epx/relic_ep8_neg.c +++ b/src/epx/relic_ep8_neg.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of point negation on elliptic prime curves over quartic - * extensions. + * Implementation of point negation on elliptic prime curves over an octic + * extension field. * * @ingroup epx */ diff --git a/src/epx/relic_ep8_norm.c b/src/epx/relic_ep8_norm.c index a477299b2..a70382332 100644 --- a/src/epx/relic_ep8_norm.c +++ b/src/epx/relic_ep8_norm.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of point normalization on prime elliptic curves over octic - * extensions. + * Implementation of point normalization on prime elliptic curves over an octic + * extension field. * * @ingroup epx */ @@ -43,36 +43,45 @@ * * @param r - the result. * @param p - the point to normalize. + * @param inv - the flag to indicate if z is already inverted. */ -static void ep8_norm_imp(ep8_t r, const ep8_t p, int inverted) { +static void ep8_norm_imp(ep8_t r, const ep8_t p, int inv) { if (p->coord != BASIC) { - fp8_t t0, t1; + fp8_t t; - fp8_null(t0); - fp8_null(t1); + fp8_null(t); RLC_TRY { + fp8_new(t); - fp8_new(t0); - fp8_new(t1); - - if (inverted) { - fp8_copy(t1, p->z); + if (inv) { + fp8_copy(r->z, p->z); } else { - fp8_inv(t1, p->z); + fp8_inv(r->z, p->z); + } + + switch (p->coord) { + case PROJC: + fp8_mul(r->x, p->x, r->z); + fp8_mul(r->y, p->y, r->z); + break; + case JACOB: + fp8_sqr(t, r->z); + fp8_mul(r->x, p->x, t); + fp8_mul(t, t, r->z); + fp8_mul(r->y, p->y, t); + break; + default: + ep8_copy(r, p); + break; } - fp8_sqr(t0, t1); - fp8_mul(r->x, p->x, t0); - fp8_mul(t0, t0, t1); - fp8_mul(r->y, p->y, t0); fp8_set_dig(r->z, 1); } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); } RLC_FINALLY { - fp8_free(t0); - fp8_free(t1); + fp8_free(t); } } @@ -92,17 +101,18 @@ void ep8_norm(ep8_t r, const ep8_t p) { } if (p->coord == BASIC) { - /* If the point is represented in affine coordinates, we just copy it. */ + /* If the point is represented in affine coordinates, just copy it. */ ep8_copy(r, p); + return; } -#if EP_ADD == PROJC || !defined(STRIP) +#if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) ep8_norm_imp(r, p, 0); -#endif +#endif /* EP_ADD == PROJC */ } void ep8_norm_sim(ep8_t *r, const ep8_t *t, int n) { int i; - fp8_t *a = RLC_ALLOCA(fp8_t, n); + fp8_t* a = RLC_ALLOCA(fp8_t, n); RLC_TRY { if (a == NULL) { @@ -114,17 +124,20 @@ void ep8_norm_sim(ep8_t *r, const ep8_t *t, int n) { fp8_copy(a[i], t[i]->z); } - fp8_inv_sim(a, a, n); + fp8_inv_sim(a, (const fp8_t *)a, n); for (i = 0; i < n; i++) { fp8_copy(r[i]->x, t[i]->x); fp8_copy(r[i]->y, t[i]->y); - fp8_copy(r[i]->z, a[i]); + if (!ep8_is_infty(t[i])) { + fp8_copy(r[i]->z, a[i]); + } } - +#if EP_ADD == PROJC || EP_ADD == JACOB || !defined(STRIP) for (i = 0; i < n; i++) { ep8_norm_imp(r[i], r[i], 1); } +#endif /* EP_ADD == PROJC */ } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); diff --git a/src/epx/relic_ep8_util.c b/src/epx/relic_ep8_util.c index 3232d4870..0c44ff045 100644 --- a/src/epx/relic_ep8_util.c +++ b/src/epx/relic_ep8_util.c @@ -24,8 +24,8 @@ /** * @file * - * Implementation of comparison for points on prime elliptic curves over - * quartic extensions. + * Implementation of comparison for points on prime elliptic curves over an + * octic extensions. * * @ingroup epx */ @@ -89,13 +89,18 @@ void ep8_blind(ep8_t r, const ep8_t p) { #if EP_ADD == BASIC (void)rand; ep8_copy(r, p); -#else +#elif EP_ADD == PROJC + fp8_mul(r->x, p->x, rand); + fp8_mul(r->y, p->y, rand); + fp8_mul(r->z, p->z, rand); + r->coord = PROJC; +#elif EP_ADD == JACOB fp8_mul(r->z, p->z, rand); fp8_mul(r->y, p->y, rand); fp8_sqr(rand, rand); fp8_mul(r->x, r->x, rand); fp8_mul(r->y, r->y, rand); - r->coord = EP_ADD; + r->coord = JACOB; #endif } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); @@ -104,7 +109,7 @@ void ep8_blind(ep8_t r, const ep8_t p) { } } -void ep8_rhs(fp8_t rhs, const ep8_t p) { +void ep8_rhs(fp8_t rhs, const fp8_t x) { fp8_t t0; fp8_null(t0); @@ -112,7 +117,7 @@ void ep8_rhs(fp8_t rhs, const ep8_t p) { RLC_TRY { fp8_new(t0); - fp8_sqr(t0, p->x); /* x1^2 */ + fp8_sqr(t0, x); switch (ep8_curve_opt_a()) { case RLC_ZERO: @@ -129,7 +134,7 @@ void ep8_rhs(fp8_t rhs, const ep8_t p) { break; case RLC_TINY: fp_add_dig(t0[0][0][0], t0[0][0][0], - ep8_curve_get_a()[0][0][0][0]) + ep8_curve_get_a()[0][0][0][0]); break; #endif default: @@ -137,7 +142,7 @@ void ep8_rhs(fp8_t rhs, const ep8_t p) { break; } - fp8_mul(t0, t0, p->x); /* x1^3 + a * x */ + fp8_mul(t0, t0, x); switch (ep8_curve_opt_b()) { case RLC_ZERO: @@ -153,7 +158,6 @@ void ep8_rhs(fp8_t rhs, const ep8_t p) { fp_add_dig(t0[0][0][0], t0[0][0][0], 2); break; case RLC_TINY: - ep8_curve_get_b(t1); fp_add_dig(t0[0][0][0], t0[0][0][0], ep8_curve_get_b()[0][0][0][0]); break; @@ -182,7 +186,7 @@ int ep8_on_curve(const ep8_t p) { ep8_norm(t, p); - ep8_rhs(t->x, t); + ep8_rhs(t->x, t->x); fp8_sqr(t->y, t->y); r = (fp8_cmp(t->x, t->y) == RLC_EQ) || ep8_is_infty(p); @@ -306,8 +310,8 @@ void ep8_write_bin(uint8_t *bin, size_t len, const ep8_t a, int pack) { RLC_THROW(ERR_NO_BUFFER); } else { bin[0] = 4; - fp8_write_bin(bin + 1, 8 * RLC_FP_BYTES, t->x); - fp8_write_bin(bin + 8 * RLC_FP_BYTES + 1, 8 * RLC_FP_BYTES, t->y); + fp8_write_bin(bin + 1, 8 * RLC_FP_BYTES, t->x, 0); + fp8_write_bin(bin + 8 * RLC_FP_BYTES + 1, 8 * RLC_FP_BYTES, t->y, 0); } } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); diff --git a/src/fpx/relic_fp8_mul.c b/src/fpx/relic_fp8_mul.c index c2d0587da..878d2d7a3 100644 --- a/src/fpx/relic_fp8_mul.c +++ b/src/fpx/relic_fp8_mul.c @@ -298,3 +298,8 @@ void fp8_mul_frb(fp8_t c, const fp8_t a, int i, int j) { fp4_free(t); } } + +void fp8_mul_dig(fp8_t c, const fp8_t a, dig_t b) { + fp4_mul_dig(c[0], a[0], b); + fp4_mul_dig(c[1], a[1], b); +} diff --git a/src/fpx/relic_fpx_cyc.c b/src/fpx/relic_fpx_cyc.c index ffc1bb640..57d41efc4 100644 --- a/src/fpx/relic_fpx_cyc.c +++ b/src/fpx/relic_fpx_cyc.c @@ -368,6 +368,108 @@ void fp8_exp_cyc(fp8_t c, const fp8_t a, const bn_t b) { } } +void fp8_exp_cyc_sim(fp8_t e, const fp8_t a, const bn_t b, const fp8_t c, + const bn_t d) { + int n0, n1; + int8_t naf0[RLC_FP_BITS + 1], naf1[RLC_FP_BITS + 1], *_k, *_m; + fp8_t r, t0[1 << (RLC_WIDTH - 2)]; + fp8_t s, t1[1 << (RLC_WIDTH - 2)]; + size_t l, l0, l1; + + if (bn_is_zero(b)) { + return fp8_exp_cyc(e, c, d); + } + + if (bn_is_zero(d)) { + return fp8_exp_cyc(e, a, b); + } + + fp8_null(r); + fp8_null(s); + + RLC_TRY { + fp8_new(r); + fp8_new(s); + for (int i = 0; i < (1 << (RLC_WIDTH - 2)); i ++) { + fp8_null(t0[i]); + fp8_null(t1[i]); + fp8_new(t0[i]); + fp8_new(t1[i]); + } + +#if RLC_WIDTH > 2 + fp8_sqr(t0[0], a); + fp8_mul(t0[1], t0[0], a); + for (int i = 2; i < (1 << (RLC_WIDTH - 2)); i++) { + fp8_mul(t0[i], t0[i - 1], t0[0]); + } + + fp8_sqr(t1[0], c); + fp8_mul(t1[1], t1[0], c); + for (int i = 2; i < (1 << (RLC_WIDTH - 2)); i++) { + fp8_mul(t1[i], t1[i - 1], t1[0]); + } +#endif + fp8_copy(t0[0], a); + fp8_copy(t1[0], c); + + l0 = l1 = RLC_FP_BITS + 1; + bn_rec_naf(naf0, &l0, b, RLC_WIDTH); + bn_rec_naf(naf1, &l1, d, RLC_WIDTH); + + l = RLC_MAX(l0, l1); + if (bn_sign(b) == RLC_NEG) { + for (size_t i = 0; i < l0; i++) { + naf0[i] = -naf0[i]; + } + } + if (bn_sign(d) == RLC_NEG) { + for (size_t i = 0; i < l1; i++) { + naf1[i] = -naf1[i]; + } + } + + _k = naf0 + l - 1; + _m = naf1 + l - 1; + + fp8_set_dig(r, 1); + for (int i = l - 1; i >= 0; i--, _k--, _m--) { + fp8_sqr(r, r); + + n0 = *_k; + n1 = *_m; + + if (n0 > 0) { + fp8_mul(r, r, t0[n0 / 2]); + } + if (n0 < 0) { + fp8_inv_cyc(s, t0[-n0 / 2]); + fp8_mul(r, r, s); + } + if (n1 > 0) { + fp8_mul(r, r, t1[n1 / 2]); + } + if (n1 < 0) { + fp8_inv_cyc(s, t1[-n1 / 2]); + fp8_mul(r, r, s); + } + } + + fp8_copy(e, r); + } + RLC_CATCH_ANY { + RLC_THROW(ERR_CAUGHT); + } + RLC_FINALLY { + fp8_free(r); + fp8_free(s); + for (int i = 0; i < (1 << (RLC_WIDTH - 2)); i++) { + fp8_free(t0[i]); + fp8_free(t1[i]); + } + } +} + void fp12_conv_cyc(fp12_t c, const fp12_t a) { fp12_t t; diff --git a/src/fpx/relic_fpx_util.c b/src/fpx/relic_fpx_util.c index a006f6582..925b2e937 100644 --- a/src/fpx/relic_fpx_util.c +++ b/src/fpx/relic_fpx_util.c @@ -367,7 +367,7 @@ void fp8_read_bin(fp8_t a, const uint8_t *bin, size_t len) { fp4_read_bin(a[1], bin + 4 * RLC_FP_BYTES, 4 * RLC_FP_BYTES); } -void fp8_write_bin(uint8_t *bin, size_t len, const fp8_t a) { +void fp8_write_bin(uint8_t *bin, size_t len, const fp8_t a, int pack) { if (len != 8 * RLC_FP_BYTES) { RLC_THROW(ERR_NO_BUFFER); return; @@ -597,8 +597,8 @@ void fp16_write_bin(uint8_t *bin, size_t len, const fp16_t a, int pack) { RLC_THROW(ERR_NO_BUFFER); return; } - 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, 8 * RLC_FP_BYTES, a[0], 0); + fp8_write_bin(bin + 8 * RLC_FP_BYTES, 8 * RLC_FP_BYTES, a[1], 0); } void fp16_set_dig(fp16_t a, const dig_t b) { @@ -791,9 +791,9 @@ void fp24_write_bin(uint8_t *bin, size_t len, const fp24_t a, int pack) { 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]); + fp8_write_bin(bin, 8 * RLC_FP_BYTES, a[0], 0); + fp8_write_bin(bin + 8 * RLC_FP_BYTES, 8 * RLC_FP_BYTES, a[1], 0); + fp8_write_bin(bin + 16 * RLC_FP_BYTES, 8 * RLC_FP_BYTES, a[2], 0); } } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); @@ -882,10 +882,10 @@ void fp48_write_bin(uint8_t *bin, size_t len, const fp48_t a, int pack) { 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]); + fp8_write_bin(bin, 8 * RLC_FP_BYTES, a[0][1], 0); + fp8_write_bin(bin + 8 * RLC_FP_BYTES, 8 * RLC_FP_BYTES, a[0][2], 0); + fp8_write_bin(bin + 16 * RLC_FP_BYTES, 8 * RLC_FP_BYTES, a[1][0], 0); + fp8_write_bin(bin + 24 * RLC_FP_BYTES, 8 * RLC_FP_BYTES, a[1][2], 0); } else { if (len != 48 * RLC_FP_BYTES) { RLC_THROW(ERR_NO_BUFFER); diff --git a/src/pc/relic_pc_exp.c b/src/pc/relic_pc_exp.c index 14b148c03..d4837a0a4 100644 --- a/src/pc/relic_pc_exp.c +++ b/src/pc/relic_pc_exp.c @@ -155,8 +155,14 @@ void gt_exp_imp(gt_t c, const gt_t a, const bn_t b, size_t f) { } } - gt_get_ord(n); fp_prime_get_par(u); + if (ep_curve_is_pairf() == EP_SG18) { + /* Compute base -3*u for the recoding below. */ + bn_dbl(n, u); + bn_add(u, u, n); + bn_neg(u, u); + } + gt_get_ord(n); bn_abs(_b[0], b); bn_mod(_b[0], _b[0], n); if (bn_sign(b) == RLC_NEG) { @@ -218,7 +224,7 @@ void gt_exp_imp(gt_t c, const gt_t a, const bn_t b, size_t f) { } } - for (size_t i = 0; i < 4; i++) { + for (size_t i = 0; i < f; i++) { /* Tables are built with points already negated, so no need here. */ gt_inv(q, t[i * RLC_GT_TABLE]); gt_mul(q, c, q); @@ -365,10 +371,10 @@ void gt_exp(gt_t c, const gt_t a, const bn_t b) { return; } -#if FP_PRIME < 1536 - RLC_CAT(RLC_GT_LOWER, exp_cyc_gls)(c, a, b); -#elif FP_PRIME == 1536 +#if FP_PRIME == 1536 || FP_PRIME == 544 RLC_CAT(RLC_GT_LOWER, exp_cyc)(c, a, b); +#elif FP_PRIME < 1536 + RLC_CAT(RLC_GT_LOWER, exp_cyc_gls)(c, a, b); #else RLC_CAT(RLC_GT_LOWER, exp)(c, a, b); #endif @@ -388,6 +394,7 @@ void gt_exp_sec(gt_t c, const gt_t a, const bn_t b) { switch (ep_param_embed()) { case 1: case 2: + case 8: f = 1; break; case 12: diff --git a/src/pc/relic_pc_util.c b/src/pc/relic_pc_util.c index c6072137a..3cc683267 100644 --- a/src/pc/relic_pc_util.c +++ b/src/pc/relic_pc_util.c @@ -58,6 +58,8 @@ void gt_rand(gt_t a) { pp_exp_k16(a, a); #elif FP_PRIME == 508 || FP_PRIME == 768 || FP_PRIME == 638 && !defined(FP_QNRES) pp_exp_k18(a, a); +#elif FP_PRIME == 544 + pp_exp_k8(a, a); #else pp_exp_k12(a, a); #endif diff --git a/src/pp/relic_pp_dbl_k16.c b/src/pp/relic_pp_dbl_k16.c index 61992eebb..26803c0dd 100644 --- a/src/pp/relic_pp_dbl_k16.c +++ b/src/pp/relic_pp_dbl_k16.c @@ -113,29 +113,7 @@ void pp_dbl_k16_projc_basic(fp16_t l, ep4_t r, const ep4_t q, const ep_t p) { fp4_sqr(t0, q->x); fp4_sqr(t1, q->y); fp4_sqr(t2, q->z); - switch (ep_curve_opt_a()) { - case RLC_ZERO: - fp4_zero(t3); - break; - case RLC_ONE: - fp4_copy(t3, t2); - break; -#if FP_RDC != MONTY - case RLC_TINY: - fp_mul_dig(t3[0][0], t2[0][0], ep_curve_get_a()[0]); - fp_mul_dig(t3[0][1], t2[0][1], ep_curve_get_a()[0]); - fp_mul_dig(t3[1][0], t2[1][0], ep_curve_get_a()[0]); - fp_mul_dig(t3[1][1], t2[1][1], ep_curve_get_a()[0]); - break; -#endif - default: - fp_mul(t3[0][0], t2[0][0], ep_curve_get_a()); - fp_mul(t3[0][1], t2[0][1], ep_curve_get_a()); - fp_mul(t3[1][0], t2[1][0], ep_curve_get_a()); - fp_mul(t3[1][1], t2[1][1], ep_curve_get_a()); - break; - } - fp4_mul_art(t3, t3); + ep4_curve_mul_a(t3, t2); /* x3 = (A - D)^2, l11 = (A - D + x1)^2 - x3 - A. */ fp4_sub(t5, t0, t3); @@ -236,29 +214,7 @@ void pp_dbl_k16_projc_lazyr(fp16_t l, ep4_t r, const ep4_t q, const ep_t p) { fp4_sqr(t0, q->x); fp4_sqr(t1, q->y); fp4_sqr(t2, q->z); - switch (ep_curve_opt_a()) { - case RLC_ZERO: - fp4_zero(t3); - break; - case RLC_ONE: - fp4_copy(t3, t2); - break; -#if FP_RDC != MONTY - case RLC_TINY: - fp_mul_dig(t3[0][0], t2[0][0], ep_curve_get_a()[0]); - fp_mul_dig(t3[0][1], t2[0][1], ep_curve_get_a()[0]); - fp_mul_dig(t3[1][0], t2[1][0], ep_curve_get_a()[0]); - fp_mul_dig(t3[1][1], t2[1][1], ep_curve_get_a()[0]); - break; -#endif - default: - fp_mul(t3[0][0], t2[0][0], ep_curve_get_a()); - fp_mul(t3[0][1], t2[0][1], ep_curve_get_a()); - fp_mul(t3[1][0], t2[1][0], ep_curve_get_a()); - fp_mul(t3[1][1], t2[1][1], ep_curve_get_a()); - break; - } - fp4_mul_art(t3, t3); + ep4_curve_mul_a(t3, t2); /* x3 = (A - D)^2, l11 = (A - D + x1)^2 - x3 - A. */ fp4_sub(t5, t0, t3); diff --git a/src/pp/relic_pp_dbl_k8.c b/src/pp/relic_pp_dbl_k8.c index 1153f3d04..72db1ce90 100644 --- a/src/pp/relic_pp_dbl_k8.c +++ b/src/pp/relic_pp_dbl_k8.c @@ -180,25 +180,7 @@ void pp_dbl_k8_projc_basic(fp8_t l, ep2_t r, const ep2_t q, const ep_t p) { fp2_sqr(t0, q->x); fp2_sqr(t1, q->y); fp2_sqr(t2, q->z); - switch (ep_curve_opt_a()) { - case RLC_ZERO: - fp2_zero(t3); - break; - case RLC_ONE: - fp2_copy(t3, t2); - break; -#if FP_RDC != MONTY - case RLC_TINY: - fp_mul_dig(t3[0], t2[0], ep_curve_get_a()[0]); - fp_mul_dig(t3[1], t2[1], ep_curve_get_a()[0]); - break; -#endif - default: - fp_mul(t3[0], t2[0], ep_curve_get_a()); - fp_mul(t3[1], t2[1], ep_curve_get_a()); - break; - } - fp2_mul_art(t3, t3); + ep2_curve_mul_a(t3, t2); /* x3 = (A - D)^2, l11 = (A - D + x1)^2 - x3 - A. */ fp2_sub(t5, t0, t3); @@ -293,28 +275,7 @@ void pp_dbl_k8_projc_lazyr(fp8_t l, ep2_t r, const ep2_t q, const ep_t p) { fp2_sqr(t0, q->x); fp2_sqr(t1, q->y); fp2_sqr(t2, q->z); - switch (ep_curve_opt_a()) { - case RLC_ZERO: - fp2_zero(t3); - break; - case RLC_ONE: - fp2_copy(t3, t2); - break; - case RLC_TWO: - fp2_dbl(t3, t2); - break; -#if FP_RDC != MONTY - case RLC_TINY: - fp_mul_dig(t3[0], t2[0], ep_curve_get_a()[0]); - fp_mul_dig(t3[1], t2[1], ep_curve_get_a()[0]); - break; -#endif - default: - fp_mul(t3[0], t2[0], ep_curve_get_a()); - fp_mul(t3[1], t2[1], ep_curve_get_a()); - break; - } - fp2_mul_art(t3, t3); + ep2_curve_mul_a(t3, t2); /* x3 = (A - D)^2, l11 = (A - D + x1)^2 - x3 - A. */ fp2_sub(t5, t0, t3); diff --git a/src/pp/relic_pp_map_k12.c b/src/pp/relic_pp_map_k12.c index 2446a083f..f82af9438 100644 --- a/src/pp/relic_pp_map_k12.c +++ b/src/pp/relic_pp_map_k12.c @@ -195,9 +195,8 @@ static void pp_mil_lit_k12(fp12_t r, ep_t *t, ep_t *p, ep2_t *q, int m, bn_t a) * @param[out] t - the resulting point. * @param[in] q - the first point of the pairing, in G_2. * @param[in] p - the second point of the pairing, in G_1. - * @param[in] a - the loop parameter. */ -static void pp_fin_k12_oatep(fp12_t r, ep2_t t, ep2_t q, ep_t p) { +static void pp_fin_k12_oatep(fp12_t r, ep2_t t, const ep2_t q, const ep_t p) { ep2_t q1, q2; fp12_t tmp; diff --git a/src/pp/relic_pp_map_k16.c b/src/pp/relic_pp_map_k16.c index 837076802..6ab261f85 100644 --- a/src/pp/relic_pp_map_k16.c +++ b/src/pp/relic_pp_map_k16.c @@ -198,29 +198,37 @@ static void pp_mil_lit_k16(fp16_t r, ep_t *t, ep_t *p, ep4_t *q, int m, bn_t a) * @param[in] p - the second point of the pairing, in G_1. * @param[in] a - the loop parameter. */ -static void pp_fin_k16_oatep(fp16_t r, ep4_t t, ep4_t q, ep_t p) { +static void pp_fin_k16_oatep(fp16_t r, ep4_t t, const ep4_t q, const ep_t p) { ep4_t q1, q2; fp16_t tmp; + ep_t _p; fp16_null(tmp); ep4_null(q1); ep4_null(q2); + ep_null(_p); RLC_TRY { ep4_new(q1); ep4_new(q2); fp16_new(tmp); - fp16_zero(tmp); + ep_new(_p); -#if EP_ADD == PROJC || EP_ADD == JACOB - fp_neg(p->x, p->x); +#if EP_ADD == BASIC + ep_neg(_p, p); +#else + fp_neg(_p->x, p->x); + fp_copy(_p->y, p->y); #endif + + fp16_zero(tmp); ep4_frb(q1, q, 1); - pp_add_k16(tmp, t, q1, p); + pp_add_k16(tmp, t, q1, _p); fp16_frb(tmp, tmp, 3); fp16_mul_dxs(r, r, tmp); - pp_dbl_k16(tmp, q2, q, p); + fp16_zero(tmp); + pp_dbl_k16(tmp, q2, q, _p); fp16_mul_dxs(r, r, tmp); } RLC_CATCH_ANY { RLC_THROW(ERR_CAUGHT); @@ -228,6 +236,7 @@ static void pp_fin_k16_oatep(fp16_t r, ep4_t t, ep4_t q, ep_t p) { fp16_free(tmp); ep4_free(q1); ep4_free(q2); + ep_free(_p); } } diff --git a/src/pp/relic_pp_map_k18.c b/src/pp/relic_pp_map_k18.c index 99f733ed9..083775443 100644 --- a/src/pp/relic_pp_map_k18.c +++ b/src/pp/relic_pp_map_k18.c @@ -196,9 +196,10 @@ static void pp_mil_lit_k18(fp18_t r, ep_t *t, ep_t *p, ep3_t *q, int m, bn_t a) * @param[out] t - the resulting point. * @param[in] q - the first point of the pairing, in G_2. * @param[in] p - the second point of the pairing, in G_1. - * @param[in] a - the loop parameter. + * @param[in] f - the flag to correct for the curve family. */ -static void pp_fin_k18_oatep(fp18_t r, ep3_t t, ep3_t q, ep_t p, int f) { +static void pp_fin_k18_oatep(fp18_t r, ep3_t t, const ep3_t q, const ep_t p, + int f) { fp18_t u, v; ep3_t _q; ep_t _p; diff --git a/src/pp/relic_pp_map_k54.c b/src/pp/relic_pp_map_k54.c index 5860fa94a..17b2b0181 100644 --- a/src/pp/relic_pp_map_k54.c +++ b/src/pp/relic_pp_map_k54.c @@ -181,6 +181,7 @@ void pp_map_k54(fp54_t r, const ep_t p, const fp9_t qx, const fp9_t qy) { if (bn_sign(a) == RLC_NEG) { fp54_inv_cyc(r, r); } + fp18_print(r[0]); pp_exp_k54(r, r); break; } diff --git a/src/pp/relic_pp_map_k8.c b/src/pp/relic_pp_map_k8.c index 0d5e2ee8f..bf2b9682a 100644 --- a/src/pp/relic_pp_map_k8.c +++ b/src/pp/relic_pp_map_k8.c @@ -163,3 +163,61 @@ void pp_map_oatep_k8(fp8_t r, const ep_t p, const ep2_t q) { bn_free(a); } } + +void pp_map_sim_oatep_k8(fp8_t r, const ep_t *p, const ep2_t *q, int m) { + ep_t *_p = RLC_ALLOCA(ep_t, m); + ep2_t *t = RLC_ALLOCA(ep2_t, m), *_q = RLC_ALLOCA(ep2_t, m); + bn_t a; + int i, j; + + RLC_TRY { + bn_null(a); + bn_new(a); + if (_p == NULL || _q == NULL || t == NULL) { + RLC_THROW(ERR_NO_MEMORY); + } + for (i = 0; i < m; i++) { + ep_null(_p[i]); + ep2_null(_q[i]); + ep2_null(t[i]); + ep_new(_p[i]); + ep2_new(_q[i]); + ep2_new(t[i]); + } + + j = 0; + for (i = 0; i < m; i++) { + if (!ep_is_infty(p[i]) && !ep2_is_infty(q[i])) { + ep_norm(_p[j], p[i]); + ep2_norm(_q[j++], q[i]); + } + } + + fp_prime_get_par(a); + fp8_set_dig(r, 1); + + if (j > 0) { + /* r = f_{|a|,Q}(P). */ + pp_mil_k8(r, t, _q, _p, j, a); + if (bn_sign(a) == RLC_NEG) { + fp8_inv_cyc(r, r); + ep2_neg(t[i], t[i]); + } + pp_exp_k8(r, r); + } + } + RLC_CATCH_ANY { + RLC_THROW(ERR_CAUGHT); + } + RLC_FINALLY { + bn_free(a); + for (i = 0; i < m; i++) { + ep_free(_p[i]); + ep4_free(_q[i]); + ep4_free(t[i]); + } + RLC_FREE(_p); + RLC_FREE(_q); + RLC_FREE(t); + } +} diff --git a/src/tmpl/relic_ep_map_tmpl.h b/src/tmpl/relic_ep_map_tmpl.h index ef33391fc..7e31f88c7 100644 --- a/src/tmpl/relic_ep_map_tmpl.h +++ b/src/tmpl/relic_ep_map_tmpl.h @@ -165,7 +165,7 @@ * Simplified SWU mapping from Section 4 of * "Fast and simple constant-time hashing to the BLS12-381 Elliptic Curve" */ -#define TMPL_MAP_SSWU(CUR, PFX, PTR_TY, copy_sec) \ +#define TMPL_MAP_SSWU(CUR, PFX, PTR_TY) \ static void CUR##_map_sswu(CUR##_t p, const PFX##_t t) { \ PFX##_t t0, t1, t2, t3; \ ctx_t *ctx = core_get(); \ @@ -197,11 +197,11 @@ const int e1 = PFX##_is_zero(t2); \ PFX##_neg(t3, u); /* t3 = -u */ \ /* exception: -u instead of u^2t^4 + ut^2 */ \ - copy_sec(t2, t3, e1); \ + PFX##_copy_sec(t2, t3, e1); \ /* t2 = -1/u or 1/(u^2 * t^4 + u*t^2) */ \ PFX##_inv(t2, t2); \ PFX##_add_dig(t3, t2, 1); /* t3 = 1 + t2 */ \ - copy_sec(t2, t3, e1 == 0); /* only add 1 if t2 != -1/u */ \ + PFX##_copy_sec(t2, t3, e1 == 0); /* add 1 if t2 != -1/u */ \ } \ /* e1 goes out of scope */ \ /* compute x1, g(x1) */ \ @@ -219,8 +219,8 @@ { \ /* try x2, g(x2) */ \ const int e1 = PFX##_is_sqr(p->y); \ - copy_sec(p->x, t2, e1 == 0); \ - copy_sec(p->y, t3, e1 == 0); \ + PFX##_copy_sec(p->x, t2, e1 == 0); \ + PFX##_copy_sec(p->y, t3, e1 == 0); \ } \ if (!PFX##_srt(p->y, p->y)) { \ RLC_THROW(ERR_NO_VALID); \ @@ -241,7 +241,7 @@ * Shallue--van de Woestijne map, based on the definition from * draft-irtf-cfrg-hash-to-curve-06, Section 6.6.1 */ -#define TMPL_MAP_SVDW(CUR, PFX, PTR_TY, copy_sec) \ +#define TMPL_MAP_SVDW(CUR, PFX, PTR_TY) \ static void CUR##_map_svdw(CUR##_t p, const PFX##_t t) { \ PFX##_t t1, t2, t3, t4; \ ctx_t *ctx = core_get(); \ @@ -274,10 +274,10 @@ { \ /* compute inv0(t3), i.e., 0 if t3 == 0, 1/t3 otherwise */ \ const int e0 = PFX##_is_zero(t3); \ - copy_sec(t3, gU, e0); /* g(u) is nonzero */ \ + PFX##_copy_sec(t3, gU, e0); /* g(u) is nonzero */ \ PFX##_inv(t3, t3); \ PFX##_zero(t4); \ - copy_sec(t3, t4, e0); \ + PFX##_copy_sec(t3, t4, e0); \ } \ /* e0 goes out of scope */ \ PFX##_mul(t4, t, t1); \ @@ -286,14 +286,14 @@ \ /* compute x1 and g(x1) */ \ PFX##_sub(p->x, mUover2, t4); \ - CUR##_rhs(p->y, p); \ + CUR##_rhs(p->y, p->x); \ { \ const int e0 = PFX##_is_sqr(p->y); \ /* compute x2 and g(x2) */ \ PFX##_add(t4, mUover2, t4); \ - copy_sec(p->x, t4, e0 == 0); \ - CUR##_rhs(t1, p); \ - copy_sec(p->y, t1, e0 == 0); \ + PFX##_copy_sec(p->x, t4, e0 == 0); \ + CUR##_rhs(t1, p->x); \ + PFX##_copy_sec(p->y, t1, e0 == 0); \ } \ { \ const int e1 = PFX##_is_sqr(p->y); \ @@ -303,9 +303,9 @@ PFX##_sqr(t1, t1); \ PFX##_mul(t1, t1, c4); \ PFX##_add(t1, t1, u); \ - copy_sec(p->x, t1, e1 == 0); \ - CUR##_rhs(t2, p); \ - copy_sec(p->y, t2, e1 == 0); \ + PFX##_copy_sec(p->x, t1, e1 == 0); \ + CUR##_rhs(t2, p->x); \ + PFX##_copy_sec(p->y, t2, e1 == 0); \ } \ if (!PFX##_srt(p->y, p->y)) { \ RLC_THROW(ERR_NO_VALID); \ diff --git a/test/test_ep.c b/test/test_ep.c index 5c60e2560..5484a4ede 100644 --- a/test/test_ep.c +++ b/test/test_ep.c @@ -1394,15 +1394,17 @@ static int hashing(void) { #endif #if EP_MAP == SWIFT || !defined(STRIP) - if (ep_curve_opt_a() == RLC_ZERO || ep_curve_opt_b() == RLC_ZERO) { - TEST_CASE("swift point hashing is correct") { - rand_bytes(msg, sizeof(msg)); - ep_map_swift(a, msg, sizeof(msg)); - TEST_ASSERT(ep_on_curve(a) && ep_is_infty(a) == 0, end); - ep_mul(a, a, n); - TEST_ASSERT(ep_on_curve(a) && ep_is_infty(a) == 1, end); + if (!ep_curve_is_super()) { + if (ep_curve_opt_a() == RLC_ZERO || ep_curve_opt_b() == RLC_ZERO) { + TEST_CASE("swift point hashing is correct") { + rand_bytes(msg, sizeof(msg)); + ep_map_swift(a, msg, sizeof(msg)); + TEST_ASSERT(ep_on_curve(a) && ep_is_infty(a) == 0, end); + ep_mul(a, a, n); + TEST_ASSERT(ep_on_curve(a) && ep_is_infty(a) == 1, end); + } + TEST_END; } - TEST_END; } #endif } diff --git a/test/test_epx.c b/test/test_epx.c index 612e484ad..5dda4c52f 100644 --- a/test/test_epx.c +++ b/test/test_epx.c @@ -435,6 +435,26 @@ static int doubling2(void) { TEST_ASSERT(ep2_cmp(b, c) == RLC_EQ, end); } TEST_END; #endif + +#if EP_ADD == JACOB || !defined(STRIP) + TEST_CASE("point doubling in jacobian coordinates is correct") { + ep2_rand(a); + /* a in projective coordinates. */ + ep2_dbl_jacob(a, a); + ep2_dbl_jacob(b, a); + ep2_norm(a, a); + ep2_dbl(c, a); + TEST_ASSERT(ep2_cmp(b, c) == RLC_EQ, end); + } TEST_END; + + TEST_CASE("point doubling in mixed coordinates (z1 = 1) is correct") { + ep2_rand(a); + ep2_dbl_jacob(b, a); + ep2_norm(b, b); + ep2_dbl(c, a); + TEST_ASSERT(ep2_cmp(b, c) == RLC_EQ, end); + } TEST_END; +#endif } RLC_CATCH_ANY { RLC_ERROR(end); @@ -1458,9 +1478,9 @@ static int addition3(void) { ep3_rand(a); ep3_set_infty(d); ep3_add(e, a, d); - TEST_ASSERT(ep3_cmp(e, a) == RLC_EQ, end); + TEST_ASSERT(ep3_cmp(a, e) == RLC_EQ, end); ep3_add(e, d, a); - TEST_ASSERT(ep3_cmp(e, a) == RLC_EQ, end); + TEST_ASSERT(ep3_cmp(a, e) == RLC_EQ, end); } TEST_END; TEST_CASE("point addition has inverse") { @@ -1476,7 +1496,7 @@ static int addition3(void) { ep3_rand(b); ep3_add(d, a, b); ep3_add_basic(e, a, b); - TEST_ASSERT(ep3_cmp(e, d) == RLC_EQ, end); + TEST_ASSERT(ep3_cmp(d, e) == RLC_EQ, end); } TEST_END; #endif @@ -1520,6 +1540,46 @@ static int addition3(void) { } TEST_END; #endif +#if EP_ADD == JACOB || !defined(STRIP) +#if !defined(EP_MIXED) || !defined(STRIP) + TEST_CASE("point addition in jacobian coordinates is correct") { + ep3_rand(a); + ep3_rand(b); + ep3_rand(c); + ep3_add_jacob(a, a, b); + ep3_add_jacob(b, b, c); + /* a and b in projective coordinates. */ + ep3_add_jacob(d, a, b); + ep3_norm(a, a); + ep3_norm(b, b); + ep3_add(e, a, b); + TEST_ASSERT(ep3_cmp(d, e) == RLC_EQ, end); + } TEST_END; +#endif + + TEST_CASE("point addition in mixed coordinates (z2 = 1) is correct") { + ep3_rand(a); + ep3_rand(b); + /* a in projective, b in affine coordinates. */ + ep3_add_jacob(a, a, b); + ep3_add_jacob(d, a, b); + /* a in affine coordinates. */ + ep3_norm(a, a); + ep3_add(e, a, b); + TEST_ASSERT(ep3_cmp(d, e) == RLC_EQ, end); + } TEST_END; + + TEST_CASE("point addition in mixed coordinates (z1,z2 = 1) is correct") { + ep3_rand(a); + ep3_rand(b); + ep3_norm(a, a); + ep3_norm(b, b); + /* a and b in affine coordinates. */ + ep3_add(d, a, b); + ep3_add_jacob(e, a, b); + TEST_ASSERT(ep3_cmp(d, e) == RLC_EQ, end); + } TEST_END; +#endif } RLC_CATCH_ANY { RLC_ERROR(end); @@ -1634,6 +1694,26 @@ static int doubling3(void) { TEST_ASSERT(ep3_cmp(b, c) == RLC_EQ, end); } TEST_END; #endif + +#if EP_ADD == JACOB || !defined(STRIP) + TEST_CASE("point doubling in jacobian coordinates is correct") { + ep3_rand(a); + /* a in projective coordinates. */ + ep3_dbl_jacob(a, a); + ep3_dbl_jacob(b, a); + ep3_norm(a, a); + ep3_dbl(c, a); + TEST_ASSERT(ep3_cmp(b, c) == RLC_EQ, end); + } TEST_END; + + TEST_CASE("point doubling in mixed coordinates (z1 = 1) is correct") { + ep3_rand(a); + ep3_dbl_jacob(b, a); + ep3_norm(b, b); + ep3_dbl(c, a); + TEST_ASSERT(ep3_cmp(b, c) == RLC_EQ, end); + } TEST_END; +#endif } RLC_CATCH_ANY { RLC_ERROR(end); @@ -1786,6 +1866,34 @@ static int multiplication3(void) { TEST_END; #endif +#if EP_MUL == LWREG || !defined(STRIP) + TEST_CASE("left-to-right regular point multiplication is correct") { + bn_zero(k); + ep3_mul_lwreg(r, p, k); + TEST_ASSERT(ep3_is_infty(r), end); + bn_set_dig(k, 1); + ep3_mul_lwreg(r, p, k); + TEST_ASSERT(ep3_cmp(p, r) == RLC_EQ, end); + ep3_rand(p); + ep3_mul_lwreg(r, p, n); + TEST_ASSERT(ep3_is_infty(r), end); + bn_rand_mod(k, n); + ep3_mul(q, p, k); + ep3_mul_lwreg(r, p, k); + TEST_ASSERT(ep3_cmp(q, r) == RLC_EQ, end); + bn_neg(k, k); + ep3_mul_lwreg(r, p, k); + ep3_neg(r, r); + TEST_ASSERT(ep3_cmp(q, r) == RLC_EQ, end); + bn_rand_mod(k, n); + ep3_mul_lwreg(q, p, k); + bn_add(k, k, n); + ep3_mul_lwreg(r, p, k); + TEST_ASSERT(ep3_cmp(q, r) == RLC_EQ, end); + } + TEST_END; +#endif + TEST_CASE("multiplication by digit is correct") { ep3_mul_dig(r, p, 0); TEST_ASSERT(ep3_is_infty(r), end); @@ -2487,9 +2595,9 @@ static int addition4(void) { ep4_rand(a); ep4_set_infty(d); ep4_add(e, a, d); - TEST_ASSERT(ep4_cmp(e, a) == RLC_EQ, end); + TEST_ASSERT(ep4_cmp(a, e) == RLC_EQ, end); ep4_add(e, d, a); - TEST_ASSERT(ep4_cmp(e, a) == RLC_EQ, end); + TEST_ASSERT(ep4_cmp(a, e) == RLC_EQ, end); } TEST_END; TEST_CASE("point addition has inverse") { @@ -2505,7 +2613,7 @@ static int addition4(void) { ep4_rand(b); ep4_add(d, a, b); ep4_add_basic(e, a, b); - TEST_ASSERT(ep4_cmp(e, d) == RLC_EQ, end); + TEST_ASSERT(ep4_cmp(d, e) == RLC_EQ, end); } TEST_END; #endif @@ -2549,6 +2657,46 @@ static int addition4(void) { } TEST_END; #endif +#if EP_ADD == JACOB || !defined(STRIP) +#if !defined(EP_MIXED) || !defined(STRIP) + TEST_CASE("point addition in jacobian coordinates is correct") { + ep4_rand(a); + ep4_rand(b); + ep4_rand(c); + ep4_add_jacob(a, a, b); + ep4_add_jacob(b, b, c); + /* a and b in projective coordinates. */ + ep4_add_jacob(d, a, b); + ep4_norm(a, a); + ep4_norm(b, b); + ep4_add(e, a, b); + TEST_ASSERT(ep4_cmp(d, e) == RLC_EQ, end); + } TEST_END; +#endif + + TEST_CASE("point addition in mixed coordinates (z2 = 1) is correct") { + ep4_rand(a); + ep4_rand(b); + /* a in projective, b in affine coordinates. */ + ep4_add_jacob(a, a, b); + ep4_add_jacob(d, a, b); + /* a in affine coordinates. */ + ep4_norm(a, a); + ep4_add(e, a, b); + TEST_ASSERT(ep4_cmp(d, e) == RLC_EQ, end); + } TEST_END; + + TEST_CASE("point addition in mixed coordinates (z1,z2 = 1) is correct") { + ep4_rand(a); + ep4_rand(b); + ep4_norm(a, a); + ep4_norm(b, b); + /* a and b in affine coordinates. */ + ep4_add(d, a, b); + ep4_add_jacob(e, a, b); + TEST_ASSERT(ep4_cmp(d, e) == RLC_EQ, end); + } TEST_END; +#endif } RLC_CATCH_ANY { RLC_ERROR(end); @@ -2663,6 +2811,26 @@ static int doubling4(void) { TEST_ASSERT(ep4_cmp(b, c) == RLC_EQ, end); } TEST_END; #endif + +#if EP_ADD == JACOB || !defined(STRIP) + TEST_CASE("point doubling in jacobian coordinates is correct") { + ep4_rand(a); + /* a in projective coordinates. */ + ep4_dbl_jacob(a, a); + ep4_dbl_jacob(b, a); + ep4_norm(a, a); + ep4_dbl(c, a); + TEST_ASSERT(ep4_cmp(b, c) == RLC_EQ, end); + } TEST_END; + + TEST_CASE("point doubling in mixed coordinates (z1 = 1) is correct") { + ep4_rand(a); + ep4_dbl_jacob(b, a); + ep4_norm(b, b); + ep4_dbl(c, a); + TEST_ASSERT(ep4_cmp(b, c) == RLC_EQ, end); + } TEST_END; +#endif } RLC_CATCH_ANY { RLC_ERROR(end); @@ -2815,6 +2983,34 @@ static int multiplication4(void) { TEST_END; #endif +#if EP_MUL == LWREG || !defined(STRIP) + TEST_CASE("left-to-right regular point multiplication is correct") { + bn_zero(k); + ep4_mul_lwreg(r, p, k); + TEST_ASSERT(ep4_is_infty(r), end); + bn_set_dig(k, 1); + ep4_mul_lwreg(r, p, k); + TEST_ASSERT(ep4_cmp(p, r) == RLC_EQ, end); + ep4_rand(p); + ep4_mul_lwreg(r, p, n); + TEST_ASSERT(ep4_is_infty(r), end); + bn_rand_mod(k, n); + ep4_mul(q, p, k); + ep4_mul_lwreg(r, p, k); + TEST_ASSERT(ep4_cmp(q, r) == RLC_EQ, end); + bn_neg(k, k); + ep4_mul_lwreg(r, p, k); + ep4_neg(r, r); + TEST_ASSERT(ep4_cmp(q, r) == RLC_EQ, end); + bn_rand_mod(k, n); + ep4_mul_lwreg(q, p, k); + bn_add(k, k, n); + ep4_mul_lwreg(r, p, k); + TEST_ASSERT(ep4_cmp(q, r) == RLC_EQ, end); + } + TEST_END; +#endif + TEST_CASE("multiplication by digit is correct") { ep4_mul_dig(r, p, 0); TEST_ASSERT(ep4_is_infty(r), end); @@ -3516,9 +3712,9 @@ static int addition8(void) { ep8_rand(a); ep8_set_infty(d); ep8_add(e, a, d); - TEST_ASSERT(ep8_cmp(e, a) == RLC_EQ, end); + TEST_ASSERT(ep8_cmp(a, e) == RLC_EQ, end); ep8_add(e, d, a); - TEST_ASSERT(ep8_cmp(e, a) == RLC_EQ, end); + TEST_ASSERT(ep8_cmp(a, e) == RLC_EQ, end); } TEST_END; TEST_CASE("point addition has inverse") { @@ -3534,7 +3730,7 @@ static int addition8(void) { ep8_rand(b); ep8_add(d, a, b); ep8_add_basic(e, a, b); - TEST_ASSERT(ep8_cmp(e, d) == RLC_EQ, end); + TEST_ASSERT(ep8_cmp(d, e) == RLC_EQ, end); } TEST_END; #endif @@ -3578,6 +3774,46 @@ static int addition8(void) { } TEST_END; #endif +#if EP_ADD == JACOB || !defined(STRIP) +#if !defined(EP_MIXED) || !defined(STRIP) + TEST_CASE("point addition in jacobian coordinates is correct") { + ep8_rand(a); + ep8_rand(b); + ep8_rand(c); + ep8_add_jacob(a, a, b); + ep8_add_jacob(b, b, c); + /* a and b in projective coordinates. */ + ep8_add_jacob(d, a, b); + ep8_norm(a, a); + ep8_norm(b, b); + ep8_add(e, a, b); + TEST_ASSERT(ep8_cmp(d, e) == RLC_EQ, end); + } TEST_END; +#endif + + TEST_CASE("point addition in mixed coordinates (z2 = 1) is correct") { + ep8_rand(a); + ep8_rand(b); + /* a in projective, b in affine coordinates. */ + ep8_add_jacob(a, a, b); + ep8_add_jacob(d, a, b); + /* a in affine coordinates. */ + ep8_norm(a, a); + ep8_add(e, a, b); + TEST_ASSERT(ep8_cmp(d, e) == RLC_EQ, end); + } TEST_END; + + TEST_CASE("point addition in mixed coordinates (z1,z2 = 1) is correct") { + ep8_rand(a); + ep8_rand(b); + ep8_norm(a, a); + ep8_norm(b, b); + /* a and b in affine coordinates. */ + ep8_add(d, a, b); + ep8_add_jacob(e, a, b); + TEST_ASSERT(ep8_cmp(d, e) == RLC_EQ, end); + } TEST_END; +#endif } RLC_CATCH_ANY { RLC_ERROR(end); @@ -3692,6 +3928,26 @@ static int doubling8(void) { TEST_ASSERT(ep8_cmp(b, c) == RLC_EQ, end); } TEST_END; #endif + +#if EP_ADD == JACOB || !defined(STRIP) + TEST_CASE("point doubling in jacobian coordinates is correct") { + ep8_rand(a); + /* a in projective coordinates. */ + ep8_dbl_jacob(a, a); + ep8_dbl_jacob(b, a); + ep8_norm(a, a); + ep8_dbl(c, a); + TEST_ASSERT(ep8_cmp(b, c) == RLC_EQ, end); + } TEST_END; + + TEST_CASE("point doubling in mixed coordinates (z1 = 1) is correct") { + ep8_rand(a); + ep8_dbl_jacob(b, a); + ep8_norm(b, b); + ep8_dbl(c, a); + TEST_ASSERT(ep8_cmp(b, c) == RLC_EQ, end); + } TEST_END; +#endif } RLC_CATCH_ANY { RLC_ERROR(end); @@ -3844,6 +4100,34 @@ static int multiplication8(void) { TEST_END; #endif +#if EP_MUL == LWREG || !defined(STRIP) + TEST_CASE("left-to-right regular point multiplication is correct") { + bn_zero(k); + ep8_mul_lwreg(r, p, k); + TEST_ASSERT(ep8_is_infty(r), end); + bn_set_dig(k, 1); + ep8_mul_lwreg(r, p, k); + TEST_ASSERT(ep8_cmp(p, r) == RLC_EQ, end); + ep8_rand(p); + ep8_mul_lwreg(r, p, n); + TEST_ASSERT(ep8_is_infty(r), end); + bn_rand_mod(k, n); + ep8_mul(q, p, k); + ep8_mul_lwreg(r, p, k); + TEST_ASSERT(ep8_cmp(q, r) == RLC_EQ, end); + bn_neg(k, k); + ep8_mul_lwreg(r, p, k); + ep8_neg(r, r); + TEST_ASSERT(ep8_cmp(q, r) == RLC_EQ, end); + bn_rand_mod(k, n); + ep8_mul_lwreg(q, p, k); + bn_add(k, k, n); + ep8_mul_lwreg(r, p, k); + TEST_ASSERT(ep8_cmp(q, r) == RLC_EQ, end); + } + TEST_END; +#endif + TEST_CASE("multiplication by digit is correct") { ep8_mul_dig(r, p, 0); TEST_ASSERT(ep8_is_infty(r), end); diff --git a/test/test_fpx.c b/test/test_fpx.c index aa922c2c4..4a242fe17 100644 --- a/test/test_fpx.c +++ b/test/test_fpx.c @@ -2953,7 +2953,7 @@ static int util8(void) { TEST_CASE("reading and writing a finite field element are consistent") { fp8_rand(a); - fp8_write_bin(bin, sizeof(bin), a); + fp8_write_bin(bin, sizeof(bin), a, 0); fp8_read_bin(b, bin, sizeof(bin)); TEST_ASSERT(fp8_cmp(a, b) == RLC_EQ, end); } @@ -8615,7 +8615,9 @@ static int inversion54(void) { fp54_conv_cyc(a, a); fp54_inv(b, a); fp54_inv_cyc(c, a); - TEST_ASSERT(fp54_cmp(b, c) == RLC_EQ, end); + fp18_print(b[0]); + fp18_print(c[0]); + TEST_ASSERT(fp18_cmp(b[0], c[0]) == RLC_EQ, end); } TEST_END; } RLC_CATCH_ANY { diff --git a/tools/run-pairings.sh b/tools/run-pairings.sh index 0f091790f..39a659e24 100755 --- a/tools/run-pairings.sh +++ b/tools/run-pairings.sh @@ -1,17 +1,33 @@ set -e for script in preset/x64-pbc-*; do - file=${script##*/} - file=${file%.sh} - echo target-$file - mkdir -p target-$file - cd target-$file - ../$script ../ - make - ./bin/test_fpx && ./bin/test_pc - if [ $? -ne 0 ]; then - echo "FAILED: target-$file" - exit 1 - fi - cd .. + file=${script##*/} + file=${file%.sh} + echo target-$file + mkdir -p target-$file + cd target-$file + ../$script ../ + make + ./bin/test_fpx && ./bin/test_pc + if [ $? -ne 0 ]; then + echo "FAILED: target-$file" + exit 1 + fi + cd .. +done + +for script in preset/gmp-pbc-*; do + file=${script##*/} + file=${file%.sh} + echo target-$file + mkdir -p target-$file + cd target-$file + ../$script ../ + make + ./bin/test_fpx && ./bin/test_pc + if [ $? -ne 0 ]; then + echo "FAILED: target-$file" + exit 1 + fi + cd .. done From 08ddd8702a0880e3b89d83d763d51def90b13c47 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sun, 31 Mar 2024 04:19:29 +0200 Subject: [PATCH 26/37] Save some space with the new APIs. --- src/ep/relic_ep_map.c | 10 +++--- src/epx/relic_ep2_map.c | 27 +++++----------- src/epx/relic_ep3_map.c | 28 +++++------------ src/epx/relic_ep4_map.c | 69 ++++++++++------------------------------- src/epx/relic_ep8_map.c | 36 +++++---------------- 5 files changed, 45 insertions(+), 125 deletions(-) diff --git a/src/ep/relic_ep_map.c b/src/ep/relic_ep_map.c index 669fbc6af..4b7841b7a 100644 --- a/src/ep/relic_ep_map.c +++ b/src/ep/relic_ep_map.c @@ -485,16 +485,16 @@ void ep_map_swift(ep_t p, const uint8_t *msg, size_t len) { int c2 = fp_is_sqr(u); int c3 = fp_is_sqr(v); - dv_swap_sec(x1, y1, RLC_FP_DIGS, c2); - dv_swap_sec(t, u, RLC_FP_DIGS, c2); - dv_swap_sec(x1, z1, RLC_FP_DIGS, c3); - dv_swap_sec(t, v, RLC_FP_DIGS, c3); + fp_copy_sec(x1, y1, c2); + fp_copy_sec(t, u, c2); + fp_copy_sec(x1, z1, c3); + fp_copy_sec(t, v, c3); if (!fp_srt(t, t)) { RLC_THROW(ERR_NO_VALID); } fp_neg(u, t); - dv_swap_sec(t, u, RLC_FP_DIGS, fp_is_even(t) ^ s); + fp_copy_sec(t, u, fp_is_even(t) ^ s); fp_copy(p->x, x1); fp_copy(p->y, t); diff --git a/src/epx/relic_ep2_map.c b/src/epx/relic_ep2_map.c index db7a8694f..2445deed3 100644 --- a/src/epx/relic_ep2_map.c +++ b/src/epx/relic_ep2_map.c @@ -475,27 +475,17 @@ void ep2_map_swift(ep2_t p, const uint8_t *msg, size_t len) { fp2_sqr(z1, z1); fp2_add(z1, z1, u); - fp2_sqr(t, x1); - fp2_mul(t, t, x1); - fp2_add(t, t, ctx->ep2_b); - - fp2_sqr(u, y1); - fp2_mul(u, u, y1); - fp2_add(u, u, ctx->ep2_b); - - fp2_sqr(v, z1); - fp2_mul(v, v, z1); - fp2_add(v, v, ctx->ep2_b); + ep2_rhs(t, x1); + ep2_rhs(u, y1); + ep2_rhs(v, z1); c2 = fp2_is_sqr(u); c3 = fp2_is_sqr(v); - for (int i = 0; i < 2; i++) { - dv_swap_sec(x1[i], y1[i], RLC_FP_DIGS, c2); - dv_swap_sec(t[i], u[i], RLC_FP_DIGS, c2); - dv_swap_sec(x1[i], z1[i], RLC_FP_DIGS, c3); - dv_swap_sec(t[i], v[i], RLC_FP_DIGS, c3); - } + fp2_copy_sec(x1, y1, c2); + fp2_copy_sec(t, u, c2); + fp2_copy_sec(x1, z1, c3); + fp2_copy_sec(t, v, c3); if (!fp2_srt(t, t)) { RLC_THROW(ERR_NO_VALID); @@ -510,8 +500,7 @@ void ep2_map_swift(ep2_t p, const uint8_t *msg, size_t len) { sign ^= (t0 | (t0z & t1)); fp2_neg(u, t); - dv_swap_sec(t[0], u[0], RLC_FP_DIGS, sign); - dv_swap_sec(t[1], u[1], RLC_FP_DIGS, sign); + fp2_copy_sec(t, u, sign); fp2_copy(p->x, x1); fp2_copy(p->y, t); diff --git a/src/epx/relic_ep3_map.c b/src/epx/relic_ep3_map.c index 87bf57315..29388c842 100644 --- a/src/epx/relic_ep3_map.c +++ b/src/epx/relic_ep3_map.c @@ -115,27 +115,17 @@ void ep3_map(ep3_t p, const uint8_t *msg, size_t len) { fp3_sqr(z1, z1); fp3_add(z1, z1, u); - fp3_sqr(t, x1); - fp3_mul(t, t, x1); - fp3_add(t, t, ep3_curve_get_b()); - - fp3_sqr(u, y1); - fp3_mul(u, u, y1); - fp3_add(u, u, ep3_curve_get_b()); - - fp3_sqr(v, z1); - fp3_mul(v, v, z1); - fp3_add(v, v, ep3_curve_get_b()); + ep3_rhs(t, x1); + ep3_rhs(u, y1); + ep3_rhs(v, z1); c2 = fp3_is_sqr(u); c3 = fp3_is_sqr(v); - for (int i = 0; i < 3; i++) { - dv_swap_sec(x1[i], y1[i], RLC_FP_DIGS, c2); - dv_swap_sec(t[i], u[i], RLC_FP_DIGS, c2); - dv_swap_sec(x1[i], z1[i], RLC_FP_DIGS, c3); - dv_swap_sec(t[i], v[i], RLC_FP_DIGS, c3); - } + fp3_copy_sec(t, u, c2); + fp3_copy_sec(x1, y1, c2); + fp3_copy_sec(t, v, c3); + fp3_copy_sec(x1, z1, c3); if (!fp3_srt(t, t)) { RLC_THROW(ERR_NO_VALID); @@ -154,9 +144,7 @@ void ep3_map(ep3_t p, const uint8_t *msg, size_t len) { sign ^= (t0 | (t0z & (t1 | (t1z & t2)))); fp3_neg(u, t); - dv_swap_sec(t[0], u[0], RLC_FP_DIGS, sign); - dv_swap_sec(t[1], u[1], RLC_FP_DIGS, sign); - dv_swap_sec(t[2], u[2], RLC_FP_DIGS, sign); + fp3_copy_sec(t, u, sign); fp3_copy(p->x, x1); fp3_copy(p->y, t); diff --git a/src/epx/relic_ep4_map.c b/src/epx/relic_ep4_map.c index bbcfba746..7c47ffe00 100644 --- a/src/epx/relic_ep4_map.c +++ b/src/epx/relic_ep4_map.c @@ -177,45 +177,23 @@ void ep4_map(ep4_t p, const uint8_t *msg, size_t len) { fp4_mul(y1, y1, den[1]); fp4_mul(z1, z1, den[2]); - fp4_sqr(t, x1); - fp4_add(t, t, ep4_curve_get_a()); - fp4_mul(t, t, x1); - fp4_sqr(u, y1); - fp4_add(u, u, ep4_curve_get_a()); - fp4_mul(u, u, y1); - fp4_sqr(v, z1); - fp4_add(v, v, ep4_curve_get_a()); - fp4_mul(v, v, z1); + ep4_rhs(t, x1); + ep4_rhs(u, y1); + ep4_rhs(v, z1); int c2 = fp4_is_sqr(u); int c3 = fp4_is_sqr(v); - dv_swap_sec(t[0][0], u[0][0], RLC_FP_DIGS, c2); - dv_swap_sec(t[0][1], u[0][1], RLC_FP_DIGS, c2); - dv_swap_sec(t[1][0], u[1][0], RLC_FP_DIGS, c2); - dv_swap_sec(t[1][1], u[1][1], RLC_FP_DIGS, c2); - dv_swap_sec(x1[0][0], y1[0][0], RLC_FP_DIGS, c2); - dv_swap_sec(x1[0][1], y1[0][1], RLC_FP_DIGS, c2); - dv_swap_sec(x1[1][0], y1[1][0], RLC_FP_DIGS, c2); - dv_swap_sec(x1[1][1], y1[1][1], RLC_FP_DIGS, c2); - dv_swap_sec(t[0][0], v[0][0], RLC_FP_DIGS, c3); - dv_swap_sec(t[0][1], v[0][1], RLC_FP_DIGS, c3); - dv_swap_sec(t[1][0], v[1][0], RLC_FP_DIGS, c3); - dv_swap_sec(t[1][1], v[1][1], RLC_FP_DIGS, c3); - dv_swap_sec(x1[0][0], z1[0][0], RLC_FP_DIGS, c3); - dv_swap_sec(x1[0][1], z1[0][1], RLC_FP_DIGS, c3); - dv_swap_sec(x1[1][0], z1[1][0], RLC_FP_DIGS, c3); - dv_swap_sec(x1[1][1], z1[1][1], RLC_FP_DIGS, c3); + fp4_copy_sec(t, u, c2); + fp4_copy_sec(x1, y1, c2); + fp4_copy_sec(t, v, c3); + fp4_copy_sec(x1, z1, c3); if (!fp4_srt(t, t)) { RLC_THROW(ERR_NO_VALID); } fp4_neg(u, t); - c2 = fp_is_even(t[0][0]); - dv_swap_sec(t[0][0], u[0][0], RLC_FP_DIGS, c2 ^ sign); - dv_swap_sec(t[0][1], u[0][1], RLC_FP_DIGS, c2 ^ sign); - dv_swap_sec(t[1][0], u[1][0], RLC_FP_DIGS, c2 ^ sign); - dv_swap_sec(t[1][1], u[1][1], RLC_FP_DIGS, c2 ^ sign); + fp4_copy_sec(t, u, fp_is_even(t[0][0]) ^ sign); fp4_copy(p->x, x1); fp4_copy(p->y, t); @@ -259,29 +237,17 @@ void ep4_map(ep4_t p, const uint8_t *msg, size_t len) { fp4_sqr(z1, z1); fp4_add(z1, z1, u); - fp4_sqr(t, x1); - fp4_mul(t, t, x1); - fp4_add(t, t, ep4_curve_get_b()); - - fp4_sqr(u, y1); - fp4_mul(u, u, y1); - fp4_add(u, u, ep4_curve_get_b()); - - fp4_sqr(v, z1); - fp4_mul(v, v, z1); - fp4_add(v, v, ep4_curve_get_b()); + ep4_rhs(t, x1); + ep4_rhs(u, y1); + ep4_rhs(v, z1); dig_t c2 = fp4_is_sqr(u); dig_t c3 = fp4_is_sqr(v); - for (int i = 0; i < 2; i++) { - for (int j = 0; j < 2; j++) { - dv_swap_sec(x1[i][j], y1[i][j], RLC_FP_DIGS, c2); - dv_swap_sec(t[i][j], u[i][j], RLC_FP_DIGS, c2); - dv_swap_sec(x1[i][j], z1[i][j], RLC_FP_DIGS, c3); - dv_swap_sec(t[i][j], v[i][j], RLC_FP_DIGS, c3); - } - } + fp4_copy_sec(x1, y1, c2); + fp4_copy_sec(t, u, c2); + fp4_copy_sec(x1, z1, c3); + fp4_copy_sec(t, v, c3); if (!fp4_srt(t, t)) { RLC_THROW(ERR_NO_VALID); @@ -301,10 +267,7 @@ void ep4_map(ep4_t p, const uint8_t *msg, size_t len) { sign ^= (s[0] | (t0z & s[1])); fp4_neg(u, t); - dv_swap_sec(t[0][0], u[0][0], RLC_FP_DIGS, sign); - dv_swap_sec(t[0][1], u[0][1], RLC_FP_DIGS, sign); - dv_swap_sec(t[1][0], u[1][0], RLC_FP_DIGS, sign); - dv_swap_sec(t[1][1], u[1][1], RLC_FP_DIGS, sign); + fp4_copy_sec(t, u, sign); fp4_copy(p->x, x1); fp4_copy(p->y, t); diff --git a/src/epx/relic_ep8_map.c b/src/epx/relic_ep8_map.c index 094154325..05f11180d 100644 --- a/src/epx/relic_ep8_map.c +++ b/src/epx/relic_ep8_map.c @@ -124,31 +124,17 @@ void ep8_map(ep8_t p, const uint8_t *msg, size_t len) { fp8_sqr(z1, z1); fp8_add(z1, z1, u); - fp8_sqr(t, x1); - fp8_mul(t, t, x1); - fp8_add(t, t, ep8_curve_get_b()); - - fp8_sqr(u, y1); - fp8_mul(u, u, y1); - fp8_add(u, u, ep8_curve_get_b()); - - fp8_sqr(v, z1); - fp8_mul(v, v, z1); - fp8_add(v, v, ep8_curve_get_b()); + ep8_rhs(t, x1); + ep8_rhs(u, y1); + ep8_rhs(v, z1); c2 = fp8_is_sqr(u); c3 = fp8_is_sqr(v); - for (int i = 0; i < 2; i++) { - for (int j = 0; j < 2; j++) { - for (int l = 0; l < 2; l++) { - dv_swap_sec(x1[i][j][l], y1[i][j][l], RLC_FP_DIGS, c2); - dv_swap_sec(t[i][j][l], u[i][j][l], RLC_FP_DIGS, c2); - dv_swap_sec(x1[i][j][l], z1[i][j][l], RLC_FP_DIGS, c3); - dv_swap_sec(t[i][j][l], v[i][j][l], RLC_FP_DIGS, c3); - } - } - } + fp8_copy_sec(t, u, c2); + fp8_copy_sec(x1, y1, c2); + fp8_copy_sec(t, v, c3); + fp8_copy_sec(x1, z1, c3); if (!fp8_srt(t, t)) { RLC_THROW(ERR_NO_VALID); @@ -170,13 +156,7 @@ void ep8_map(ep8_t p, const uint8_t *msg, size_t len) { } fp8_neg(u, t); - for (int i = 0; i < 2; i++) { - for (int j = 0; j < 2; j++) { - for (int l = 0; l < 2; l++) { - dv_swap_sec(t[i][j][l], u[i][j][l], RLC_FP_DIGS, sign); - } - } - } + fp8_copy_sec(t, u, sign); fp8_copy(p->x, x1); fp8_copy(p->y, t); From 7d6138c16a3bd93d23685a59e569bec93eaca88c Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sun, 31 Mar 2024 04:48:18 +0200 Subject: [PATCH 27/37] Search for GMP harder. --- cmake/gmp.cmake | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/cmake/gmp.cmake b/cmake/gmp.cmake index abb00c44c..f71f64e65 100644 --- a/cmake/gmp.cmake +++ b/cmake/gmp.cmake @@ -36,7 +36,18 @@ if (GMP_INCLUDE_DIR AND GMP_LIBRARIES) unset(GMP_LIBRARIES CACHE) endif (GMP_INCLUDE_DIR AND GMP_LIBRARIES) -find_path(GMP_INCLUDE_DIR NAMES gmp.h) +find_path(GMP_INCLUDE_DIR gmp.h + PATHS + ENV GMP_ROOT + ENV GMP_INCLUDE_DIR + ${GMP_ROOT} + /usr + /usr/local + $ENV{HOME}/.local + PATH_SUFFIXES + include +) + if(STBIN) find_library(GMP_LIBRARIES NAMES libgmp.a gmp.lib libgmp-10 libgmp gmp) else(STBIN) From ac4b411f9b3c66c589d2f1f195eb53af7f6341ea Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sun, 31 Mar 2024 06:35:08 +0200 Subject: [PATCH 28/37] Simplify handling of QNRs. --- src/ep/relic_ep_param.c | 3 +++ src/epx/relic_ep2_curve.c | 38 ++++++++++++++++++-------------- src/fpx/relic_fp2_mul.c | 15 ++++++++----- src/fpx/relic_fpx_field.c | 4 +++- src/low/easy/relic_fpx_add_low.c | 31 +++++++++++++++----------- test/test_fpx.c | 4 ---- 6 files changed, 54 insertions(+), 41 deletions(-) diff --git a/src/ep/relic_ep_param.c b/src/ep/relic_ep_param.c index e14a85302..e4c5334fc 100644 --- a/src/ep/relic_ep_param.c +++ b/src/ep/relic_ep_param.c @@ -1785,6 +1785,9 @@ void ep_param_print(void) { case FM18_P768: util_banner("Curve FM18-P768:", 0); break; + case B12_P1150: + util_banner("Curve B12-P1150:", 0); + break; case SS_P1536: util_banner("Curve SS-P1536:", 0); break; diff --git a/src/epx/relic_ep2_curve.c b/src/epx/relic_ep2_curve.c index 137517733..99ccdb978 100644 --- a/src/epx/relic_ep2_curve.c +++ b/src/epx/relic_ep2_curve.c @@ -275,23 +275,6 @@ /** @} */ #endif -#if defined(EP_ENDOM) && FP_PRIME == 638 -/** @{ */ -#define B12_1150_A0 "0" -#define B12_1150_A1 "0" -#define B12_1150_B0 "4" -#define B12_1150_B1 "4" -#define B12_1150_X0 "3C57F8F05130D336804E30CAA7BB45E06244DFC0BA836056B036038703719449A42CCC7C34452B4EE2DCA3CBCE0B7637E14E9CA88BDEF105440FB3F84AA95C75DE0BA05686394492B8648BB71D5E7F39" -#define B12_1150_X1 "07B30040203566584002D6DBB49A3DA1D99ECA3CBCD113C07E0CF1FFB3FA4F87F034A034C86F56DB380F2810AC329ED8BD6FE0F4D5C1FA26949739AF82D3AAD4702D2186862B0293E16C5EDDDDA3C922" -#define B12_1150_Y0 "29ED1A1C4F3F5AFC64AB2BA97CFA4D17998061179331A1C34E024B7D82134C60A3569F644E4155753C48698C8A01C80C0C3CEC9E3BDE2E5E22D81BBB514FD24DE186FEBA69B82E88809BFCCE51A1840F" -#define B12_1150_Y1 "3795191221DB4917EEE4B7B85BC7D7CA0C60E82116064463FED0892BA82ACECF905E6DB8083C5F589F04DB80E3203C1B2BEB52ACDED6DF96FC515F36761E7152AEED13369A504FE38C4FF93860B89550" -#define B12_1150_R "50F94035FF4000FFFFFFFFFFF9406BFDC0040000000000000035FB801DFFBFFFFFFFFFFFFFFF401BFF80000000000000000000FFC01" -#define B12_1150_H "2D88688DBA18275F5801BFFD4DDE93725697788C46C7B4BC8050639BA17EA2158B6784CCACDDECE490643943E5376D29C71C96B894056CCCC13C3DC6AAAAAA0F89601DC2979B3721C71C71C8B38CB8AEFEEB9E1C71C71C71C4B9FED17AE51B8E38E38E38E38FC954E8C65" -#define B12_1150_MAPU0 "0" -#define B12_1150_MAPU1 "1" -/** @} */ -#endif - #if defined(EP_ENDOM) && FP_PRIME == 638 /** @{ */ #define B12_P638_A0 "0" @@ -309,6 +292,23 @@ /** @} */ #endif +#if defined(EP_ENDOM) && FP_PRIME == 1150 +/** @{ */ +#define B12_P1150_A0 "0" +#define B12_P1150_A1 "0" +#define B12_P1150_B0 "20" +#define B12_P1150_B1 "1" +#define B12_P1150_X0 "4E1E10FAFC4A9E497CA939E1A8D1CF8893750CC24D82E0ED0C492C1FA618AB2FF7A02F95B12B8234C2E42B74DF654EF090ED50AD9EBF2E727F27B6CD6F368F84AB2AF7318C02994534DE0C863AC311527D3AFC2C2A4BD1A921899BD6B02CC89106FAB128E698C87928158FD2A79AABDC262097BACA9E7B226225E82DEB020FFEA5125F8F00213F5860A0217A6CCB165" +#define B12_P1150_X1 "4E0131D6174E76582E6456DC0D1D3C32BFD89F7910DD17E18C83CD8F9C658D0448A87F327AD1B7676521B2E2E0D12C4A001431F5DEE3C3E36A543D8A380ABC3CEC68488B8793A9BF98946B6994F18EC71D843B9CA980858B48C64833D7F327BBBEDBB3255D16813D550B8456140B28C587C1415C445AE12371EDB8FDC90B2E79132DB4C91AD26BE1C1B5E88E6DEA271" +#define B12_P1150_Y0 "6DDD7AA03804184D92896703D977BE38F4185F860B61EE1D6F8D586158C7B5D50905CEDE8B9313CC447F11F0C3FD83858E89E3AF93D902990383C6016138600F6A982713E0ED83D02240F89014822BCB3B5F5CD92312019DF5CDF6E9F9133761F76FFC6A63440C4DE8795057B5C184CE94A8BF49C3C7DF5AD4A691769981CA88BA23529EAD7A474ACCFA9915566548E" +#define B12_P1150_Y1 "D302BD36809044D5F86AAD68CBB100A01B504668BF5F520D0F12C2654B5A8783E6348B69958016D814548DC1380CE8CC760CFDF7832FFC51A039C6461FF93DBFB91271969B19EACDDB805038EE619DBCFC725BB34D5D5489FBCC2603ED09B35BBA11813F15468929CF817511E890F8AB76A749AF6C7A88E495DCDA0848324A4DC0DC566B98FDEE53ED8AAE8EC2AC19C" +#define B12_P1150_R "C5C1000000000000001B30F00000000000003622EC6000034BC000057869AF00005703000575ED588100ABBDB4005D60C600000BA2C6103D98AD58000B730C000210975450006301E00100052800004000108000050000000000200000000001" +#define B12_P1150_H "10F92DB9000000000004AAEC92E000000000099D3161ABC0D94249423783DA57023447266F56A9EBD69D80C7566A16D9BA9FBE7158549609DC9A0A3AA8118DCA532B07515F0AFC55A9B3F5EF6D796B3CE40C5705D3C1A799E87E283A56736E303655930FE3A33D221A3188B1CA2886B457B6998AE042390CED049D27C756A249FBA914ED3D1CC2CA3A96C10101BDBDA7F906593201A323F4EBDE9F7D48EBA93923F5727B11CA0DF5F1E65565D555573C71EBC71C81C71C71C71CB8E38E38E390" +#define B12_P1150_MAPU0 "0" +#define B12_P1150_MAPU1 "1" +/** @} */ +#endif + /** * Assigns a set of ordinary elliptic curve parameters. * @@ -861,6 +861,10 @@ void ep2_curve_set_twist(int type) { case B12_P638: ASSIGN(B12_P638); break; +#elif FP_PRIME == 1150 + case B12_P1150: + ASSIGN(B12_P1150); + break; #endif default: (void)str; diff --git a/src/fpx/relic_fp2_mul.c b/src/fpx/relic_fp2_mul.c index 558f8b0f7..457e8724b 100644 --- a/src/fpx/relic_fp2_mul.c +++ b/src/fpx/relic_fp2_mul.c @@ -121,17 +121,20 @@ void fp2_mul_nor_basic(fp2_t c, const fp2_t a) { int qnr = fp2_field_get_qnr(); switch (fp_prime_get_mod8()) { - case 3: - /* If p = 3 mod 8, (1 + i) is a QNR/CNR. */ - fp_neg(t[0], a[1]); - fp_add(c[1], a[0], a[1]); - fp_add(c[0], t[0], a[0]); - break; case 1: case 5: /* If p = 1,5 mod 8, (i) is a QNR/CNR. */ fp2_mul_art(c, a); break; + case 3: + if (qnr == 1) { + /* If p = 3 mod 8, (1 + i) is a QNR/CNR. */ + fp_neg(t[0], a[1]); + fp_add(c[1], a[0], a[1]); + fp_add(c[0], t[0], a[0]); + break; + } + /* Otherwise, fall back to next one. */ case 7: /* If p = 7 mod 8, we choose (2^k + i) as a QNR/CNR. */ fp2_mul_art(t, a); diff --git a/src/fpx/relic_fpx_field.c b/src/fpx/relic_fpx_field.c index fad2a4536..bf89b87e9 100644 --- a/src/fpx/relic_fpx_field.c +++ b/src/fpx/relic_fpx_field.c @@ -38,7 +38,9 @@ int fp2_field_get_qnr() { /* Override some of the results when cubic non-residue is also needed. */ -#if FP_PRIME == 158 || FP_PRIME == 256 +#if FP_PRIME == 1150 + return 32; +#elif FP_PRIME == 158 || FP_PRIME == 256 return 4; #elif FP_PRIME == 446 return 16; diff --git a/src/low/easy/relic_fpx_add_low.c b/src/low/easy/relic_fpx_add_low.c index b0869e642..8954883f1 100755 --- a/src/low/easy/relic_fpx_add_low.c +++ b/src/low/easy/relic_fpx_add_low.c @@ -104,17 +104,19 @@ void fp2_norm_low(fp2_t c, fp2_t a) { #else int qnr = fp2_field_get_qnr(); switch (fp_prime_get_mod8()) { - case 3: - /* If p = 3 mod 8, (1 + i) is a QNR/CNR. */ - fp_neg(t[0], a[1]); - fp_add(c[1], a[0], a[1]); - fp_add(c[0], t[0], a[0]); - break; case 1: case 5: /* If p = 1,5 mod 8, (i) is a QNR/CNR. */ fp2_mul_art(c, a); break; + case 3: + if (qnr == 1) { + fp_copy(t[0], a[1]); + fp_add(c[1], a[0], a[1]); + fp_sub(c[0], a[0], t[0]); + break; + } + /* Otherwise, fall back to next one. */ case 7: /* If p = 7 mod 8, we choose (2^k + i) as QNR/CNR. */ fp2_mul_art(t, a); @@ -153,13 +155,6 @@ void fp2_nord_low(dv2_t c, dv2_t a) { #else int qnr = fp2_field_get_qnr(); switch (fp_prime_get_mod8()) { - case 3: - /* If p = 3 mod 8, (1 + i) is a QNR, i^2 = -1. */ - /* (a_0 + a_1 * i) * (1 + i) = (a_0 - a_1) + (a_0 + a_1) * i. */ - dv_copy(t[0], a[1], 2 * RLC_FP_DIGS); - fp_addc_low(c[1], a[0], a[1]); - fp_subc_low(c[0], a[0], t[0]); - break; case 1: case 5: /* If p = 1,5 mod 8, (i) is a QNR. */ @@ -172,6 +167,16 @@ void fp2_nord_low(dv2_t c, dv2_t a) { } dv_copy(c[1], t[0], 2 * RLC_FP_DIGS); break; + case 3: + if (qnr == 1) { + /* If p = 3 mod 8, (1 + i) is a QNR, i^2 = -1. */ + /* (a_0 + a_1 * i) * (1 + i) = (a_0 - a_1) + (a_0 + a_1) * i. */ + dv_copy(t[0], a[1], 2 * RLC_FP_DIGS); + fp_addc_low(c[1], a[0], a[1]); + fp_subc_low(c[0], a[0], t[0]); + break; + } + /* Otherwise, fall back to next one. */ case 7: /* If p = 7 mod 8, (2^k + i) is a QNR/CNR. */ dv_copy(t[0], a[0], 2 * RLC_FP_DIGS); diff --git a/test/test_fpx.c b/test/test_fpx.c index 4a242fe17..0498a37de 100644 --- a/test/test_fpx.c +++ b/test/test_fpx.c @@ -476,10 +476,6 @@ static int multiplication2(void) { fp2_mul_art(c, a); break; case 3: - fp_set_dig(c[0], 1); - fp_set_dig(c[1], 1); - fp2_mul(c, a, c); - break; case 7: fp_set_dig(c[0], fp2_field_get_qnr()); fp_set_dig(c[1], 1); From dc6bce073c9aa61e7757f5acc617fd5c4e874047 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sun, 31 Mar 2024 06:36:02 +0200 Subject: [PATCH 29/37] Update preset. --- preset/gmp-pbc-bls12-1150.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/preset/gmp-pbc-bls12-1150.sh b/preset/gmp-pbc-bls12-1150.sh index a2c95863d..4ed607e08 100755 --- a/preset/gmp-pbc-bls12-1150.sh +++ b/preset/gmp-pbc-bls12-1150.sh @@ -1,2 +1,2 @@ #!/bin/sh -cmake -DWSIZE=64 -DRAND=UDEV -DSHLIB=OFF -DSTBIN=ON -DTIMER=CYCLE -DCHECK=off -DVERBS=off -DARITH=gmp-sec -DFP_PRIME=1150 -DFP_METHD="INTEG;INTEG;INTEG;MONTY;JMPDS;JMPDS;SLIDE" -DCFLAGS="-O3 -funroll-loops -fomit-frame-pointer -finline-small-functions -march=native -mtune=native" -DFP_PMERS=off -DFP_QNRES=on -DFPX_METHD="INTEG;INTEG;LAZYR" -DEP_PLAIN=off -DEP_SUPER=off -DPP_METHD="LAZYR;OATEP" $1 +cmake -DWSIZE=64 -DRAND=UDEV -DSHLIB=OFF -DSTBIN=ON -DTIMER=CYCLE -DCHECK=off -DVERBS=off -DARITH=gmp-sec -DBN_PRECI=1536 -DFP_PRIME=1150 -DFP_METHD="INTEG;INTEG;INTEG;MONTY;JMPDS;JMPDS;SLIDE" -DCFLAGS="-O3 -funroll-loops -fomit-frame-pointer -finline-small-functions -march=native -mtune=native" -DFP_PMERS=off -DFP_QNRES=off -DFPX_METHD="INTEG;INTEG;LAZYR" -DEP_PLAIN=off -DEP_SUPER=off -DPP_METHD="LAZYR;OATEP" $1 From bd42269ba765f91944f291ac32e7125cba87f704 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sun, 31 Mar 2024 13:49:25 +0200 Subject: [PATCH 30/37] Fix towering for BLS12-446 curve. --- src/fpx/relic_fpx_field.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/fpx/relic_fpx_field.c b/src/fpx/relic_fpx_field.c index bf89b87e9..43df5f0a5 100644 --- a/src/fpx/relic_fpx_field.c +++ b/src/fpx/relic_fpx_field.c @@ -42,8 +42,6 @@ int fp2_field_get_qnr() { return 32; #elif FP_PRIME == 158 || FP_PRIME == 256 return 4; -#elif FP_PRIME == 446 - return 16; #else return core_get()->qnr2; #endif From 2201eda8c8e8b71ba9d775b113f73b53523ccccd Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sun, 31 Mar 2024 14:07:09 +0200 Subject: [PATCH 31/37] Restoring towering for BN-446. --- src/fpx/relic_fpx_field.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/fpx/relic_fpx_field.c b/src/fpx/relic_fpx_field.c index 43df5f0a5..62096c0d1 100644 --- a/src/fpx/relic_fpx_field.c +++ b/src/fpx/relic_fpx_field.c @@ -42,6 +42,8 @@ int fp2_field_get_qnr() { return 32; #elif FP_PRIME == 158 || FP_PRIME == 256 return 4; +#elif FP_PRIME == 446 && !defined(FP_QNRES) + return 16; #else return core_get()->qnr2; #endif From e28bb68b44f86b1fa8c6b7d32366ac2a6f86f2db Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sun, 31 Mar 2024 17:22:45 +0200 Subject: [PATCH 32/37] Minor polish to remove warnings. --- src/pc/relic_pc_exp.c | 7 ++++--- src/pc/relic_pc_util.c | 7 +++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pc/relic_pc_exp.c b/src/pc/relic_pc_exp.c index d4837a0a4..271bb0d29 100644 --- a/src/pc/relic_pc_exp.c +++ b/src/pc/relic_pc_exp.c @@ -381,8 +381,6 @@ void gt_exp(gt_t c, const gt_t a, const bn_t b) { } void gt_exp_sec(gt_t c, const gt_t a, const bn_t b) { - size_t f = 0; - if (bn_bits(b) <= RLC_DIG) { gt_exp_dig(c, a, b->dp[0]); if (bn_sign(b) == RLC_NEG) { @@ -391,6 +389,10 @@ void gt_exp_sec(gt_t c, const gt_t a, const bn_t b) { return; } +#if FP_PRIME <= 1536 + + size_t f = 0; + switch (ep_param_embed()) { case 1: case 2: @@ -412,7 +414,6 @@ void gt_exp_sec(gt_t c, const gt_t a, const bn_t b) { break; } -#if FP_PRIME <= 1536 gt_exp_imp(c, a, b, f); #else RLC_CAT(RLC_GT_LOWER, exp_monty)(c, a, b); diff --git a/src/pc/relic_pc_util.c b/src/pc/relic_pc_util.c index 3cc683267..cbee2cf96 100644 --- a/src/pc/relic_pc_util.c +++ b/src/pc/relic_pc_util.c @@ -266,15 +266,14 @@ int g1_is_valid(const g1_t a) { } int g2_is_valid(const g2_t a) { +#if FP_PRIME >= 1536 + return g1_is_valid(a); +#else g2_t s, t, u, v, w; bn_t n; dig_t rem; int r = 0; -#if FP_PRIME >= 1536 - return g1_is_valid(a); -#else - if (g2_is_infty(a)) { return 0; } From 8bc067fb7306ae0a5dfcd7d2be005adad5a480cc Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sun, 31 Mar 2024 18:26:38 +0200 Subject: [PATCH 33/37] Reduce number of tests/benches to speed this one up. --- preset/gmp-pbc-k13072.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/preset/gmp-pbc-k13072.sh b/preset/gmp-pbc-k13072.sh index cc25fe6fd..5eff63b76 100755 --- a/preset/gmp-pbc-k13072.sh +++ b/preset/gmp-pbc-k13072.sh @@ -1,2 +1,2 @@ #!/bin/sh -cmake -DCHECK=off -DARITH=gmp -DBN_PRECI=3072 -DFP_PRIME=3072 -DFP_QNRES=off -DFP_METHD="BASIC;COMBA;COMBA;MONTY;JMPDS;JMPDS;SLIDE" -DFPX_METHD="INTEG;INTEG;LAZYR" -DPP_METHD="LAZYR;OATEP" -DCFLAGS="-O2 -funroll-loops -fomit-frame-pointer" $1 +cmake -DCHECK=off -DARITH=gmp -DBN_PRECI=3072 -DFP_PRIME=3072 -DFP_QNRES=off -DFP_METHD="BASIC;COMBA;COMBA;MONTY;JMPDS;JMPDS;SLIDE" -DFPX_METHD="INTEG;INTEG;LAZYR" -DPP_METHD="LAZYR;OATEP" -DCFLAGS="-O2 -funroll-loops -fomit-frame-pointer" -DEP_PLAIN=on -DTESTS=10 -DBENCH=10 $1 From 44ebbb9c22235058759a71259f5b7181455431e0 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sun, 31 Mar 2024 19:04:55 +0200 Subject: [PATCH 34/37] Do not run tests for extensions we do not need. --- test/test_fpx.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test_fpx.c b/test/test_fpx.c index 0498a37de..3c0da4afb 100644 --- a/test/test_fpx.c +++ b/test/test_fpx.c @@ -8781,7 +8781,7 @@ int main(void) { } /* Only execute these if there is an assigned cubic non-residue. */ - if (fp_prime_get_cnr()) { + if (fp_prime_get_cnr() && (ep_param_embed() >= 3)) { util_print("\n-- Cubic extension: %d as CNR\n", fp_prime_get_cnr()); util_banner("Utilities:", 1); @@ -8844,7 +8844,7 @@ int main(void) { } /* Fp^4 is defined as a quadratic extension of Fp^2. */ - if (fp_prime_get_qnr()) { + if (fp_prime_get_qnr() && (ep_param_embed() >= 4)) { util_print("\n-- Quartic extension: (i + %d) as QNR\n", fp2_field_get_qnr()); util_banner("Utilities:", 1); @@ -8908,7 +8908,7 @@ int main(void) { } /* Fp^6 is defined as a cubic extension of Fp^2. */ - if (fp_prime_get_qnr() && fp_prime_get_cnr()) { + if (fp_prime_get_qnr() && fp_prime_get_cnr() && (ep_param_embed() >= 6)) { util_print("\n-- Sextic extension: (i + %d) as CNR\n", fp2_field_get_qnr()); util_banner("Utilities:", 1); From 63f24c9dc3b663fe08398ea6cfc5c46741001e66 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Sun, 31 Mar 2024 19:17:46 +0200 Subject: [PATCH 35/37] Add missing preset. --- preset/x64-pbc-fm16-765.sh | 2 ++ 1 file changed, 2 insertions(+) create mode 100755 preset/x64-pbc-fm16-765.sh diff --git a/preset/x64-pbc-fm16-765.sh b/preset/x64-pbc-fm16-765.sh new file mode 100755 index 000000000..4cc9fbcdb --- /dev/null +++ b/preset/x64-pbc-fm16-765.sh @@ -0,0 +1,2 @@ +#!/bin/sh +cmake -DWSIZE=64 -DRAND=UDEV -DSHLIB=OFF -DSTBIN=ON -DTIMER=CYCLE -DCHECK=off -DVERBS=off -DARITH=x64-asm-12l -DBN_PRECI=3072 -DFP_PRIME=765 -DFP_METHD="INTEG;INTEG;INTEG;MONTY;JMPDS;JMPDS;SLIDE" -DCFLAGS="-O3 -funroll-loops -fomit-frame-pointer -march=native -mtune=native" -DFP_PMERS=off -DFP_QNRES=off -DFPX_METHD="INTEG;INTEG;LAZYR" -DEP_PLAIN=off -DEP_SUPER=off -DPP_METHD="LAZYR;OATEP" -DWITH="ALL" $1 From e52d0437f4a2386a26153831141bdbda46ad4e67 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Mon, 1 Apr 2024 13:06:33 +0200 Subject: [PATCH 36/37] Rename ep_param_embed to ep_curve_embed. --- bench/bench_fpx.c | 26 ++++---- bench/bench_pc.c | 2 +- bench/bench_pp.c | 16 ++--- include/relic_ep.h | 2 +- include/relic_label.h | 4 +- src/ep/relic_ep_curve.c | 30 +++++++++ src/ep/relic_ep_param.c | 29 --------- src/fpx/relic_fpx_cyc.c | 2 +- src/pc/relic_pc_exp.c | 134 +++++++++++++++++++++++++++++++++++++++- test/test_fpx.c | 32 +++++----- test/test_pp.c | 18 +++--- 11 files changed, 214 insertions(+), 81 deletions(-) diff --git a/bench/bench_fpx.c b/bench/bench_fpx.c index 4deaa7738..215f80361 100644 --- a/bench/bench_fpx.c +++ b/bench/bench_fpx.c @@ -2060,7 +2060,7 @@ static void arith12(void) { } BENCH_END; - if (ep_curve_is_pairf() && ep_param_embed() == 12) { + if (ep_curve_is_pairf() && ep_curve_embed() == 12) { BENCH_RUN("fp12_exp_cyc (gls)") { fp12_rand(a); fp12_conv_cyc(a, a); @@ -2347,7 +2347,7 @@ static void arith16(void) { } BENCH_END; - if (ep_curve_is_pairf() && ep_param_embed() == 16) { + if (ep_curve_is_pairf() && ep_curve_embed() == 16) { BENCH_RUN("fp16_exp_cyc (gls)") { fp16_rand(a); fp16_conv_cyc(a, a); @@ -2717,7 +2717,7 @@ static void arith18(void) { } BENCH_END; - if (ep_curve_is_pairf() && ep_param_embed() == 18) { + if (ep_curve_is_pairf() && ep_curve_embed() == 18) { BENCH_RUN("fp18_exp_cyc (gls)") { fp18_rand(a); fp18_conv_cyc(a, a); @@ -3050,7 +3050,7 @@ static void arith24(void) { } BENCH_END; - if (ep_curve_is_pairf() && ep_param_embed() == 24) { + if (ep_curve_is_pairf() && ep_curve_embed() == 24) { BENCH_RUN("fp24_exp_cyc (gls)") { fp24_rand(a); fp24_conv_cyc(a, a); @@ -3428,7 +3428,7 @@ static void arith48(void) { } BENCH_END; - if (ep_curve_is_pairf() && ep_param_embed() == 48) { + if (ep_curve_is_pairf() && ep_curve_embed() == 48) { BENCH_RUN("fp48_exp_cyc (gls)") { fp48_rand(a); fp48_conv_cyc(a, a); @@ -3922,7 +3922,7 @@ int main(void) { arith6(); } - if (fp_prime_get_qnr() && (ep_param_embed() >= 8)) { + if (fp_prime_get_qnr() && (ep_curve_embed() >= 8)) { util_banner("Octic extension:", 0); util_banner("Utilities:", 1); memory8(); @@ -3931,7 +3931,7 @@ int main(void) { arith8(); } - if (fp_prime_get_cnr() && (ep_param_embed() >= 9)) { + if (fp_prime_get_cnr() && (ep_curve_embed() >= 9)) { util_banner("Nonic extension:", 0); util_banner("Utilities:", 1); memory9(); @@ -3940,7 +3940,7 @@ int main(void) { arith9(); } - if (fp_prime_get_qnr() && fp_prime_get_cnr() && (ep_param_embed() >= 12)) { + if (fp_prime_get_qnr() && fp_prime_get_cnr() && (ep_curve_embed() >= 12)) { util_banner("Dodecic extension:", 0); util_banner("Utilities:", 1); memory12(); @@ -3949,7 +3949,7 @@ int main(void) { arith12(); } - if (fp_prime_get_qnr() && (ep_param_embed() >= 16)) { + if (fp_prime_get_qnr() && (ep_curve_embed() >= 16)) { util_banner("Sextadecic extension:", 0); util_banner("Utilities:", 1); memory16(); @@ -3959,7 +3959,7 @@ int main(void) { arith16(); } - if (fp_prime_get_cnr() && (ep_param_embed() >= 18)) { + if (fp_prime_get_cnr() && (ep_curve_embed() >= 18)) { util_banner("Octdecic extension:", 0); util_banner("Utilities:", 1); memory18(); @@ -3969,7 +3969,7 @@ int main(void) { arith18(); } - if (fp_prime_get_qnr() && (ep_param_embed() >= 24)) { + if (fp_prime_get_qnr() && (ep_curve_embed() >= 24)) { util_banner("Extension of degree 24:", 0); util_banner("Utilities:", 1); memory24(); @@ -3978,7 +3978,7 @@ int main(void) { arith24(); } - if (fp_prime_get_qnr() && (ep_param_embed() >= 48)) { + if (fp_prime_get_qnr() && (ep_curve_embed() >= 48)) { util_banner("Extension of degree 48:", 0); util_banner("Utilities:", 1); memory48(); @@ -3987,7 +3987,7 @@ int main(void) { arith48(); } - if (fp_prime_get_cnr() && (ep_param_embed() >= 54)) { + if (fp_prime_get_cnr() && (ep_curve_embed() >= 54)) { util_banner("Extension of degree 54:", 0); util_banner("Utilities:", 1); memory54(); diff --git a/bench/bench_pc.c b/bench/bench_pc.c index ee89804fe..bb040c6f4 100755 --- a/bench/bench_pc.c +++ b/bench/bench_pc.c @@ -629,7 +629,7 @@ static void util(void) { BENCH_ADD(gt_read_bin(a, bin, l)); } BENCH_END; - if (ep_param_embed() == 12) { + if (ep_curve_embed() == 12) { BENCH_RUN("gt_write_bin (1)") { gt_rand(a); l = gt_size_bin(a, 1); diff --git a/bench/bench_pp.c b/bench/bench_pp.c index a90c4e329..12407a134 100644 --- a/bench/bench_pp.c +++ b/bench/bench_pp.c @@ -1345,35 +1345,35 @@ int main(void) { ep_param_print(); util_banner("Arithmetic:", 1); - if (ep_param_embed() == 2) { + if (ep_curve_embed() == 2) { pairing2(); } - if (ep_param_embed() == 8) { + if (ep_curve_embed() == 8) { pairing8(); } - if (ep_param_embed() == 12) { + if (ep_curve_embed() == 12) { pairing12(); } - if (ep_param_embed() == 16) { + if (ep_curve_embed() == 16) { pairing16(); } - if (ep_param_embed() == 18) { + if (ep_curve_embed() == 18) { pairing18(); } - if (ep_param_embed() == 48) { + if (ep_curve_embed() == 48) { pairing24(); } - if (ep_param_embed() == 48) { + if (ep_curve_embed() == 48) { pairing48(); } - if (ep_param_embed() == 54) { + if (ep_curve_embed() == 54) { pairing54(); } diff --git a/include/relic_ep.h b/include/relic_ep.h index 2a773f5db..9e9a6efc5 100644 --- a/include/relic_ep.h +++ b/include/relic_ep.h @@ -744,7 +744,7 @@ int ep_param_level(void); /** * Returns the embedding degree of the currently configured elliptic curve. */ -int ep_param_embed(void); +int ep_curve_embed(void); /** * Tests if a point on a prime elliptic curve is at the infinity. diff --git a/include/relic_label.h b/include/relic_label.h index 1b4b5404f..e030b9128 100644 --- a/include/relic_label.h +++ b/include/relic_label.h @@ -944,7 +944,7 @@ #undef ep_param_get #undef ep_param_print #undef ep_param_level -#undef ep_param_embed +#undef ep_curve_embed #undef ep_is_infty #undef ep_set_infty #undef ep_copy @@ -1037,7 +1037,7 @@ #define ep_param_get RLC_PREFIX(ep_param_get) #define ep_param_print RLC_PREFIX(ep_param_print) #define ep_param_level RLC_PREFIX(ep_param_level) -#define ep_param_embed RLC_PREFIX(ep_param_embed) +#define ep_curve_embed RLC_PREFIX(ep_curve_embed) #define ep_is_infty RLC_PREFIX(ep_is_infty) #define ep_set_infty RLC_PREFIX(ep_set_infty) #define ep_copy RLC_PREFIX(ep_copy) diff --git a/src/ep/relic_ep_curve.c b/src/ep/relic_ep_curve.c index e18fc093c..fd5769ab6 100644 --- a/src/ep/relic_ep_curve.c +++ b/src/ep/relic_ep_curve.c @@ -521,3 +521,33 @@ void ep_curve_set_endom(const fp_t a, const fp_t b, const ep_t g, const bn_t r, } #endif + + +int ep_curve_embed(void) { + switch (core_get()->ep_is_pairf) { + case EP_K1: + return 1; + case EP_SS2: + return 2; + case EP_GMT8: + return 8; + case EP_BN: + case EP_B12: + return 12; + case EP_N16: + case EP_FM16: + case EP_K16: + return 16; + case EP_K18: + case EP_FM18: + case EP_SG18: + return 18; + case EP_B24: + return 24; + case EP_B48: + return 48; + case EP_SG54: + return 54; + } + return 0; +} \ No newline at end of file diff --git a/src/ep/relic_ep_param.c b/src/ep/relic_ep_param.c index e4c5334fc..c2b9a25ed 100644 --- a/src/ep/relic_ep_param.c +++ b/src/ep/relic_ep_param.c @@ -1863,32 +1863,3 @@ int ep_param_level(void) { } return 0; } - -int ep_param_embed(void) { - switch (core_get()->ep_is_pairf) { - case EP_K1: - return 1; - case EP_SS2: - return 2; - case EP_GMT8: - return 8; - case EP_BN: - case EP_B12: - return 12; - case EP_N16: - case EP_FM16: - case EP_K16: - return 16; - case EP_K18: - case EP_FM18: - case EP_SG18: - return 18; - case EP_B24: - return 24; - case EP_B48: - return 48; - case EP_SG54: - return 54; - } - return 0; -} diff --git a/src/fpx/relic_fpx_cyc.c b/src/fpx/relic_fpx_cyc.c index 57d41efc4..ad97c903a 100644 --- a/src/fpx/relic_fpx_cyc.c +++ b/src/fpx/relic_fpx_cyc.c @@ -937,7 +937,7 @@ void fp12_exp_cyc_sim(fp12_t e, const fp12_t a, const bn_t b, const fp12_t c, } bn_rec_frb(_d, 4, _d[0], x, n, ep_curve_is_pairf() == EP_BN); - if (ep_curve_is_pairf() && ep_param_embed() == 12) { + if (ep_curve_is_pairf() && ep_curve_embed() == 12) { for (i = 0; i < 4; i++) { fp12_frb(t[i], a, i); fp12_frb(u[i], c, i); diff --git a/src/pc/relic_pc_exp.c b/src/pc/relic_pc_exp.c index 271bb0d29..745c1c1f0 100644 --- a/src/pc/relic_pc_exp.c +++ b/src/pc/relic_pc_exp.c @@ -254,6 +254,138 @@ void gt_exp_imp(gt_t c, const gt_t a, const bn_t b, size_t f) { } } +/** + * Size of a precomputation table using the double-table comb method. + */ +#define RLC_GT_TABLE (1 << (RLC_WIDTH - 2)) + +/** + * Exponentiates an element from G_T in constant time. + * + * @param[out] c - the result. + * @param[in] a - the element to exponentiate. + * @param[in] b - the exponent. + * @param[in] f - the maximum Frobenius power. + */ +void gt_exp_gls_imp(gt_t c, const gt_t a, const bn_t b, size_t f) { + int8_t *naf = RLC_ALLOCA(int8_t, f * (RLC_FP_BITS + 1)); + int8_t n0, *s = RLC_ALLOCA(int8_t, f); + gt_t q, *t = RLC_ALLOCA(gt_t, f * RLC_GT_TABLE); + bn_t n, u, *_b = RLC_ALLOCA(bn_t, f); + size_t l, *_l = RLC_ALLOCA(size_t, f); + + if (naf == NULL || t == NULL || _b == NULL || _l == NULL) { + RLC_THROW(ERR_NO_MEMORY); + return; + } + + if (bn_is_zero(b)) { + RLC_FREE(naf); + RLC_FREE(s); + RLC_FREE(t); + RLC_FREE(_b); + RLC_FREE(_l); + return gt_set_unity(c); + } + + bn_null(n); + bn_null(u); + gt_null(q); + + RLC_TRY { + bn_new(n); + bn_new(u); + gt_new(q); + for (size_t i = 0; i < f; i++) { + bn_null(_b[i]); + bn_new(_b[i]); + for (size_t j = 0; j < RLC_GT_TABLE; j++) { + gt_null(t[i * RLC_GT_TABLE + j]); + gt_new(t[i * RLC_GT_TABLE + j]); + } + } + + fp_prime_get_par(u); + if (ep_curve_is_pairf() == EP_SG18) { + /* Compute base -3*u for the recoding below. */ + bn_dbl(n, u); + bn_add(u, u, n); + bn_neg(u, u); + } + gt_get_ord(n); + bn_abs(_b[0], b); + bn_mod(_b[0], _b[0], n); + if (bn_sign(b) == RLC_NEG) { + bn_neg(_b[0], _b[0]); + } + bn_rec_frb(_b, f, _b[0], u, n, ep_curve_is_pairf() == EP_BN); + + l = 0; + gt_copy(t[0], a); + for (size_t i = 0; i < f; i++) { + s[i] = bn_sign(_b[i]); + bn_abs(_b[i], _b[i]); + + _l[i] = RLC_FP_BITS + 1; + bn_rec_naf(naf + i * (RLC_FP_BITS + 1), &_l[i], _b[i], RLC_WIDTH); + l = RLC_MAX(l, _l[i]); + /* Apply Frobenius before flipping sign to build table. */ + if (i > 0) { + gt_gls(t[i * RLC_GT_TABLE], t[(i - 1) * RLC_GT_TABLE]); + } + } + + for (size_t i = 0; i < f; i++) { + gt_inv(q, t[i * RLC_GT_TABLE]); + gt_copy_sec(q, t[i * RLC_GT_TABLE], s[i] == RLC_POS); + if (RLC_WIDTH > 2) { + gt_sqr(t[i * RLC_GT_TABLE], q); + gt_mul(t[i * RLC_GT_TABLE + 1], t[i * RLC_GT_TABLE], q); + for (size_t j = 2; j < RLC_GT_TABLE; j++) { + gt_mul(t[i * RLC_GT_TABLE + j], t[i * RLC_GT_TABLE + j - 1], + t[i * (RLC_GT_TABLE)]); + } + } + gt_copy(t[i * RLC_GT_TABLE], q); + } + + gt_set_unity(c); + for (int j = l - 1; j >= 0; j--) { + gt_sqr(c, c); + + for (size_t i = 0; i < f; i++) { + n0 = naf[i * (RLC_FP_BITS + 1) + j]; + if (n0 > 0) { + gt_mul(c, c, t[i * RLC_GT_TABLE + n0 / 2]); + } + if (n0 < 0) { + gt_inv(q, t[i * RLC_GT_TABLE - n0 / 2]); + gt_mul(c, c, q); + } + } + } + } + RLC_CATCH_ANY { + RLC_THROW(ERR_CAUGHT); + } + RLC_FINALLY { + bn_free(n); + bn_free(u); + gt_free(q); + for (size_t i = 0; i < f; i++) { + bn_free(_b[i]); + for (size_t j = 0; j < RLC_GT_TABLE; j++) { + gt_free(t[i * RLC_GT_TABLE + j]); + } + } + RLC_FREE(naf); + RLC_FREE(s); + RLC_FREE(t); + RLC_FREE(_b); + RLC_FREE(_l); + } +} + /*============================================================================*/ /* Public definitions */ /*============================================================================*/ @@ -393,7 +525,7 @@ void gt_exp_sec(gt_t c, const gt_t a, const bn_t b) { size_t f = 0; - switch (ep_param_embed()) { + switch (ep_curve_embed()) { case 1: case 2: case 8: diff --git a/test/test_fpx.c b/test/test_fpx.c index 3c0da4afb..727aea007 100644 --- a/test/test_fpx.c +++ b/test/test_fpx.c @@ -4677,7 +4677,7 @@ static int cyclotomic12(void) { TEST_ASSERT(fp12_cmp(b, c) == RLC_EQ, end); } TEST_END; - if (ep_curve_is_pairf() && ep_param_embed() == 12) { + if (ep_curve_is_pairf() && ep_curve_embed() == 12) { TEST_CASE("cyclotomic exponentiation in subgroup is correct") { fp12_rand(a); pp_exp_k12(a, a); @@ -5407,7 +5407,7 @@ static int cyclotomic16(void) { TEST_ASSERT(fp16_cmp(b, c) == RLC_EQ, end); } TEST_END; - if (ep_curve_is_pairf() && ep_param_embed() == 16) { + if (ep_curve_is_pairf() && ep_curve_embed() == 16) { TEST_CASE("cyclotomic exponentiation in subgroup is correct") { fp16_rand(a); pp_exp_k16(a, a); @@ -6177,7 +6177,7 @@ static int cyclotomic18(void) { TEST_ASSERT(fp18_cmp(b, c) == RLC_EQ, end); } TEST_END; - if (ep_curve_is_pairf() && ep_param_embed() == 18) { + if (ep_curve_is_pairf() && ep_curve_embed() == 18) { TEST_CASE("cyclotomic exponentiation in subgroup is correct") { fp18_rand(a); pp_exp_k18(a, a); @@ -6953,7 +6953,7 @@ static int cyclotomic24(void) { TEST_ASSERT(fp24_cmp(b, c) == RLC_EQ, end); } TEST_END; - if (ep_curve_is_pairf() && ep_param_embed() == 24) { + if (ep_curve_is_pairf() && ep_curve_embed() == 24) { TEST_CASE("cyclotomic exponentiation in subgroup is correct") { fp24_rand(a); pp_exp_k24(a, a); @@ -7768,7 +7768,7 @@ static int cyclotomic48(void) { TEST_ASSERT(fp48_cmp(b, c) == RLC_EQ, end); } TEST_END; - if (ep_curve_is_pairf() && ep_param_embed() == 48) { + if (ep_curve_is_pairf() && ep_curve_embed() == 48) { TEST_CASE("cyclotomic exponentiation in subgroup is correct") { fp48_rand(a); pp_exp_k48(a, a); @@ -8781,7 +8781,7 @@ int main(void) { } /* Only execute these if there is an assigned cubic non-residue. */ - if (fp_prime_get_cnr() && (ep_param_embed() >= 3)) { + if (fp_prime_get_cnr() && (ep_curve_embed() >= 3)) { util_print("\n-- Cubic extension: %d as CNR\n", fp_prime_get_cnr()); util_banner("Utilities:", 1); @@ -8844,7 +8844,7 @@ int main(void) { } /* Fp^4 is defined as a quadratic extension of Fp^2. */ - if (fp_prime_get_qnr() && (ep_param_embed() >= 4)) { + if (fp_prime_get_qnr() && (ep_curve_embed() >= 4)) { util_print("\n-- Quartic extension: (i + %d) as QNR\n", fp2_field_get_qnr()); util_banner("Utilities:", 1); @@ -8908,7 +8908,7 @@ int main(void) { } /* Fp^6 is defined as a cubic extension of Fp^2. */ - if (fp_prime_get_qnr() && fp_prime_get_cnr() && (ep_param_embed() >= 6)) { + if (fp_prime_get_qnr() && fp_prime_get_cnr() && (ep_curve_embed() >= 6)) { util_print("\n-- Sextic extension: (i + %d) as CNR\n", fp2_field_get_qnr()); util_banner("Utilities:", 1); @@ -8961,7 +8961,7 @@ int main(void) { } } - if (fp_prime_get_qnr() && (ep_param_embed() >= 8)) { + if (fp_prime_get_qnr() && (ep_curve_embed() >= 8)) { util_banner("Octic extension: (j) as CNR", 0); util_banner("Utilities:", 1); @@ -9024,7 +9024,7 @@ int main(void) { } /* Only execute these if there is an assigned cubic non-residue. */ - if (fp_prime_get_cnr() && (ep_param_embed() >= 9)) { + if (fp_prime_get_cnr() && (ep_curve_embed() >= 9)) { util_print("\n-- Nonic extension: (j + %d) as CNR\n", fp3_field_get_cnr()); util_banner("Utilities:", 1); @@ -9078,7 +9078,7 @@ int main(void) { } if (fp_prime_get_qnr() && fp_prime_get_cnr() && - (ep_param_embed() >= 12) && (ep_param_embed() != 16)) { + (ep_curve_embed() >= 12) && (ep_curve_embed() != 16)) { util_banner("Dodecic extension:", 0); util_banner("Utilities:", 1); @@ -9135,7 +9135,7 @@ int main(void) { } } - if (fp_prime_get_qnr() && (ep_param_embed() >= 16)) { + if (fp_prime_get_qnr() && (ep_curve_embed() >= 16)) { util_banner("Sextadecic extension:", 0); util_banner("Utilities:", 1); @@ -9197,7 +9197,7 @@ int main(void) { } } - if (fp_prime_get_cnr() && (ep_param_embed() >= 18)) { + if (fp_prime_get_cnr() && (ep_curve_embed() >= 18)) { util_banner("Octdecic extension:", 0); util_banner("Utilities:", 1); @@ -9254,7 +9254,7 @@ int main(void) { } } - if (fp_prime_get_qnr() && (ep_param_embed() >= 24)) { + if (fp_prime_get_qnr() && (ep_curve_embed() >= 24)) { util_banner("Extension of degree 24:", 0); util_banner("Utilities:", 1); @@ -9311,7 +9311,7 @@ int main(void) { } } - if (fp_prime_get_qnr() && (ep_param_embed() >= 48)) { + if (fp_prime_get_qnr() && (ep_curve_embed() >= 48)) { util_banner("Extension of degree 48:", 0); util_banner("Utilities:", 1); @@ -9368,7 +9368,7 @@ int main(void) { } } - if (fp_prime_get_cnr() && (ep_param_embed() == 54)) { + if (fp_prime_get_cnr() && (ep_curve_embed() == 54)) { util_banner("Extension of degree 54:", 0); util_banner("Utilities:", 1); diff --git a/test/test_pp.c b/test/test_pp.c index 6af9e2bab..56e2cd695 100644 --- a/test/test_pp.c +++ b/test/test_pp.c @@ -3927,7 +3927,7 @@ int main(void) { util_banner("Arithmetic", 1); - if (ep_param_embed() == 1) { + if (ep_curve_embed() == 1) { if (doubling1() != RLC_OK) { core_clean(); return 1; @@ -3944,7 +3944,7 @@ int main(void) { } } - if (ep_param_embed() == 2) { + if (ep_curve_embed() == 2) { if (doubling2() != RLC_OK) { core_clean(); return 1; @@ -3961,7 +3961,7 @@ int main(void) { } } - if (ep_param_embed() == 8) { + if (ep_curve_embed() == 8) { if (doubling8() != RLC_OK) { core_clean(); return 1; @@ -3978,7 +3978,7 @@ int main(void) { } } - if (ep_param_embed() == 12) { + if (ep_curve_embed() == 12) { if (doubling12() != RLC_OK) { core_clean(); return 1; @@ -3995,7 +3995,7 @@ int main(void) { } } - if (ep_param_embed() == 16) { + if (ep_curve_embed() == 16) { if (doubling16() != RLC_OK) { core_clean(); return 1; @@ -4012,7 +4012,7 @@ int main(void) { } } - if (ep_param_embed() == 18) { + if (ep_curve_embed() == 18) { if (doubling18() != RLC_OK) { core_clean(); return 1; @@ -4029,7 +4029,7 @@ int main(void) { } } - if (ep_param_embed() == 24) { + if (ep_curve_embed() == 24) { if (doubling24() != RLC_OK) { core_clean(); return 1; @@ -4046,7 +4046,7 @@ int main(void) { } } - if (ep_param_embed() == 48) { + if (ep_curve_embed() == 48) { if (doubling48() != RLC_OK) { core_clean(); return 1; @@ -4063,7 +4063,7 @@ int main(void) { } } - if (ep_param_embed() == 54) { + if (ep_curve_embed() == 54) { if (doubling54() != RLC_OK) { core_clean(); return 1; From 911ed2af7d1ac78a3979ff1e7246a2fcf1ef5685 Mon Sep 17 00:00:00 2001 From: "Diego F. Aranha" Date: Mon, 1 Apr 2024 13:29:24 +0200 Subject: [PATCH 37/37] Remove redundant code by moving function from FPX to GT. --- bench/bench_fpx.c | 50 ----- include/relic_ep.h | 16 +- src/ep/relic_ep_curve.c | 26 ++- src/fpx/relic_fpx_cyc.c | 462 ---------------------------------------- src/pc/relic_pc_exp.c | 28 +-- test/test_fpx.c | 92 -------- test/test_pc.c | 14 +- 7 files changed, 50 insertions(+), 638 deletions(-) diff --git a/bench/bench_fpx.c b/bench/bench_fpx.c index 215f80361..1a89a6333 100644 --- a/bench/bench_fpx.c +++ b/bench/bench_fpx.c @@ -2060,16 +2060,6 @@ static void arith12(void) { } BENCH_END; - if (ep_curve_is_pairf() && ep_curve_embed() == 12) { - BENCH_RUN("fp12_exp_cyc (gls)") { - fp12_rand(a); - fp12_conv_cyc(a, a); - bn_rand(e, RLC_POS, RLC_FP_BITS); - BENCH_ADD(fp12_exp_cyc_gls(c, a, e)); - } - BENCH_END; - } - BENCH_RUN("fp12_exp_cyc (param or sparse)") { fp12_rand(a); fp12_conv_cyc(a, a); @@ -2347,16 +2337,6 @@ static void arith16(void) { } BENCH_END; - if (ep_curve_is_pairf() && ep_curve_embed() == 16) { - BENCH_RUN("fp16_exp_cyc (gls)") { - fp16_rand(a); - fp16_conv_cyc(a, a); - bn_rand(e, RLC_POS, RLC_FP_BITS); - BENCH_ADD(fp16_exp_cyc_gls(c, a, e)); - } - BENCH_END; - } - BENCH_RUN("fp16_exp_cyc (param or sparse)") { fp16_rand(a); fp16_conv_cyc(a, a); @@ -2717,16 +2697,6 @@ static void arith18(void) { } BENCH_END; - if (ep_curve_is_pairf() && ep_curve_embed() == 18) { - BENCH_RUN("fp18_exp_cyc (gls)") { - fp18_rand(a); - fp18_conv_cyc(a, a); - bn_rand(e, RLC_POS, RLC_FP_BITS); - BENCH_ADD(fp18_exp_cyc_gls(c, a, e)); - } - BENCH_END; - } - BENCH_RUN("fp18_exp_cyc (param or sparse)") { fp18_rand(a); fp18_conv_cyc(a, a); @@ -3050,16 +3020,6 @@ static void arith24(void) { } BENCH_END; - if (ep_curve_is_pairf() && ep_curve_embed() == 24) { - BENCH_RUN("fp24_exp_cyc (gls)") { - fp24_rand(a); - fp24_conv_cyc(a, a); - bn_rand(e, RLC_POS, RLC_FP_BITS); - BENCH_ADD(fp24_exp_cyc_gls(c, a, e)); - } - BENCH_END; - } - BENCH_RUN("fp24_exp_cyc (param or sparse)") { fp24_rand(a); fp24_conv_cyc(a, a); @@ -3428,16 +3388,6 @@ static void arith48(void) { } BENCH_END; - if (ep_curve_is_pairf() && ep_curve_embed() == 48) { - BENCH_RUN("fp48_exp_cyc (gls)") { - fp48_rand(a); - fp48_conv_cyc(a, a); - bn_rand(e, RLC_POS, RLC_FP_BITS); - BENCH_ADD(fp48_exp_cyc_gls(c, a, e)); - } - BENCH_END; - } - BENCH_RUN("fp48_exp_cyc (param or sparse)") { fp48_rand(a); fp48_conv_cyc(a, a); diff --git a/include/relic_ep.h b/include/relic_ep.h index 9e9a6efc5..6daa5b96d 100644 --- a/include/relic_ep.h +++ b/include/relic_ep.h @@ -679,6 +679,17 @@ void ep_curve_set_super(const fp_t a, const fp_t b, const ep_t g, const bn_t r, void ep_curve_set_endom(const fp_t a, const fp_t b, const ep_t g, const bn_t r, const bn_t h, const fp_t beta, const bn_t l, int ctmap); +/** + * Returns the embedding degree of the currently configured elliptic curve. + */ +int ep_curve_embed(void); + +/** + * Returns the dimension of Frobenius expansions of the currently configured + * elliptic curve. + */ +int ep_curve_frdim(void); + /** * Configures a prime elliptic curve by its parameter identifier. * @@ -741,11 +752,6 @@ void ep_param_print(void); */ int ep_param_level(void); -/** - * Returns the embedding degree of the currently configured elliptic curve. - */ -int ep_curve_embed(void); - /** * Tests if a point on a prime elliptic curve is at the infinity. * diff --git a/src/ep/relic_ep_curve.c b/src/ep/relic_ep_curve.c index fd5769ab6..707a7cfdd 100644 --- a/src/ep/relic_ep_curve.c +++ b/src/ep/relic_ep_curve.c @@ -522,7 +522,6 @@ void ep_curve_set_endom(const fp_t a, const fp_t b, const ep_t g, const bn_t r, #endif - int ep_curve_embed(void) { switch (core_get()->ep_is_pairf) { case EP_K1: @@ -550,4 +549,29 @@ int ep_curve_embed(void) { return 54; } return 0; +} + +int ep_curve_frdim(void) { + size_t f = 0; + + switch (ep_curve_embed()) { + case 1: + case 2: + case 8: + return 1; + break; + case 12: + return 4; + break; + case 18: + return 6; + break; + case 16: + case 24: + return 8; + break; + case 48: + return 16; + break; + } } \ No newline at end of file diff --git a/src/fpx/relic_fpx_cyc.c b/src/fpx/relic_fpx_cyc.c index ad97c903a..f1b9acbbf 100644 --- a/src/fpx/relic_fpx_cyc.c +++ b/src/fpx/relic_fpx_cyc.c @@ -816,81 +816,6 @@ void fp12_exp_cyc(fp12_t c, const fp12_t a, const bn_t b) { } } -void fp12_exp_cyc_gls(fp12_t c, const fp12_t a, const bn_t b) { - size_t l, _l[4]; - int8_t naf[4][RLC_FP_BITS + 1]; - fp12_t t[4]; - bn_t _b[4], n, u; - - if (bn_is_zero(b)) { - return fp12_set_dig(c, 1); - } - - bn_null(n); - bn_null(u); - - RLC_TRY { - bn_new(n); - bn_new(u); - for (size_t i = 0; i < 4; i++) { - bn_null(_b[i]); - bn_new(_b[i]); - fp12_null(t[i]); - fp12_new(t[i]); - } - - ep_curve_get_ord(n); - fp_prime_get_par(u); - bn_abs(_b[0], b); - bn_mod(_b[0], _b[0], n); - if (bn_sign(b) == RLC_NEG) { - bn_neg(_b[0], _b[0]); - } - bn_rec_frb(_b, 4, _b[0], u, n, ep_curve_is_pairf() == EP_BN); - - fp12_copy(t[0], a); - fp12_frb(t[1], t[0], 1); - fp12_frb(t[2], t[1], 1); - fp12_frb(t[3], t[2], 1); - - l = 0; - for (size_t i = 0; i < 4; i++) { - if (bn_sign(_b[i]) == RLC_NEG) { - fp12_inv_cyc(t[i], t[i]); - } - _l[i] = RLC_FP_BITS + 1; - bn_rec_naf(naf[i], &_l[i], _b[i], 2); - l = RLC_MAX(l, _l[i]); - } - - fp12_set_dig(c, 1); - for (int i = l - 1; i >= 0; i--) { - fp12_sqr_cyc(c, c); - for (size_t j = 0; j < 4; j++) { - if (naf[j][i] > 0) { - fp12_mul(c, c, t[j]); - } - if (naf[j][i] < 0) { - fp12_inv_cyc(t[j], t[j]); - fp12_mul(c, c, t[j]); - fp12_inv_cyc(t[j], t[j]); - } - } - } - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - bn_free(n); - bn_free(u); - for (size_t i = 0; i < 4; i++) { - bn_free(_b[i]); - fp12_free(t[i]); - } - } -} - void fp12_exp_cyc_sim(fp12_t e, const fp12_t a, const bn_t b, const fp12_t c, const bn_t d) { int i, j, l; @@ -1129,41 +1054,6 @@ int fp16_test_cyc(const fp16_t a) { return result; } -static void fp16_gls(fp16_t c, const fp16_t a) { - fp16_t b; - - fp16_null(b); - - RLC_TRY { - fp16_new(b); - - switch (ep_curve_is_pairf()) { - case EP_K16: - /* u = (2*p^5 - p) mod r */ - fp16_frb(b, a, 1); - fp16_frb(c, b, 4); - fp16_sqr_cyc(c, c); - fp16_inv_cyc(b, b); - fp16_mul(c, c, b); - break; - case EP_N16: - /* u = -p^5 mod r */ - fp16_frb(c, a, 5); - fp16_inv_cyc(c, c); - break; - case EP_FM16: - fp16_frb(c, a, 1); - break; - } - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp16_free(b); - } -} - void fp16_exp_cyc(fp16_t c, const fp16_t a, const bn_t b) { size_t l, w = RLC_WIDTH; fp16_t r, s, t[1 << (RLC_WIDTH - 2)]; @@ -1231,81 +1121,6 @@ void fp16_exp_cyc(fp16_t c, const fp16_t a, const bn_t b) { } } -void fp16_exp_cyc_gls(fp16_t c, const fp16_t a, const bn_t b) { - size_t i, l, _l[8]; - int8_t naf[8][RLC_FP_BITS + 1]; - fp16_t t[8]; - bn_t _b[8], n, x; - - if (bn_is_zero(b)) { - return fp16_set_dig(c, 1); - } - - bn_null(n); - bn_null(x); - - RLC_TRY { - bn_new(n); - bn_new(x); - for (i = 0; i < 8; i++) { - bn_null(_b[i]); - bn_new(_b[i]); - fp16_null(t[i]); - fp16_new(t[i]); - } - - ep_curve_get_ord(n); - fp_prime_get_par(x); - bn_abs(_b[0], b); - bn_mod(_b[0], _b[0], n); - if (bn_sign(b) == RLC_NEG) { - bn_neg(_b[0], _b[0]); - } - bn_rec_frb(_b, 8, _b[0], x, n, ep_curve_is_pairf() == EP_BN); - - fp16_copy(t[0], a); - for (int i = 1; i < 8; i++) { - fp16_gls(t[i], t[i - 1]); - } - - l = 0; - for (size_t i = 0; i < 8; i++) { - if (bn_sign(_b[i]) == RLC_NEG) { - fp16_inv_cyc(t[i], t[i]); - } - _l[i] = RLC_FP_BITS + 1; - bn_rec_naf(naf[i], &_l[i], _b[i], 2); - l = RLC_MAX(l, _l[i]); - } - - fp16_set_dig(c, 1); - for (int i = l - 1; i >= 0; i--) { - fp16_sqr_cyc(c, c); - for (size_t j = 0; j < 8; j++) { - if (naf[j][i] > 0) { - fp16_mul(c, c, t[j]); - } - if (naf[j][i] < 0) { - fp16_inv_cyc(t[j], t[j]); - fp16_mul(c, c, t[j]); - fp16_inv_cyc(t[j], t[j]); - } - } - } - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - bn_free(n); - bn_free(x); - for (size_t i = 0; i < 8; i++) { - bn_free(_b[i]); - fp16_free(t[i]); - } - } -} - void fp16_exp_cyc_sim(fp16_t e, const fp16_t a, const bn_t b, const fp16_t c, const bn_t d) { int n0, n1; @@ -1626,50 +1441,6 @@ void fp18_back_cyc_sim(fp18_t c[], const fp18_t a[], int n) { } } -static void fp18_gls(fp18_t c, const fp18_t a) { - fp18_t b; - - fp18_null(b); - - RLC_TRY { - fp18_new(b); - - switch (ep_curve_is_pairf()) { - case EP_SG18: - /* -3*u = (2*p^2 - p^5) mod r */ - fp18_frb(b, a, 5); - fp18_inv_cyc(b, b); - fp18_frb(c, a, 2); - fp18_sqr_cyc(c, c); - fp18_mul(c, c, b); - break; - case EP_K18: - /* For KSS18, we have that x = p^4 - 3*p = (p^3 - 3)p mod n. */ - fp18_sqr_cyc(b, a); - fp18_mul(b, b, a); - fp18_frb(c, a, 3); - fp18_inv_cyc(b, b); - fp18_mul(c, c, b); - fp18_frb(c, c, 1); - break; - case EP_FM18: - /* For FM18, we have that u = (p^4-p) mod r. */ - fp18_frb(b, a, 3); - fp18_inv_cyc(b, b); - fp18_mul(c, a, b); - fp18_frb(c, c, 1); - fp18_inv_cyc(c, c); - break; - } - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - fp18_free(b); - } -} - void fp18_exp_cyc(fp18_t c, const fp18_t a, const bn_t b) { size_t l, w = bn_ham(b); @@ -1801,89 +1572,6 @@ void fp18_exp_cyc(fp18_t c, const fp18_t a, const bn_t b) { } } -void fp18_exp_cyc_gls(fp18_t c, const fp18_t a, const bn_t b) { - size_t l, _l[6]; - int8_t naf[6][RLC_FP_BITS + 1]; - fp18_t t[6]; - bn_t _b[6], n, x; - - if (bn_is_zero(b)) { - return fp18_set_dig(c, 1); - } - - bn_null(n); - bn_null(x); - - RLC_TRY { - bn_new(n); - bn_new(x); - for (size_t i = 0; i < 6; i++) { - bn_null(_b[i]); - bn_new(_b[i]); - fp18_null(t[i]); - fp18_new(t[i]); - } - - fp_prime_get_par(x); - if (ep_curve_is_pairf() == EP_SG18) { - /* Compute base -3*u for the recoding below. */ - bn_dbl(n, x); - bn_add(x, x, n); - bn_neg(x, x); - } - ep_curve_get_ord(n); - bn_abs(_b[0], b); - bn_mod(_b[0], _b[0], n); - if (bn_sign(b) == RLC_NEG) { - bn_neg(_b[0], _b[0]); - } - bn_rec_frb(_b, 6, _b[0], x, n, ep_curve_is_pairf() == EP_BN); - - l = 0; - fp18_copy(t[0], a); - for (size_t i = 0; i < 6; i++) { - _l[i] = RLC_FP_BITS + 1; - bn_rec_naf(naf[i], &_l[i], _b[i], 2); - l = RLC_MAX(l, _l[i]); - if (i > 0) { - fp18_gls(t[i], t[i - 1]); - } - } - - for (size_t i = 0; i < 6; i++) { - if (bn_sign(_b[i]) == RLC_NEG) { - fp18_inv_cyc(t[i], t[i]); - } - } - - fp18_set_dig(c, 1); - for (int i = l - 1; i >= 0; i--) { - fp18_sqr_cyc(c, c); - for (int j = 0; j < 6; j++) { - if (naf[j][i] > 0) { - fp18_mul(c, c, t[j]); - } - if (naf[j][i] < 0) { - fp18_inv_cyc(t[j], t[j]); - fp18_mul(c, c, t[j]); - fp18_inv_cyc(t[j], t[j]); - } - } - } - } - RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - bn_free(n); - bn_free(x); - for (size_t i = 0; i < 6; i++) { - bn_free(_b[i]); - fp18_free(t[i]); - } - } -} - void fp18_exp_cyc_sim(fp18_t e, const fp18_t a, const bn_t b, const fp18_t c, const bn_t d) { int i, n0, n1; @@ -2414,81 +2102,6 @@ void fp24_exp_cyc(fp24_t c, const fp24_t a, const bn_t b) { } } -void fp24_exp_cyc_gls(fp24_t c, const fp24_t a, const bn_t b) { - size_t l, _l[8]; - int8_t naf[8][RLC_FP_BITS + 1]; - fp24_t t[8]; - bn_t _b[8], n, x; - - if (bn_is_zero(b)) { - fp24_set_dig(c, 1); - return; - } - - bn_null(n); - bn_null(x); - - RLC_TRY { - bn_new(n); - bn_new(x); - for (int i = 0; i < 8; i++) { - bn_null(_b[i]); - bn_new(_b[i]); - fp24_null(t[i]); - fp24_new(t[i]); - } - - ep_curve_get_ord(n); - fp_prime_get_par(x); - bn_abs(_b[0], b); - bn_mod(_b[0], _b[0], n); - if (bn_sign(b) == RLC_NEG) { - bn_neg(_b[0], _b[0]); - } - bn_rec_frb(_b, 8, _b[0], x, n, ep_curve_is_pairf() == EP_BN); - - fp24_copy(t[0], a); - for (size_t i = 1; i < 8; i++) { - fp24_frb(t[i], t[i - 1], 1); - } - - l = 0; - for (size_t i = 0; i < 8; i++) { - if (bn_sign(_b[i]) == RLC_NEG) { - fp24_inv_cyc(t[i], t[i]); - } - _l[i] = RLC_FP_BITS + 1; - bn_rec_naf(naf[i], &_l[i], _b[i], 2); - l = RLC_MAX(l, _l[i]); - } - - fp24_set_dig(c, 1); - for (int i = l - 1; i >= 0; i--) { - fp24_sqr_cyc(c, c); - for (size_t j = 0; j < 8; j++) { - if (naf[j][i] > 0) { - fp24_mul(c, c, t[j]); - } - if (naf[j][i] < 0) { - fp24_inv_cyc(t[j], t[j]); - fp24_mul(c, c, t[j]); - fp24_inv_cyc(t[j], t[j]); - } - } - } - } RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - bn_free(n); - bn_free(x); - for (size_t i = 0; i < 8; i++) { - bn_free(_b[i]); - fp24_free(t[i]); - } - } -} - void fp24_exp_cyc_sim(fp24_t e, const fp24_t a, const bn_t b, const fp24_t c, const bn_t d) { int n0, n1; @@ -3020,81 +2633,6 @@ void fp48_exp_cyc(fp48_t c, const fp48_t a, const bn_t b) { } } -void fp48_exp_cyc_gls(fp48_t c, const fp48_t a, const bn_t b) { - size_t l, _l[16]; - int8_t naf[16][RLC_FP_BITS + 1]; - fp48_t t[16]; - bn_t _b[16], n, x; - - if (bn_is_zero(b)) { - fp48_set_dig(c, 1); - return; - } - - bn_null(n); - bn_null(x); - - RLC_TRY { - bn_new(n); - bn_new(x); - for (int i = 0; i < 8; i++) { - bn_null(_b[i]); - bn_new(_b[i]); - fp48_null(t[i]); - fp48_new(t[i]); - } - - ep_curve_get_ord(n); - fp_prime_get_par(x); - bn_abs(_b[0], b); - bn_mod(_b[0], _b[0], n); - if (bn_sign(b) == RLC_NEG) { - bn_neg(_b[0], _b[0]); - } - bn_rec_frb(_b, 16, _b[0], x, n, ep_curve_is_pairf() == EP_BN); - - fp48_copy(t[0], a); - for (size_t i = 1; i < 16; i++) { - fp48_frb(t[i], t[i - 1], 1); - } - - l = 0; - for (size_t i = 0; i < 16; i++) { - if (bn_sign(_b[i]) == RLC_NEG) { - fp48_inv_cyc(t[i], t[i]); - } - _l[i] = RLC_FP_BITS + 1; - bn_rec_naf(naf[i], &_l[i], _b[i], 2); - l = RLC_MAX(l, _l[i]); - } - - fp48_set_dig(c, 1); - for (int i = l - 1; i >= 0; i--) { - fp48_sqr_cyc(c, c); - for (size_t j = 0; j < 16; j++) { - if (naf[j][i] > 0) { - fp48_mul(c, c, t[j]); - } - if (naf[j][i] < 0) { - fp48_inv_cyc(t[j], t[j]); - fp48_mul(c, c, t[j]); - fp48_inv_cyc(t[j], t[j]); - } - } - } - } RLC_CATCH_ANY { - RLC_THROW(ERR_CAUGHT); - } - RLC_FINALLY { - bn_free(n); - bn_free(x); - for (size_t i = 0; i < 16; i++) { - bn_free(_b[i]); - fp48_free(t[i]); - } - } -} - void fp48_exp_cyc_sim(fp48_t e, const fp48_t a, const bn_t b, const fp48_t c, const bn_t d) { int n0, n1; diff --git a/src/pc/relic_pc_exp.c b/src/pc/relic_pc_exp.c index 745c1c1f0..87b8cdf8c 100644 --- a/src/pc/relic_pc_exp.c +++ b/src/pc/relic_pc_exp.c @@ -506,7 +506,7 @@ void gt_exp(gt_t c, const gt_t a, const bn_t b) { #if FP_PRIME == 1536 || FP_PRIME == 544 RLC_CAT(RLC_GT_LOWER, exp_cyc)(c, a, b); #elif FP_PRIME < 1536 - RLC_CAT(RLC_GT_LOWER, exp_cyc_gls)(c, a, b); + gt_exp_gls_imp(c, a, b, ep_curve_frdim()); #else RLC_CAT(RLC_GT_LOWER, exp)(c, a, b); #endif @@ -522,31 +522,7 @@ void gt_exp_sec(gt_t c, const gt_t a, const bn_t b) { } #if FP_PRIME <= 1536 - - size_t f = 0; - - switch (ep_curve_embed()) { - case 1: - case 2: - case 8: - f = 1; - break; - case 12: - f = 4; - break; - case 18: - f = 6; - break; - case 16: - case 24: - f = 8; - break; - case 48: - f = 16; - break; - } - - gt_exp_imp(c, a, b, f); + gt_exp_imp(c, a, b, ep_curve_frdim()); #else RLC_CAT(RLC_GT_LOWER, exp_monty)(c, a, b); #endif diff --git a/test/test_fpx.c b/test/test_fpx.c index 727aea007..45da38fb6 100644 --- a/test/test_fpx.c +++ b/test/test_fpx.c @@ -4677,29 +4677,6 @@ static int cyclotomic12(void) { TEST_ASSERT(fp12_cmp(b, c) == RLC_EQ, end); } TEST_END; - if (ep_curve_is_pairf() && ep_curve_embed() == 12) { - TEST_CASE("cyclotomic exponentiation in subgroup is correct") { - fp12_rand(a); - pp_exp_k12(a, a); - bn_zero(f); - fp12_exp_cyc_gls(c, a, f); - TEST_ASSERT(fp12_cmp_dig(c, 1) == RLC_EQ, end); - bn_set_dig(f, 1); - fp12_exp_cyc_gls(c, a, f); - TEST_ASSERT(fp12_cmp(c, a) == RLC_EQ, end); - bn_rand(f, RLC_POS, RLC_FP_BITS); - fp12_exp(b, a, f); - fp12_exp_cyc_gls(c, a, f); - TEST_ASSERT(fp12_cmp(b, c) == RLC_EQ, end); - bn_rand(f, RLC_POS, RLC_FP_BITS); - fp12_exp_cyc_gls(b, a, f); - bn_neg(f, f); - fp12_exp_cyc_gls(c, a, f); - fp12_inv_cyc(c, c); - TEST_ASSERT(fp12_cmp(b, c) == RLC_EQ, end); - } TEST_END; - } - TEST_CASE("sparse cyclotomic exponentiation is correct") { int g[3] = {0, 0, RLC_FP_BITS - 1}; do { @@ -6177,29 +6154,6 @@ static int cyclotomic18(void) { TEST_ASSERT(fp18_cmp(b, c) == RLC_EQ, end); } TEST_END; - if (ep_curve_is_pairf() && ep_curve_embed() == 18) { - TEST_CASE("cyclotomic exponentiation in subgroup is correct") { - fp18_rand(a); - pp_exp_k18(a, a); - bn_zero(f); - fp18_exp_cyc_gls(c, a, f); - TEST_ASSERT(fp18_cmp_dig(c, 1) == RLC_EQ, end); - bn_set_dig(f, 1); - fp18_exp_cyc_gls(c, a, f); - TEST_ASSERT(fp18_cmp(c, a) == RLC_EQ, end); - bn_rand(f, RLC_POS, RLC_FP_BITS); - fp18_exp(b, a, f); - fp18_exp_cyc_gls(c, a, f); - TEST_ASSERT(fp18_cmp(b, c) == RLC_EQ, end); - bn_rand(f, RLC_POS, RLC_FP_BITS); - fp18_exp_cyc_gls(b, a, f); - bn_neg(f, f); - fp18_exp_cyc_gls(c, a, f); - fp18_inv_cyc(c, c); - TEST_ASSERT(fp18_cmp(b, c) == RLC_EQ, end); - } TEST_END; - } - TEST_CASE("sparse cyclotomic exponentiation is correct") { int g[3] = {0, 0, RLC_FP_BITS - 1}; do { @@ -6953,29 +6907,6 @@ static int cyclotomic24(void) { TEST_ASSERT(fp24_cmp(b, c) == RLC_EQ, end); } TEST_END; - if (ep_curve_is_pairf() && ep_curve_embed() == 24) { - TEST_CASE("cyclotomic exponentiation in subgroup is correct") { - fp24_rand(a); - pp_exp_k24(a, a); - bn_zero(f); - fp24_exp_cyc_gls(c, a, f); - TEST_ASSERT(fp24_cmp_dig(c, 1) == RLC_EQ, end); - bn_set_dig(f, 1); - fp24_exp_cyc_gls(c, a, f); - TEST_ASSERT(fp24_cmp(c, a) == RLC_EQ, end); - bn_rand(f, RLC_POS, RLC_DIG); - fp24_exp(b, a, f); - fp24_exp_cyc_gls(c, a, f); - TEST_ASSERT(fp24_cmp(b, c) == RLC_EQ, end); - bn_rand(f, RLC_POS, RLC_FP_BITS); - fp24_exp_cyc_gls(b, a, f); - bn_neg(f, f); - fp24_exp_cyc_gls(c, a, f); - fp24_inv_cyc(c, c); - TEST_ASSERT(fp24_cmp(b, c) == RLC_EQ, end); - } TEST_END; - } - TEST_CASE("sparse cyclotomic exponentiation is correct") { int g[3] = {0, 0, RLC_FP_BITS - 1}; do { @@ -7768,29 +7699,6 @@ static int cyclotomic48(void) { TEST_ASSERT(fp48_cmp(b, c) == RLC_EQ, end); } TEST_END; - if (ep_curve_is_pairf() && ep_curve_embed() == 48) { - TEST_CASE("cyclotomic exponentiation in subgroup is correct") { - fp48_rand(a); - pp_exp_k48(a, a); - bn_zero(f); - fp48_exp_cyc_gls(c, a, f); - TEST_ASSERT(fp48_cmp_dig(c, 1) == RLC_EQ, end); - bn_set_dig(f, 1); - fp48_exp_cyc_gls(c, a, f); - TEST_ASSERT(fp48_cmp(c, a) == RLC_EQ, end); - bn_rand(f, RLC_POS, RLC_DIG); - fp48_exp(b, a, f); - fp48_exp_cyc_gls(c, a, f); - TEST_ASSERT(fp48_cmp(b, c) == RLC_EQ, end); - bn_rand(f, RLC_POS, RLC_FP_BITS); - fp48_exp_cyc_gls(b, a, f); - bn_neg(f, f); - fp48_exp_cyc_gls(c, a, f); - fp48_inv_cyc(c, c); - TEST_ASSERT(fp48_cmp(b, c) == RLC_EQ, end); - } TEST_END; - } - TEST_CASE("sparse cyclotomic exponentiation is correct") { int g[3] = {0, 0, RLC_FP_BITS - 1}; do { diff --git a/test/test_pc.c b/test/test_pc.c index 6e6aaa169..53bfb9e18 100644 --- a/test/test_pc.c +++ b/test/test_pc.c @@ -1488,11 +1488,21 @@ int exponentiation(void) { TEST_CASE("exponentiation is correct") { gt_rand(a); - gt_exp_dig(b, a, 0); + bn_set_dig(d, 0); + gt_exp(b, a, d); TEST_ASSERT(gt_is_unity(b), end); - gt_exp_dig(b, a, 1); + bn_set_dig(d, 1); + gt_exp(b, a, d); TEST_ASSERT(gt_cmp(a, b) == RLC_EQ, end); bn_rand_mod(d, n); + RLC_CAT(RLC_GT_LOWER, exp)(b, a, d); + gt_exp(c, a, d); + TEST_ASSERT(gt_cmp(b, c) == RLC_EQ, end); + bn_rand_mod(d, n); + gt_exp(b, a, d); + gt_exp_sec(c, a, d); + TEST_ASSERT(gt_cmp(b, c) == RLC_EQ, end); + bn_neg(d, d); gt_exp(b, a, d); gt_exp_sec(c, a, d); TEST_ASSERT(gt_cmp(b, c) == RLC_EQ, end);