From 01275dd311736f05aafd83dcab5c295a772c608b Mon Sep 17 00:00:00 2001 From: Yosuke Shimizu Date: Fri, 30 Oct 2026 09:45:34 +0900 Subject: [PATCH] ecc: return infinity for a scalar that is 0 modulo the order - wc_ecc_mulmod_ex() and wc_ecc_mulmod_ex2(), with and without FP_ECC, set R to (0, 0, 1) and return when k is zero. - accel_fp_mul(), accel_fp_mul2add() and normal_ecc_mul2add() set the result to infinity when first is still set after the loop. - accel_fp_mul2add() copies LUT[zB] into R and clears first when the add of LUT[zA] in the same column set first. - tests: add test_wc_ecc_mulmod_zero for k == 0, k == 2n, and ecc_mul2add() with both scalars 0 or 2n and with (n - 2, 3). - os-check: all-public-mp adds -DWOLFSSL_PUBLIC_ECC_ADD_DBL. Issue: F-16895, F-16896, F-16904 --- .github/configs/os-check-linux.json | 4 +- tests/api/test_ecc.c | 158 ++++++++++++++++++++++++++++ tests/api/test_ecc.h | 2 + wolfcrypt/src/ecc.c | 52 ++++++++- 4 files changed, 213 insertions(+), 3 deletions(-) diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index 06c69fe9e9f..61c5c51c6f3 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -11,8 +11,8 @@ {"name": "all-no-client-auth", "minutes": 9.0, "configure": ["--enable-all", "CPPFLAGS=-DWOLFSSL_NO_CLIENT_AUTH"]}, {"name": "all-public-mp", "minutes": 8.0, - "comment": "--enable-all plus WOLFSSL_PUBLIC_MP. The shared-secret-at-infinity regression assertion needs direct mp access to plant an out-of-range scalar, because the import path itself now rejects one; without this the assertion is compiled out of every mainstream build.", - "configure": ["--enable-all", "CPPFLAGS=-DWOLFSSL_PUBLIC_MP"]}, + "comment": "--enable-all plus WOLFSSL_PUBLIC_MP. The shared-secret-at-infinity regression assertion needs direct mp access to plant an out-of-range scalar, because the import path itself now rejects one; without this the assertion is compiled out of every mainstream build. WOLFSSL_PUBLIC_ECC_ADD_DBL exports the ECC point functions for the scalar-multiplication tests in test_ecc.c.", + "configure": ["--enable-all", "CPPFLAGS=-DWOLFSSL_PUBLIC_MP -DWOLFSSL_PUBLIC_ECC_ADD_DBL"]}, {"name": "all-dilithium-cryptocb", "minutes": 8.9, "configure": ["--enable-all", "--enable-dilithium", "--enable-cryptocb", "--enable-cryptocbutils", "--enable-pkcallbacks"]}, diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index 1cde6ede4ae..efad857503f 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -3732,6 +3732,164 @@ int test_wc_ecc_mulmod_unreduced(void) return EXPECT_RESULT(); } /* END test_wc_ecc_mulmod_unreduced */ +#if defined(HAVE_ECC) && defined(WOLFSSL_PUBLIC_MP) && \ + defined(WOLFSSL_PUBLIC_ECC_ADD_DBL) && !defined(WOLFSSL_SP_MATH) && \ + !defined(NO_ECC256) && !defined(NO_ECC_SECP) && \ + !(defined(WOLFSSL_ATECC508A) || defined(WOLFSSL_ATECC608A) || \ + defined(WOLFSSL_MICROCHIP_TA100) || \ + defined(WOLFSSL_VALIDATE_ECC_IMPORT)) && \ + !defined(WOLF_CRYPTO_CB_ONLY_ECC) && !defined(HAVE_SELFTEST) && \ + !defined(WOLFSSL_KCAPI_ECC) && !defined(WOLFSSL_STM32_PKA) && \ + !defined(FREESCALE_LTC_ECC) && \ + (!defined(HAVE_FIPS) || FIPS_VERSION3_GE(7,0,0)) && \ + !defined(WOLFSSL_NO_ECC_SW) +#define TEST_ECC_MULMOD_ZERO +/* Point at infinity is (0, 0, 1). */ +static int test_ecc_point_is_inf(ecc_point* p) +{ + return (p != NULL) && mp_iszero(p->x) && mp_iszero(p->y) && + (mp_cmp_d(p->z, 1) == MP_EQ); +} + +static void test_ecc_fp_reset(void) +{ +#ifdef FP_ECC + wc_ecc_fp_free(); +#endif +} +#endif + +/* + * Testing scalar multiplication with a scalar that is 0 modulo the order. + */ +int test_wc_ecc_mulmod_zero(void) +{ + EXPECT_DECLS; +#ifdef TEST_ECC_MULMOD_ZERO + const ecc_set_type* dp = NULL; + ecc_key base; + ecc_key curve; + ecc_point* g = NULL; + ecc_point* b = NULL; + ecc_point* r = NULL; + mp_int zero; + mp_int two; + mp_int order; + mp_int order2; + mp_int orderm2; + mp_int three; + int i; + + XMEMSET(&base, 0, sizeof(ecc_key)); + XMEMSET(&curve, 0, sizeof(ecc_key)); + XMEMSET(&zero, 0, sizeof(mp_int)); + XMEMSET(&two, 0, sizeof(mp_int)); + XMEMSET(&order, 0, sizeof(mp_int)); + XMEMSET(&order2, 0, sizeof(mp_int)); + XMEMSET(&orderm2, 0, sizeof(mp_int)); + XMEMSET(&three, 0, sizeof(mp_int)); + + test_ecc_fp_reset(); + + ExpectNotNull(dp = wc_ecc_get_curve_params( + wc_ecc_get_curve_idx(ECC_SECP256R1))); + ExpectIntEQ(wc_ecc_init(&base), 0); + ExpectIntEQ(wc_ecc_init(&curve), 0); + ExpectIntEQ(mp_init_multi(&zero, &two, &order, &order2, &orderm2, + &three), 0); + ExpectIntEQ(mp_set(&two, 2), 0); + ExpectIntEQ(mp_set(&three, 3), 0); + ExpectNotNull(g = wc_ecc_new_point()); + ExpectNotNull(b = wc_ecc_new_point()); + ExpectNotNull(r = wc_ecc_new_point()); + + /* base carries the base point; curve carries a and the prime as x and y, + * as in test_wc_ecc_mulmod(). */ + if (dp != NULL) { + ExpectIntEQ(wc_ecc_import_raw_ex(&base, dp->Gx, dp->Gy, NULL, + ECC_SECP256R1), 0); + ExpectIntEQ(wc_ecc_import_raw_ex(&curve, dp->Af, dp->prime, NULL, + ECC_SECP256R1), 0); + ExpectIntEQ(mp_read_radix(&order, dp->order, MP_RADIX_HEX), 0); + } + /* 2n has one bit more than the prime and is 0 modulo the order. */ + ExpectIntEQ(mp_add(&order, &order, &order2), 0); + ExpectIntEQ(mp_sub_d(&order, 2, &orderm2), 0); + /* Second base point for ecc_mul2add(): 2G. */ + ExpectIntEQ(wc_ecc_mulmod(&two, &base.pubkey, b, curve.pubkey.x, + curve.pubkey.y, 1), 0); + + /* k == 0, with R holding a point beforehand. */ + for (i = 0; i < 2; i++) { + ExpectIntEQ(wc_ecc_copy_point(&base.pubkey, r), 0); + ExpectIntEQ(wc_ecc_mulmod_ex(&zero, &base.pubkey, r, curve.pubkey.x, + curve.pubkey.y, i, NULL), 0); + ExpectTrue(test_ecc_point_is_inf(r)); + + ExpectIntEQ(wc_ecc_copy_point(&base.pubkey, r), 0); + ExpectIntEQ(wc_ecc_mulmod_ex2(&zero, &base.pubkey, r, curve.pubkey.x, + curve.pubkey.y, &order, NULL, i, NULL), 0); + ExpectTrue(test_ecc_point_is_inf(r)); + } + + /* k == 0 with R the same point as G. */ + ExpectIntEQ(wc_ecc_copy_point(&base.pubkey, g), 0); + ExpectIntEQ(wc_ecc_mulmod_ex(&zero, g, g, curve.pubkey.x, curve.pubkey.y, + 1, NULL), 0); + ExpectTrue(test_ecc_point_is_inf(g)); + + /* k == 2n. */ + test_ecc_fp_reset(); + for (i = 0; i < 3; i++) { + ExpectIntEQ(wc_ecc_copy_point(&base.pubkey, r), 0); + ExpectIntEQ(wc_ecc_mulmod_ex(&order2, &base.pubkey, r, curve.pubkey.x, + curve.pubkey.y, 1, NULL), 0); + ExpectTrue(test_ecc_point_is_inf(r)); + } + +#ifdef ECC_SHAMIR + /* kA == kB == 0, and kA == kB == 2n. */ + test_ecc_fp_reset(); + for (i = 0; i < 3; i++) { + ExpectIntEQ(wc_ecc_copy_point(&base.pubkey, r), 0); + ExpectIntEQ(ecc_mul2add(&base.pubkey, &zero, b, &zero, r, + curve.pubkey.x, curve.pubkey.y, NULL), 0); + ExpectTrue(test_ecc_point_is_inf(r)); + } + test_ecc_fp_reset(); + for (i = 0; i < 3; i++) { + ExpectIntEQ(wc_ecc_copy_point(&base.pubkey, r), 0); + ExpectIntEQ(ecc_mul2add(&base.pubkey, &order2, b, &order2, r, + curve.pubkey.x, curve.pubkey.y, NULL), 0); + ExpectTrue(test_ecc_point_is_inf(r)); + } + /* (n - 2)G + 3G = G. With FP_ECC the comb's last column adds kA's part + * to reach infinity, then adds kB's part to that. */ + test_ecc_fp_reset(); + for (i = 0; i < 3; i++) { + ExpectIntEQ(ecc_mul2add(&base.pubkey, &orderm2, &base.pubkey, &three, + r, curve.pubkey.x, curve.pubkey.y, NULL), 0); + ExpectIntEQ(wc_ecc_cmp_point(r, &base.pubkey), MP_EQ); + } +#endif + + wc_ecc_del_point(r); + wc_ecc_del_point(b); + wc_ecc_del_point(g); + mp_free(&three); + mp_free(&orderm2); + mp_free(&order2); + mp_free(&order); + mp_free(&two); + mp_free(&zero); + wc_ecc_free(&curve); + wc_ecc_free(&base); + + test_ecc_fp_reset(); +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_mulmod_zero */ + /* * Testing wc_ecc_is_valid_idx() */ diff --git a/tests/api/test_ecc.h b/tests/api/test_ecc.h index e642751c122..33527118036 100644 --- a/tests/api/test_ecc.h +++ b/tests/api/test_ecc.h @@ -72,6 +72,7 @@ int test_wc_ecc_shared_secret_ssh(void); int test_wc_ecc_verify_hash_ex(void); int test_wc_ecc_mulmod(void); int test_wc_ecc_mulmod_unreduced(void); +int test_wc_ecc_mulmod_zero(void); int test_wc_ecc_is_valid_idx(void); int test_wc_ecc_get_curve_id_from_oid(void); int test_wc_ecc_sig_size_calc(void); @@ -133,6 +134,7 @@ int test_wc_ecc_make_key_id_dev(void); TEST_DECL_GROUP("ecc", test_wc_ecc_verify_hash_ex), \ TEST_DECL_GROUP("ecc", test_wc_ecc_mulmod), \ TEST_DECL_GROUP("ecc", test_wc_ecc_mulmod_unreduced), \ + TEST_DECL_GROUP("ecc", test_wc_ecc_mulmod_zero), \ TEST_DECL_GROUP("ecc", test_wc_ecc_is_valid_idx), \ TEST_DECL_GROUP("ecc", test_wc_ecc_get_curve_id_from_oid), \ TEST_DECL_GROUP("ecc", test_wc_ecc_sig_size_calc), \ diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index c3c51da62b4..4ad87d070cb 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -3880,6 +3880,15 @@ int wc_ecc_mulmod_ex(const mp_int* k, ecc_point *G, ecc_point *R, mp_int* a, goto exit; } + if (mp_iszero(k)) { + err = mp_set(R->x, 0); + if (err == MP_OKAY) + err = mp_set(R->y, 0); + if (err == MP_OKAY) + err = mp_set(R->z, 1); + goto exit; + } + #ifdef WOLFSSL_SMALL_STACK_CACHE key = (ecc_key *)XMALLOC(sizeof(*key), heap, DYNAMIC_TYPE_ECC); if (key == NULL) { @@ -4099,6 +4108,15 @@ int wc_ecc_mulmod_ex2(const mp_int* k, ecc_point* G, ecc_point* R, mp_int* a, return ECC_OUT_OF_RANGE_E; } + if (mp_iszero(k)) { + err = mp_set(R->x, 0); + if (err == MP_OKAY) + err = mp_set(R->y, 0); + if (err == MP_OKAY) + err = mp_set(R->z, 1); + return err; + } + /* init variables */ tG = NULL; XMEMSET(M, 0, sizeof(M)); @@ -9254,6 +9272,11 @@ int ecc_mul2add(ecc_point* A, mp_int* kA, } } } + + /* kA and kB both 0, or the last add gave infinity */ + if ((err == MP_OKAY) && (first == 1)) { + err = ecc_set_point_infinity(C); + } } /* reduce to affine */ @@ -14234,6 +14257,11 @@ static int accel_fp_mul(int idx, const mp_int* k, ecc_point *R, mp_int* a, first = 0; } } + + /* k reduced to 0 mod order, or the last add gave infinity */ + if ((err == MP_OKAY) && (first == 1)) { + err = ecc_set_point_infinity(R); + } } if (err == MP_OKAY) { @@ -14485,13 +14513,22 @@ static int accel_fp_mul2add(int idx1, int idx2, } } - if (zB) { + if (zB && first == 0) { if ((err = ecc_projective_add_point_safe(R, fp_cache[idx2].LUT[zB], R, a, modulus, mp, &first)) != MP_OKAY) { break; } } + else if (zB) { + if ((mp_copy(fp_cache[idx2].LUT[zB]->x, R->x) != MP_OKAY) || + (mp_copy(fp_cache[idx2].LUT[zB]->y, R->y) != MP_OKAY) || + (mp_copy(&fp_cache[idx2].mu, R->z) != MP_OKAY)) { + err = MP_MEM; + break; + } + first = 0; + } } else { if (zA) { if ((mp_copy(fp_cache[idx1].LUT[zA]->x, R->x) != MP_OKAY) || @@ -14519,6 +14556,11 @@ static int accel_fp_mul2add(int idx1, int idx2, } } } + + /* kA and kB both reduced to 0 mod order, or the last add gave infinity */ + if ((err == MP_OKAY) && (first == 1)) { + err = ecc_set_point_infinity(R); + } } done: @@ -14730,6 +14772,10 @@ int wc_ecc_mulmod_ex(const mp_int* k, ecc_point *G, ecc_point *R, mp_int* a, return ECC_OUT_OF_RANGE_E; } + if (mp_iszero(k)) { + return ecc_set_point_infinity(R); + } + #ifdef WOLFSSL_SMALL_STACK if ((mu = (mp_int *)XMALLOC(sizeof(*mu), NULL, DYNAMIC_TYPE_ECC_BUFFER)) == NULL) return MP_MEM; @@ -14890,6 +14936,10 @@ int wc_ecc_mulmod_ex2(const mp_int* k, ecc_point *G, ecc_point *R, mp_int* a, return ECC_OUT_OF_RANGE_E; } + if (mp_iszero(k)) { + return ecc_set_point_infinity(R); + } + #ifdef WOLFSSL_SMALL_STACK if ((mu = (mp_int *)XMALLOC(sizeof(*mu), NULL, DYNAMIC_TYPE_ECC_BUFFER)) == NULL) return MP_MEM;