From 7e60e73082e680a85f269653f0e09b13ff55785d Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Wed, 28 Oct 2026 09:51:36 +0000 Subject: [PATCH 1/3] Use the SP code in wc_ecc_mulmod() with sp-math-all With WOLFSSL_SP_MATH_ALL, wc_ecc_mulmod_ex() always runs the generic point multiplication; only WOLFSSL_SP_MATH builds hand it to the SP code. Key generation, signing, verification and shared secrets reach the SP code by their own paths, but callers of the public point API do not, so for example hostapd/wpa_supplicant's WPA3-SAE got no benefit from SP ECC. Use the SP code when the curve is one it implements and the result is mapped back to affine coordinates. Only the modulus and a are passed in, and other curves of the same size may be compiled in, so the curve is identified by both rather than by its bit length. Add an API test that checks k*G against the public key on every curve compiled in, including the Koblitz and Brainpool curves of the same sizes. --- tests/api/test_ecc.c | 89 ++++++++++++++++++++++++++++++++++++++ tests/api/test_ecc.h | 2 + wolfcrypt/src/ecc.c | 100 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 191 insertions(+) diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index 2bfbc899c47..bc8129cd5e3 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -3601,6 +3601,95 @@ int test_wc_ecc_mulmod(void) return EXPECT_RESULT(); } /* END test_wc_ecc_mulmod */ +/* + * Testing that wc_ecc_mulmod() of the private key and the base point gives + * the public key, on every curve compiled in. This covers curves the SP code + * implements and other curves of the same size, which must not be handed to + * the SP code. + */ +int test_wc_ecc_mulmod_curves(void) +{ + EXPECT_DECLS; +#if defined(HAVE_ECC) && !defined(WC_NO_RNG) && \ + !(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(HAVE_FIPS) && \ + !defined(WOLFSSL_NO_ECC_SW) + static const int curves[] = { + ECC_SECP256R1, ECC_SECP384R1, ECC_SECP521R1, ECC_SECP256K1, + ECC_BRAINPOOLP256R1, ECC_BRAINPOOLP384R1, ECC_BRAINPOOLP512R1 + }; + ecc_key key1; + ecc_key key2; + ecc_key key3; + ecc_key key4; + WC_RNG rng; + int ret; + int i; + int tested = 0; + + XMEMSET(&rng, 0, sizeof(WC_RNG)); + ExpectIntEQ(wc_InitRng(&rng), 0); + + for (i = 0; i < (int)(sizeof(curves) / sizeof(curves[0])); i++) { + const ecc_set_type* dp; + int idx = wc_ecc_get_curve_idx(curves[i]); + + if (idx < 0) { + continue; + } + dp = wc_ecc_get_curve_params(idx); + ExpectNotNull(dp); + if (dp == NULL) { + break; + } + + XMEMSET(&key1, 0, sizeof(ecc_key)); + XMEMSET(&key2, 0, sizeof(ecc_key)); + XMEMSET(&key3, 0, sizeof(ecc_key)); + XMEMSET(&key4, 0, sizeof(ecc_key)); + ExpectIntEQ(wc_ecc_init(&key1), 0); + ExpectIntEQ(wc_ecc_init(&key2), 0); + ExpectIntEQ(wc_ecc_init(&key3), 0); + ExpectIntEQ(wc_ecc_init(&key4), 0); + + ret = wc_ecc_make_key_ex(&rng, dp->size, &key1, curves[i]); + #if defined(WOLFSSL_ASYNC_CRYPT) + ret = wc_AsyncWait(ret, &key1.asyncDev, WC_ASYNC_FLAG_NONE); + #endif + ExpectIntEQ(ret, 0); + + /* As in test_wc_ecc_mulmod(): the base point, the result point, and + * the curve parameter A and the prime as public coordinates. */ + ExpectIntEQ(wc_ecc_import_raw_ex(&key2, dp->Gx, dp->Gy, NULL, + curves[i]), 0); + ExpectIntEQ(wc_ecc_import_raw_ex(&key3, dp->Gx, dp->Gy, NULL, + curves[i]), 0); + ExpectIntEQ(wc_ecc_import_raw_ex(&key4, dp->Af, dp->prime, NULL, + curves[i]), 0); + + ExpectIntEQ(wc_ecc_mulmod(wc_ecc_key_get_priv(&key1), &key2.pubkey, + &key3.pubkey, key4.pubkey.x, key4.pubkey.y, 1), 0); + ExpectIntEQ(wc_ecc_cmp_point(&key3.pubkey, &key1.pubkey), MP_EQ); + tested++; + + wc_ecc_free(&key1); + wc_ecc_free(&key2); + wc_ecc_free(&key3); + wc_ecc_free(&key4); + } + ExpectIntGT(tested, 0); + + DoExpectIntEQ(wc_FreeRng(&rng), 0); +#ifdef FP_ECC + wc_ecc_fp_free(); +#endif +#endif + return EXPECT_RESULT(); +} /* END test_wc_ecc_mulmod_curves */ + /* * Testing wc_ecc_is_valid_idx() */ diff --git a/tests/api/test_ecc.h b/tests/api/test_ecc.h index 60bd0a70afe..1fc8d9b5579 100644 --- a/tests/api/test_ecc.h +++ b/tests/api/test_ecc.h @@ -71,6 +71,7 @@ int test_wc_ecc_pointFns(void); 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_curves(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); @@ -130,6 +131,7 @@ int test_wc_EccPublicKeyDecode_specifiedOverflow(void); TEST_DECL_GROUP("ecc", test_wc_ecc_shared_secret_ssh), \ 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_curves), \ 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 2112b90cc83..0c0cf138f16 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -3804,6 +3804,99 @@ static void ecc_key_tmp_final(ecc_key* key, void* heap) #endif /* WOLFSSL_SMALL_STACK_CACHE */ #endif /* !WOLFSSL_SP_MATH */ +#if defined(WOLFSSL_HAVE_SP_ECC) && !defined(WOLFSSL_SP_MATH) +/* Point multiplication with the SP code for callers of wc_ecc_mulmod() and + * wc_ecc_mulmod_ex() (hostapd's SAE, for example) when the curve is one the SP + * code implements. Only the modulus and a are given, so the curve is + * identified by both, as other curves of the same size may be compiled in. + * Only when mapping back to affine coordinates, as otherwise the result is + * expected in the generic code's representation. + * + * Returns NOT_COMPILED_IN when the SP code does not apply. + */ +static int ecc_sp_mulmod(const mp_int* k, ecc_point* G, ecc_point* R, + mp_int* a, mp_int* modulus, int map, void* heap) +{ + DECL_MP_INT_SIZE_DYN(t, mp_bitsused(modulus), MAX_ECC_BITS_USE); + int bits = mp_count_bits(modulus); + int curveId; + int idx; + int err; + + if (!map || a == NULL || MP_BITS_OVER_MAX(bits, MAX_ECC_BITS_USE)) { + return NOT_COMPILED_IN; + } + switch (bits) { +#ifndef WOLFSSL_SP_NO_256 + case 256: + curveId = ECC_SECP256R1; + break; +#endif +#ifdef WOLFSSL_SP_384 + case 384: + curveId = ECC_SECP384R1; + break; +#endif +#ifdef WOLFSSL_SP_521 + case 521: + curveId = ECC_SECP521R1; + break; +#endif + default: + return NOT_COMPILED_IN; + } + if (mp_count_bits(k) > bits) { + return NOT_COMPILED_IN; + } + idx = wc_ecc_get_curve_idx(curveId); + if (idx < 0) { + return NOT_COMPILED_IN; + } + + NEW_MP_INT_SIZE(t, bits, heap, DYNAMIC_TYPE_ECC); +#ifdef MP_INT_SIZE_CHECK_NULL + if (t == NULL) { + return MEMORY_E; + } +#endif + err = INIT_MP_INT_SIZE(t, bits); + if (err == MP_OKAY) { + err = mp_read_radix(t, ecc_sets[idx].prime, MP_RADIX_HEX); + } + if ((err == MP_OKAY) && (mp_cmp(t, modulus) != MP_EQ)) { + err = NOT_COMPILED_IN; + } + if (err == MP_OKAY) { + err = mp_read_radix(t, ecc_sets[idx].Af, MP_RADIX_HEX); + } + if ((err == MP_OKAY) && (mp_cmp(t, a) != MP_EQ)) { + err = NOT_COMPILED_IN; + } + mp_clear(t); + FREE_MP_INT_SIZE(t, heap, DYNAMIC_TYPE_ECC); + if (err != MP_OKAY) { + return err; + } + + switch (bits) { +#ifndef WOLFSSL_SP_NO_256 + case 256: + return sp_ecc_mulmod_256(k, G, R, map, heap); +#endif +#ifdef WOLFSSL_SP_384 + case 384: + return sp_ecc_mulmod_384(k, G, R, map, heap); +#endif +#ifdef WOLFSSL_SP_521 + case 521: + return sp_ecc_mulmod_521(k, G, R, map, heap); +#endif + default: + return NOT_COMPILED_IN; + } +} +#endif /* WOLFSSL_HAVE_SP_ECC && !WOLFSSL_SP_MATH */ + #if !defined(WOLFSSL_SP_MATH) || !defined(FP_ECC) /** Perform a point multiplication @@ -3851,6 +3944,13 @@ int wc_ecc_mulmod_ex(const mp_int* k, ecc_point *G, ecc_point *R, mp_int* a, goto exit; } +#if defined(WOLFSSL_HAVE_SP_ECC) && !defined(WOLFSSL_SP_MATH) + err = ecc_sp_mulmod(k, G, R, a, modulus, map, heap); + if (err != WC_NO_ERR_TRACE(NOT_COMPILED_IN)) { + return err; + } +#endif + #ifdef WOLFSSL_SMALL_STACK_CACHE key = (ecc_key *)XMALLOC(sizeof(*key), heap, DYNAMIC_TYPE_ECC); if (key == NULL) { From 8d45a7f79a50f9a9a805df227184e873140372ee Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Wed, 28 Oct 2026 10:50:49 +0000 Subject: [PATCH 2/3] Leave unreduced points to the generic code in wc_ecc_mulmod() The SP code takes a scalar and coordinates no wider than the curve and truncates anything wider, so a base point with a coordinate not reduced modulo the prime gave a wrong result, where the generic code reduces it. Use the SP code only for a non-negative scalar and non-negative coordinates below the modulus; leave anything else to the generic code. Test it with x + prime in place of the base point's x. --- tests/api/test_ecc.c | 11 +++++++++++ wolfcrypt/src/ecc.c | 11 ++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index bc8129cd5e3..b0241ae83fd 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -3673,6 +3673,17 @@ int test_wc_ecc_mulmod_curves(void) ExpectIntEQ(wc_ecc_mulmod(wc_ecc_key_get_priv(&key1), &key2.pubkey, &key3.pubkey, key4.pubkey.x, key4.pubkey.y, 1), 0); ExpectIntEQ(wc_ecc_cmp_point(&key3.pubkey, &key1.pubkey), MP_EQ); + #if defined(WOLFSSL_PUBLIC_MP) && !defined(WOLFSSL_SP_MATH) + /* The same base point with x + prime in place of x, a coordinate + * that is not reduced (and wider than the curve for most primes), + * must give the same result. WOLFSSL_SP_MATH builds reject + * coordinates wider than the curve instead. */ + ExpectIntEQ(mp_add(key2.pubkey.x, key4.pubkey.y, key2.pubkey.x), + MP_OKAY); + ExpectIntEQ(wc_ecc_mulmod(wc_ecc_key_get_priv(&key1), &key2.pubkey, + &key3.pubkey, key4.pubkey.x, key4.pubkey.y, 1), 0); + ExpectIntEQ(wc_ecc_cmp_point(&key3.pubkey, &key1.pubkey), MP_EQ); + #endif tested++; wc_ecc_free(&key1); diff --git a/wolfcrypt/src/ecc.c b/wolfcrypt/src/ecc.c index 0c0cf138f16..00d61c4ef71 100644 --- a/wolfcrypt/src/ecc.c +++ b/wolfcrypt/src/ecc.c @@ -3845,7 +3845,16 @@ static int ecc_sp_mulmod(const mp_int* k, ecc_point* G, ecc_point* R, default: return NOT_COMPILED_IN; } - if (mp_count_bits(k) > bits) { + /* The SP code takes a non-negative scalar no longer than the curve and + * coordinates already reduced modulo the prime; it would truncate wider + * values. Leave anything else to the generic code, which reduces them. */ + if (mp_isneg(k) || (mp_count_bits(k) > bits)) { + return NOT_COMPILED_IN; + } + if (mp_isneg(G->x) || mp_isneg(G->y) || mp_isneg(G->z) || + (mp_cmp(G->x, modulus) != MP_LT) || + (mp_cmp(G->y, modulus) != MP_LT) || + (mp_cmp(G->z, modulus) != MP_LT)) { return NOT_COMPILED_IN; } idx = wc_ecc_get_curve_idx(curveId); From 3c7ede85908ac52b04dda0d35bb547d93e1b0d3c Mon Sep 17 00:00:00 2001 From: Andrew Hutchings Date: Wed, 28 Oct 2026 11:16:09 +0000 Subject: [PATCH 3/3] Test the unreduced base point with z = 1 + prime The generic code does not handle an unreduced P-521 coordinate correctly: y + prime gives a wrong point every time and x + prime now and then, so test_wc_ecc_mulmod_curves failed intermittently. That is independent of the SP dispatch. An unreduced z still exercises the fallback on every curve (the SP code would truncate it) and the generic code handles it. --- tests/api/test_ecc.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/api/test_ecc.c b/tests/api/test_ecc.c index b0241ae83fd..deae17b6046 100644 --- a/tests/api/test_ecc.c +++ b/tests/api/test_ecc.c @@ -3674,11 +3674,11 @@ int test_wc_ecc_mulmod_curves(void) &key3.pubkey, key4.pubkey.x, key4.pubkey.y, 1), 0); ExpectIntEQ(wc_ecc_cmp_point(&key3.pubkey, &key1.pubkey), MP_EQ); #if defined(WOLFSSL_PUBLIC_MP) && !defined(WOLFSSL_SP_MATH) - /* The same base point with x + prime in place of x, a coordinate - * that is not reduced (and wider than the curve for most primes), - * must give the same result. WOLFSSL_SP_MATH builds reject - * coordinates wider than the curve instead. */ - ExpectIntEQ(mp_add(key2.pubkey.x, key4.pubkey.y, key2.pubkey.x), + /* The same base point with z = 1 + prime, a coordinate that is not + * reduced (and wider than the curve for most primes), must give the + * same result. WOLFSSL_SP_MATH builds reject coordinates wider than + * the curve instead. */ + ExpectIntEQ(mp_add(key2.pubkey.z, key4.pubkey.y, key2.pubkey.z), MP_OKAY); ExpectIntEQ(wc_ecc_mulmod(wc_ecc_key_get_priv(&key1), &key2.pubkey, &key3.pubkey, key4.pubkey.x, key4.pubkey.y, 1), 0);