From cd054c4f3e0853e230e4f988b669de7d7ae128d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 5 Nov 2026 14:21:46 +0100 Subject: [PATCH 1/5] Clear the KPAK Montgomery flag when making an ECCSI key Verifying a signature without SP-ECC converts the KPAK held in the key to Montgomery form and sets kpakMont. wc_MakeEccsiKey() replaced the KPAK with a fresh normal-form point but left the flag set, so the next export, KPAK hash or pair issuance reduced the new point a second time. Pairs made after that were bound to the wrong KPAK and their signatures failed to verify. Convert the KPAK back to normal form before generating the key, so the flag also stays correct when key generation fails before it writes the new point, and add a test that makes, signs and verifies twice with one key object. Fixes F-16944. --- tests/api/test_eccsi.c | 57 ++++++++++++++++++++++++++++++++++++++++++ tests/api/test_eccsi.h | 4 ++- wolfcrypt/src/eccsi.c | 3 +++ 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/tests/api/test_eccsi.c b/tests/api/test_eccsi.c index df09a80e2b2..30ead910896 100644 --- a/tests/api/test_eccsi.c +++ b/tests/api/test_eccsi.c @@ -860,3 +860,60 @@ int test_wc_Eccsi_FeatureCoverage(void) #endif return EXPECT_RESULT(); } + +/* Reuse one KMS key object across key generations and signatures. */ +int test_wc_Eccsi_KeyReuse(void) +{ + EXPECT_DECLS; +#ifdef WOLFCRYPT_HAVE_ECCSI + EccsiKey key; + WC_RNG rng; + mp_int ssk; + ecc_point* pvt = NULL; + char mail[] = "test@wolfssl.com"; + byte* id = (byte*)mail; + word32 idSz; + int verified; + int round; + byte hash[WC_MAX_DIGEST_SIZE]; + byte hashSz = 0; + byte sig[32 * 4 + 1]; + word32 sigSz; + byte msg[1] = { 0x00 }; + + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(&rng, 0, sizeof(rng)); + idSz = (word32)XSTRLEN(mail); + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectNotNull(pvt = wc_ecc_new_point()); + ExpectIntEQ(mp_init(&ssk), MP_OKAY); + ExpectIntEQ(wc_InitEccsiKey(&key, HEAP_HINT, INVALID_DEVID), 0); + + /* The second round regenerates the KPAK after a verify used it. */ + for (round = 0; round < 2; round++) { + verified = 0; + ExpectIntEQ(wc_MakeEccsiKey(&key, &rng), 0); + ExpectIntEQ(wc_MakeEccsiPair(&key, &rng, WC_HASH_TYPE_SHA256, id, + idSz, &ssk, pvt), 0); + ExpectIntEQ(wc_HashEccsiId(&key, WC_HASH_TYPE_SHA256, id, idSz, pvt, + hash, &hashSz), 0); + ExpectIntEQ(wc_SetEccsiHash(&key, hash, hashSz), 0); + ExpectIntEQ(wc_SetEccsiPair(&key, &ssk, pvt), 0); + sigSz = sizeof(sig); + ExpectIntEQ(wc_SignEccsiHash(&key, &rng, WC_HASH_TYPE_SHA256, msg, + (word32)sizeof(msg), sig, &sigSz), 0); + ExpectIntEQ(wc_VerifyEccsiHash(&key, WC_HASH_TYPE_SHA256, msg, + (word32)sizeof(msg), sig, sigSz, &verified), 0); + ExpectIntEQ(verified, 1); + } + + if (pvt != NULL) { + wc_ecc_del_point(pvt); + } + mp_free(&ssk); + wc_FreeRng(&rng); + wc_FreeEccsiKey(&key); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_eccsi.h b/tests/api/test_eccsi.h index 80ec3257944..997c3bb097f 100644 --- a/tests/api/test_eccsi.h +++ b/tests/api/test_eccsi.h @@ -26,9 +26,11 @@ int test_wc_Eccsi_DecisionCoverage(void); int test_wc_Eccsi_FeatureCoverage(void); +int test_wc_Eccsi_KeyReuse(void); #define TEST_ECCSI_DECLS \ TEST_DECL_GROUP("eccsi", test_wc_Eccsi_DecisionCoverage), \ - TEST_DECL_GROUP("eccsi", test_wc_Eccsi_FeatureCoverage) + TEST_DECL_GROUP("eccsi", test_wc_Eccsi_FeatureCoverage), \ + TEST_DECL_GROUP("eccsi", test_wc_Eccsi_KeyReuse) #endif /* WOLFCRYPT_TEST_ECCSI_H */ diff --git a/wolfcrypt/src/eccsi.c b/wolfcrypt/src/eccsi.c index 95cab2236cf..04d5ab9dae5 100644 --- a/wolfcrypt/src/eccsi.c +++ b/wolfcrypt/src/eccsi.c @@ -486,6 +486,9 @@ int wc_MakeEccsiKey(EccsiKey* key, WC_RNG* rng) err = BAD_FUNC_ARG; } + if (err == 0) { + err = eccsi_kpak_from_mont(key); + } if (err == 0) { err = wc_ecc_make_key_ex(rng, key->ecc.dp->size, &key->ecc, key->ecc.dp->id); From deaada963b75a4dcd1a5ceaf17f5406b793426d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 5 Nov 2026 14:42:12 +0100 Subject: [PATCH 2/5] Validate the ECCSI PVT as a public key only wc_ValidateEccsiPvt() copies the PVT into the scratch key->pubkey and runs wc_ecc_check_key() on it, but left the scratch key's type as it was. After wc_MakeEccsiPair() or wc_SignEccsiHash() that type is ECC_PRIVATEKEY with a zeroed scalar, so the private key range check failed and a valid PVT was reported invalid. After wc_VerifyEccsiHash() the scalar holds r, which SP-ECC builds then rejected in the [k]G check. Mark the scratch key as public before the check, since RFC 6507 PVT validation involves no private key, and extend the key reuse test to validate the PVT after each operation. Also use a logical OR in the function's argument check. Fixes F-16945. --- tests/api/test_eccsi.c | 33 ++++++++++++++++++++++++++++----- wolfcrypt/src/eccsi.c | 3 ++- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/tests/api/test_eccsi.c b/tests/api/test_eccsi.c index 30ead910896..71e00df0c85 100644 --- a/tests/api/test_eccsi.c +++ b/tests/api/test_eccsi.c @@ -485,11 +485,7 @@ int test_wc_Eccsi_DecisionCoverage(void) #endif /* --- wc_ValidateEccsiPvt() (CLIENT) --- - * eccsi.c ~line 1579: (key==NULL) | (pvt==NULL) || (valid==NULL) - * (first two operands are combined with a bitwise '|', not '&&'/'||', - * so both are always evaluated; MC/DC's baseline+single-flip pattern - * still applies since the result is the same 0/1 boolean algebra.) - */ + * eccsi.c: (key==NULL) || (pvt==NULL) || (valid==NULL) */ ExpectIntEQ(wc_ValidateEccsiPvt(NULL, pvt, &valid), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); ExpectIntEQ(wc_ValidateEccsiPvt(&keyPub, NULL, &valid), @@ -870,9 +866,11 @@ int test_wc_Eccsi_KeyReuse(void) WC_RNG rng; mp_int ssk; ecc_point* pvt = NULL; + ecc_point* badPvt = NULL; char mail[] = "test@wolfssl.com"; byte* id = (byte*)mail; word32 idSz; + int valid; int verified; int round; byte hash[WC_MAX_DIGEST_SIZE]; @@ -887,6 +885,7 @@ int test_wc_Eccsi_KeyReuse(void) ExpectIntEQ(wc_InitRng(&rng), 0); ExpectNotNull(pvt = wc_ecc_new_point()); + ExpectNotNull(badPvt = wc_ecc_new_point()); ExpectIntEQ(mp_init(&ssk), MP_OKAY); ExpectIntEQ(wc_InitEccsiKey(&key, HEAP_HINT, INVALID_DEVID), 0); @@ -896,6 +895,9 @@ int test_wc_Eccsi_KeyReuse(void) ExpectIntEQ(wc_MakeEccsiKey(&key, &rng), 0); ExpectIntEQ(wc_MakeEccsiPair(&key, &rng, WC_HASH_TYPE_SHA256, id, idSz, &ssk, pvt), 0); + valid = 0; + ExpectIntEQ(wc_ValidateEccsiPvt(&key, pvt, &valid), 0); + ExpectIntEQ(valid, 1); ExpectIntEQ(wc_HashEccsiId(&key, WC_HASH_TYPE_SHA256, id, idSz, pvt, hash, &hashSz), 0); ExpectIntEQ(wc_SetEccsiHash(&key, hash, hashSz), 0); @@ -903,11 +905,32 @@ int test_wc_Eccsi_KeyReuse(void) sigSz = sizeof(sig); ExpectIntEQ(wc_SignEccsiHash(&key, &rng, WC_HASH_TYPE_SHA256, msg, (word32)sizeof(msg), sig, &sigSz), 0); + valid = 0; + ExpectIntEQ(wc_ValidateEccsiPvt(&key, pvt, &valid), 0); + ExpectIntEQ(valid, 1); ExpectIntEQ(wc_VerifyEccsiHash(&key, WC_HASH_TYPE_SHA256, msg, (word32)sizeof(msg), sig, sigSz, &verified), 0); ExpectIntEQ(verified, 1); + valid = 0; + ExpectIntEQ(wc_ValidateEccsiPvt(&key, pvt, &valid), 0); + ExpectIntEQ(valid, 1); } + /* A PVT off the curve or at infinity is rejected. */ + ExpectIntEQ(wc_ecc_copy_point(pvt, badPvt), 0); + ExpectIntEQ(mp_add_d(badPvt->y, 1, badPvt->y), MP_OKAY); + valid = 1; + ExpectIntEQ(wc_ValidateEccsiPvt(&key, badPvt, &valid), 0); + ExpectIntEQ(valid, 0); + ExpectIntEQ(mp_set(badPvt->x, 0), MP_OKAY); + ExpectIntEQ(mp_set(badPvt->y, 0), MP_OKAY); + valid = 1; + ExpectIntEQ(wc_ValidateEccsiPvt(&key, badPvt, &valid), 0); + ExpectIntEQ(valid, 0); + + if (badPvt != NULL) { + wc_ecc_del_point(badPvt); + } if (pvt != NULL) { wc_ecc_del_point(pvt); } diff --git a/wolfcrypt/src/eccsi.c b/wolfcrypt/src/eccsi.c index 04d5ab9dae5..4d98931846c 100644 --- a/wolfcrypt/src/eccsi.c +++ b/wolfcrypt/src/eccsi.c @@ -1618,7 +1618,7 @@ int wc_ValidateEccsiPvt(EccsiKey* key, const ecc_point* pvt, int* valid) { int err = 0; - if ((key == NULL)| (pvt == NULL) || (valid == NULL)) { + if ((key == NULL) || (pvt == NULL) || (valid == NULL)) { err = BAD_FUNC_ARG; } @@ -1630,6 +1630,7 @@ int wc_ValidateEccsiPvt(EccsiKey* key, const ecc_point* pvt, int* valid) err = wc_ecc_copy_point(pvt, &key->pubkey.pubkey); } if (err == 0) { + key->pubkey.type = ECC_PUBLICKEY; *valid = (wc_ecc_check_key(&key->pubkey) == 0); } From 0bf12cc00a34abd79fd4730e23970cb5d019af25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 5 Nov 2026 14:47:14 +0100 Subject: [PATCH 3/5] Clear the ECCSI identity hash when making a new key wc_MakeEccsiKey() replaces the KPAK but kept the identity hash set with wc_SetEccsiHash(). HS is computed over the KPAK, so signing after a new key succeeded and produced signatures that no holder of the new KPAK could verify. Clear the hash so that signing and verifying return BAD_STATE_E until the caller sets one computed with the new key. --- tests/api/test_eccsi.c | 8 ++++++++ wolfcrypt/src/eccsi.c | 2 ++ 2 files changed, 10 insertions(+) diff --git a/tests/api/test_eccsi.c b/tests/api/test_eccsi.c index 71e00df0c85..e5c314f3570 100644 --- a/tests/api/test_eccsi.c +++ b/tests/api/test_eccsi.c @@ -881,6 +881,7 @@ int test_wc_Eccsi_KeyReuse(void) XMEMSET(&key, 0, sizeof(key)); XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(&ssk, 0, sizeof(ssk)); idSz = (word32)XSTRLEN(mail); ExpectIntEQ(wc_InitRng(&rng), 0); @@ -893,6 +894,13 @@ int test_wc_Eccsi_KeyReuse(void) for (round = 0; round < 2; round++) { verified = 0; ExpectIntEQ(wc_MakeEccsiKey(&key, &rng), 0); + if (round > 0) { + /* The identity hash set in the first round is for the old KPAK. */ + sigSz = sizeof(sig); + ExpectIntEQ(wc_SignEccsiHash(&key, &rng, WC_HASH_TYPE_SHA256, msg, + (word32)sizeof(msg), sig, &sigSz), + WC_NO_ERR_TRACE(BAD_STATE_E)); + } ExpectIntEQ(wc_MakeEccsiPair(&key, &rng, WC_HASH_TYPE_SHA256, id, idSz, &ssk, pvt), 0); valid = 0; diff --git a/wolfcrypt/src/eccsi.c b/wolfcrypt/src/eccsi.c index 4d98931846c..e4edb8af23c 100644 --- a/wolfcrypt/src/eccsi.c +++ b/wolfcrypt/src/eccsi.c @@ -470,6 +470,7 @@ static int eccsi_compute_hs(EccsiKey* key, enum wc_HashType hashType, * * Creates a random private key and multiplies it by the base point to calculate * the public key. + * Clears the identity hash, as it was computed with the previous KPAK. * * @param [in] key ECCSI key. * @param [in] rng Random number generator. @@ -487,6 +488,7 @@ int wc_MakeEccsiKey(EccsiKey* key, WC_RNG* rng) } if (err == 0) { + key->idHashSz = 0; err = eccsi_kpak_from_mont(key); } if (err == 0) { From eaad809c4b05508a21df0408e331a674cf951947 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 5 Nov 2026 15:02:19 +0100 Subject: [PATCH 4/5] Keep the ECCSI KPAK in normal form during verification Verification needs the KPAK in Montgomery form for the generic point add, so it converted key->ecc.pubkey in place and tracked that with kpakMont. Every function that read or replaced the KPAK had to undo or reset the conversion, and missing one corrupted the key. The SP and generic selection was also wrong: SP-ECC builds without SP P-256 took the generic add without converting the KPAK, so every valid signature failed to verify, and builds with SP P-256 had no fallback for other curves, so verification returned NOT_COMPILED_IN for keys on curves such as brainpoolP256r1. Select the SP code in eccsi_mulmod_point_add() only for P-256, as the other point helpers do, and in the generic code convert a temporary copy of the KPAK. The stored KPAK now stays in normal form, so the conversion helpers, the kpakMont field and the resets in the import and key generation functions are removed. Add a test that signs and verifies on each 256-bit curve compiled in. Fixes F-14285. --- tests/api/test_eccsi.c | 75 +++++++++++++++++ tests/api/test_eccsi.h | 4 +- wolfcrypt/src/eccsi.c | 167 ++++++++++++-------------------------- wolfcrypt/test/test.c | 10 +-- wolfssl/wolfcrypt/eccsi.h | 2 - 5 files changed, 133 insertions(+), 125 deletions(-) diff --git a/tests/api/test_eccsi.c b/tests/api/test_eccsi.c index e5c314f3570..da0102f0e67 100644 --- a/tests/api/test_eccsi.c +++ b/tests/api/test_eccsi.c @@ -948,3 +948,78 @@ int test_wc_Eccsi_KeyReuse(void) #endif return EXPECT_RESULT(); } + +/* Sign and verify on each 256-bit curve compiled in. */ +int test_wc_Eccsi_Curves(void) +{ + EXPECT_DECLS; +#ifdef WOLFCRYPT_HAVE_ECCSI + static const int curves[] = { + ECC_SECP256R1, + #ifdef HAVE_ECC_BRAINPOOL + ECC_BRAINPOOLP256R1, + #endif + #ifdef HAVE_ECC_KOBLITZ + ECC_SECP256K1, + #endif + }; + EccsiKey key; + WC_RNG rng; + mp_int ssk; + ecc_point* pvt = NULL; + char mail[] = "test@wolfssl.com"; + byte* id = (byte*)mail; + word32 idSz; + int valid; + int verified; + int i; + byte hash[WC_MAX_DIGEST_SIZE]; + byte hashSz = 0; + byte sig[32 * 4 + 1]; + word32 sigSz; + byte msg[1] = { 0x00 }; + + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(&ssk, 0, sizeof(ssk)); + idSz = (word32)XSTRLEN(mail); + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectNotNull(pvt = wc_ecc_new_point()); + ExpectIntEQ(mp_init(&ssk), MP_OKAY); + + for (i = 0; i < (int)(sizeof(curves) / sizeof(curves[0])); i++) { + verified = 0; + XMEMSET(&key, 0, sizeof(key)); + ExpectIntEQ(wc_InitEccsiKey_ex(&key, 32, curves[i], HEAP_HINT, + INVALID_DEVID), 0); + ExpectIntEQ(wc_MakeEccsiKey(&key, &rng), 0); + ExpectIntEQ(wc_MakeEccsiPair(&key, &rng, WC_HASH_TYPE_SHA256, id, + idSz, &ssk, pvt), 0); + valid = 0; + ExpectIntEQ(wc_ValidateEccsiPair(&key, WC_HASH_TYPE_SHA256, id, idSz, + &ssk, pvt, &valid), 0); + ExpectIntEQ(valid, 1); + valid = 0; + ExpectIntEQ(wc_ValidateEccsiPvt(&key, pvt, &valid), 0); + ExpectIntEQ(valid, 1); + ExpectIntEQ(wc_HashEccsiId(&key, WC_HASH_TYPE_SHA256, id, idSz, pvt, + hash, &hashSz), 0); + ExpectIntEQ(wc_SetEccsiHash(&key, hash, hashSz), 0); + ExpectIntEQ(wc_SetEccsiPair(&key, &ssk, pvt), 0); + sigSz = sizeof(sig); + ExpectIntEQ(wc_SignEccsiHash(&key, &rng, WC_HASH_TYPE_SHA256, msg, + (word32)sizeof(msg), sig, &sigSz), 0); + ExpectIntEQ(wc_VerifyEccsiHash(&key, WC_HASH_TYPE_SHA256, msg, + (word32)sizeof(msg), sig, sigSz, &verified), 0); + ExpectIntEQ(verified, 1); + wc_FreeEccsiKey(&key); + } + + if (pvt != NULL) { + wc_ecc_del_point(pvt); + } + mp_free(&ssk); + wc_FreeRng(&rng); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_eccsi.h b/tests/api/test_eccsi.h index 997c3bb097f..01a0eb6d65f 100644 --- a/tests/api/test_eccsi.h +++ b/tests/api/test_eccsi.h @@ -27,10 +27,12 @@ int test_wc_Eccsi_DecisionCoverage(void); int test_wc_Eccsi_FeatureCoverage(void); int test_wc_Eccsi_KeyReuse(void); +int test_wc_Eccsi_Curves(void); #define TEST_ECCSI_DECLS \ TEST_DECL_GROUP("eccsi", test_wc_Eccsi_DecisionCoverage), \ TEST_DECL_GROUP("eccsi", test_wc_Eccsi_FeatureCoverage), \ - TEST_DECL_GROUP("eccsi", test_wc_Eccsi_KeyReuse) + TEST_DECL_GROUP("eccsi", test_wc_Eccsi_KeyReuse), \ + TEST_DECL_GROUP("eccsi", test_wc_Eccsi_Curves) #endif /* WOLFCRYPT_TEST_ECCSI_H */ diff --git a/wolfcrypt/src/eccsi.c b/wolfcrypt/src/eccsi.c index e4edb8af23c..32a8ed5efad 100644 --- a/wolfcrypt/src/eccsi.c +++ b/wolfcrypt/src/eccsi.c @@ -306,80 +306,6 @@ static int eccsi_encode_base(EccsiKey* key, byte* data, word32* dataSz) return err; } -#ifndef WOLFSSL_HAVE_SP_ECC -/* - * Convert the KPAK to montgomery form. - * - * The KPAK is needed in Montgomery form for verification. - * - * @param [in] key ECCSI key. - * @return 0 on success. - * @return MP_MEM or MEMORY_E when dynamic memory allocation fails. - * @return Other -ve value when an internal operation fails. - */ -static int eccsi_kpak_to_mont(EccsiKey* key) -{ - int err = 0; - ecc_point* kpak = &key->ecc.pubkey; - mp_int* mu = &key->tmp; - mp_int* prime = &key->params.prime; - - if (!key->kpakMont) { - err = mp_montgomery_calc_normalization(mu, prime); - if (err == 0) { - err = mp_mulmod(kpak->x, mu, prime, kpak->x); - } - if (err == 0) { - err = mp_mulmod(kpak->y, mu, prime, kpak->y); - } - if (err == 0) { - err = mp_mulmod(kpak->z, mu, prime, kpak->z); - } - if (err == 0) { - key->kpakMont = 1; - } - } - - return err; -} -#endif - -/* - * Convert the KPAK from montgomery form. - * - * The KPAK is needed in Montgomery form for verification. - * - * @param [in] key ECCSI key. - * @return 0 on success. - * @return MP_MEM or MEMORY_E when dynamic memory allocation fails. - * @return Other -ve value when an internal operation fails. - */ -static int eccsi_kpak_from_mont(EccsiKey* key) -{ - int err = 0; - ecc_point* kpak = &key->ecc.pubkey; - mp_digit mp; - mp_int* prime = &key->params.prime; - - if (key->kpakMont) { - err = mp_montgomery_setup(prime, &mp); - if (err == 0) { - err = mp_montgomery_reduce(kpak->x, prime, mp); - } - if (err == 0) { - err = mp_montgomery_reduce(kpak->y, prime, mp); - } - if (err == 0) { - err = mp_montgomery_reduce(kpak->z, prime, mp); - } - if (err == 0) { - key->kpakMont = 0; - } - } - - return err; -} - /* * Compute HS = hash( G | KPAK | ID | PVT ) * @@ -416,9 +342,6 @@ static int eccsi_compute_hs(EccsiKey* key, enum wc_HashType hashType, if (err == 0) { err = wc_HashUpdate(&key->hash, hashType, key->data, dataSz); } - if (err == 0) { - err = eccsi_kpak_from_mont(key); - } if (err == 0) { dataSz = sizeof(key->data); /* KPAK - public key */ @@ -489,9 +412,6 @@ int wc_MakeEccsiKey(EccsiKey* key, WC_RNG* rng) if (err == 0) { key->idHashSz = 0; - err = eccsi_kpak_from_mont(key); - } - if (err == 0) { err = wc_ecc_make_key_ex(rng, key->ecc.dp->size, &key->ecc, key->ecc.dp->id); #ifdef WOLFSSL_ASYNC_CRYPT @@ -682,9 +602,6 @@ int wc_ExportEccsiKey(EccsiKey* key, byte* data, word32* sz) *sz = (word32)(key->ecc.dp->size * 3); } } - if (err == 0) { - err = eccsi_kpak_from_mont(key); - } if (err == 0) { /* Encode key */ err = eccsi_encode_key(key, data); @@ -752,8 +669,6 @@ int wc_ImportEccsiKey(EccsiKey* key, const byte* data, word32 sz) } if (err == 0) { - key->kpakMont = 0; - /* Decode key */ err = eccsi_decode_key(key, data); } @@ -884,9 +799,6 @@ int wc_ExportEccsiPublicKey(EccsiKey* key, byte* data, word32* sz, int raw) err = BAD_STATE_E; } - if ((err == 0) && (data != NULL)) { - err = eccsi_kpak_from_mont(key); - } if (err == 0) { /* Write out public key. */ err = eccsi_encode_point(&key->ecc.pubkey, (word32)key->ecc.dp->size, @@ -1347,8 +1259,6 @@ int wc_ImportEccsiPublicKey(EccsiKey* key, const byte* data, word32 sz, } if (err == 0) { - key->kpakMont = 0; - /* Read the public key. */ err = eccsi_decode_point(&key->ecc.pubkey, (word32)key->ecc.dp->size, data, sz); @@ -1368,7 +1278,7 @@ int wc_ImportEccsiPublicKey(EccsiKey* key, const byte* data, word32 sz, * * @param [in] key ECCSI key. * @param [in] n MP integer representing scalar to multiply by. - * @param [in] a ECC point to add. + * @param [in] a ECC point to add, in Montgomery form. * @param [out] res ECC point representation of the resulting point. * @param [in] mp Montgomery reduction multiplier. * @param [in] map 0 indicates to leave in projective representation. @@ -1458,7 +1368,7 @@ static int eccsi_mulmod_point(EccsiKey* key, const mp_int* n, ecc_point* point, * @param [in] key ECCSI key. * @param [in] n MP integer representing scalar to multiply by. * @param [in] point ECC point representation of a point on the curve. - * @param [in] a ECC point to add. + * @param [in] a ECC point to add, in normal form. * @param [out] res ECC point representation of the resulting point. * @param [in] mp Montgomery reduction multiplier. * @param [in] map 0 indicates to leave in projective representation. @@ -1470,32 +1380,64 @@ static int eccsi_mulmod_point(EccsiKey* key, const mp_int* n, ecc_point* point, static int eccsi_mulmod_point_add(EccsiKey* key, const mp_int* n, ecc_point* point, ecc_point* a, ecc_point* res, mp_digit mp, int map) { -#if defined(WOLFSSL_HAVE_SP_ECC) && !defined(WOLFSSL_SP_NO_256) - int err = WC_NO_ERR_TRACE(NOT_COMPILED_IN); + int err = 0; +#if defined(WOLFSSL_HAVE_SP_ECC) && !defined(WOLFSSL_SP_NO_256) if ((key->ecc.idx != ECC_CUSTOM_IDX) && (ecc_sets[key->ecc.idx].id == ECC_SECP256R1)) { err = sp_ecc_mulmod_add_256(n, point, a, 0, res, map, key->heap); } + else +#endif +#ifndef WOLFSSL_SP_MATH + { + EccsiKeyParams* params = &key->params; + ecc_point* montA; - (void)mp; - - return err; -#else - int err; - EccsiKeyParams* params = &key->params; - - err = wc_ecc_mulmod(n, point, res, ¶ms->a, ¶ms->prime, 0); - if (err == 0) { - err = ecc_projective_add_point(res, a, res, &key->params.a, - ¶ms->prime, mp); + montA = wc_ecc_new_point_h(key->heap); + if (montA == NULL) { + err = MEMORY_E; + } + if (err == 0) { + err = wc_ecc_mulmod(n, point, res, ¶ms->a, ¶ms->prime, 0); + } + if (err == 0) { + err = mp_montgomery_calc_normalization(montA->z, ¶ms->prime); + } + if (err == 0) { + err = mp_mulmod(a->x, montA->z, ¶ms->prime, montA->x); + } + if (err == 0) { + err = mp_mulmod(a->y, montA->z, ¶ms->prime, montA->y); + } + if (err == 0) { + err = mp_mulmod(a->z, montA->z, ¶ms->prime, montA->z); + } + if (err == 0) { + err = ecc_projective_add_point(res, montA, res, ¶ms->a, + ¶ms->prime, mp); + } + if ((err == 0) && map) { + err = ecc_map(res, ¶ms->prime, mp); + } + if (montA != NULL) { + wc_ecc_del_point_h(montA, key->heap); + } } - if ((err == 0) && map) { - err = ecc_map(res, ¶ms->prime, mp); +#else + { + err = NOT_COMPILED_IN; } + (void)key; + (void)n; + (void)point; + (void)a; + (void)res; + (void)mp; + (void)map; +#endif return err; -#endif } /** @@ -1567,7 +1509,6 @@ int wc_ValidateEccsiPair(EccsiKey* key, enum wc_HashType hashType, /* Step 2: Compute HS = hash( G | KPAK | ID | PVT ) */ if (err == 0) { hashSz = (byte)sizeof(key->data); - /* Converts KPAK from mont. */ err = eccsi_compute_hs(key, hashType, id, idSz, pvt, key->data, &hashSz); } @@ -2149,15 +2090,7 @@ static int eccsi_calc_y(EccsiKey* key, ecc_point* pvt, mp_digit mp, int err; mp_int* hs = &key->tmp; -#ifndef WOLFSSL_HAVE_SP_ECC - err = eccsi_kpak_to_mont(key); - /* Need KPAK in montgomery form. */ - if (err == 0) { - err = mp_read_unsigned_bin(hs, key->idHash, key->idHashSz); - } -#else err = mp_read_unsigned_bin(hs, key->idHash, key->idHashSz); -#endif if (err == 0) { /* [HS]PVT + KPAK */ diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 75f2d90b683..1775ac4718e 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -82406,12 +82406,12 @@ static wc_test_ret_t eccsi_sign_verify_test(EccsiKey* priv, EccsiKey* pub, WC_RN if (!verified) return WC_TEST_RET_ENC_NC; - /* Check that the KPAK is converted from montgomery form. */ + /* Check that the key exports and imports after verifying. */ ret = eccsi_imp_exp_key_test(priv); if (ret != 0) return ret; - /* Check that KPAK can converted to Montgomery form again. */ + /* Check that verifying still works afterwards. */ ret = wc_VerifyEccsiHash(priv, WC_HASH_TYPE_SHA256, msg, msgSz, sig, sigSz, &verified); if (ret != 0) @@ -82419,7 +82419,7 @@ static wc_test_ret_t eccsi_sign_verify_test(EccsiKey* priv, EccsiKey* pub, WC_RN if (!verified) return WC_TEST_RET_ENC_NC; - /* Check that the KPAK is converted from montgomery form. */ + /* Check that the pair still validates after verifying. */ ret = wc_ValidateEccsiPair(pub, WC_HASH_TYPE_SHA256, id, idSz, ssk, pvt, &valid); if (ret != 0) @@ -82427,7 +82427,7 @@ static wc_test_ret_t eccsi_sign_verify_test(EccsiKey* priv, EccsiKey* pub, WC_RN if (!valid) return WC_TEST_RET_ENC_NC; - /* Check that KPAK can converted to Montgomery form again. */ + /* Check that verifying still works afterwards. */ ret = wc_VerifyEccsiHash(priv, WC_HASH_TYPE_SHA256, msg, msgSz, sig, sigSz, &verified); if (ret != 0) @@ -82435,7 +82435,7 @@ static wc_test_ret_t eccsi_sign_verify_test(EccsiKey* priv, EccsiKey* pub, WC_RN if (!verified) return WC_TEST_RET_ENC_NC; - /* Check that the KPAK is converted from montgomery form. */ + /* Check that the public key exports and imports after verifying. */ ret = eccsi_imp_exp_pubkey_test(priv, pub); if (ret != 0) return ret; diff --git a/wolfssl/wolfcrypt/eccsi.h b/wolfssl/wolfcrypt/eccsi.h index 7583320d981..3e3545b7c55 100644 --- a/wolfssl/wolfcrypt/eccsi.h +++ b/wolfssl/wolfcrypt/eccsi.h @@ -103,8 +103,6 @@ typedef struct EccsiKey { #endif /** Heap hint for dynamic memory allocation. */ void* heap; - /** Bit indicates KPAK (public key) is in montgomery form. */ - WC_BITFIELD kpakMont:1; } EccsiKey; #ifdef __cplusplus From a9a22f85587491731059a99dd2266ee36b3107f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Frauenschl=C3=A4ger?= Date: Thu, 5 Nov 2026 15:19:39 +0100 Subject: [PATCH 5/5] Add CI coverage for ECCSI with SP-ECC ECCSI picks between the SP P-256 code and the generic point operations per curve, and the generic path inside an SP-ECC build was not built by any CI configuration. Add one configuration with SP-ECC but without SP P-256, so P-256 and brainpool keys take the generic point add, and one with SP P-256 and the custom curves, so brainpoolP256r1 and secp256k1 keys use the generic fallback next to the SP code. Both are full builds, since --enable-cryptonly does not build tests/unit.test and so would skip the ECCSI API tests. --- .github/configs/os-check-linux.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/configs/os-check-linux.json b/.github/configs/os-check-linux.json index 2dcbdfba8d2..56d3a3fb7d6 100644 --- a/.github/configs/os-check-linux.json +++ b/.github/configs/os-check-linux.json @@ -530,6 +530,12 @@ "configure": ["--enable-cryptonly", "--disable-dh", "--enable-eccsi", "--enable-sakke", "--enable-rsavfy", "--enable-sp-math", "--enable-sp", "--disable-examples"]}, +{"name": "eccsi-sp-no-p256", + "comment": "ECCSI on SP-ECC without SP P-256.", + "configure": ["--enable-eccsi", "--enable-sp=ec384", "--enable-ecccustcurves=all"]}, +{"name": "eccsi-sp-custcurves", + "comment": "ECCSI on SP P-256 with the custom curves.", + "configure": ["--enable-eccsi", "--enable-sp", "--enable-ecccustcurves=all"]}, {"name": "cryptonly-no-asn-rsa", "minutes": 0.2, "check": false, "comment": "RSA with ASN.1 disabled. Build only, since wolfcrypt test and benchmark do not support this combination.", "configure": ["--enable-cryptonly", "--disable-asn", "--enable-lowresource",