From 26bcd798a69f37c7d441f8fb3853ddf579262234 Mon Sep 17 00:00:00 2001 From: Koji Takeda Date: Tue, 2 Jun 2026 17:26:51 +0900 Subject: [PATCH] Support non-block verification for Ed25519 --- wolfcrypt/src/ed25519.c | 219 ++++++++++++++++++++++++++++++++++++ wolfssl/wolfcrypt/ed25519.h | 50 ++++++++ 2 files changed, 269 insertions(+) diff --git a/wolfcrypt/src/ed25519.c b/wolfcrypt/src/ed25519.c index de12e87b83f..ffe7caca6af 100644 --- a/wolfcrypt/src/ed25519.c +++ b/wolfcrypt/src/ed25519.c @@ -205,6 +205,213 @@ static int ed25519_hash(ed25519_key* key, const byte* in, word32 inLen, return ret; } +#if defined(WC_ED25519_NONBLOCK) && defined(ED25519_SMALL) + +/* Forward declarations for static helpers defined later in this file. */ +#ifdef HAVE_ED25519_VERIFY +static int ed25519_verify_msg_init_with_sha(const byte* sig, word32 sigLen, + ed25519_key* key, wc_Sha512 *sha, byte type, const byte* context, + byte contextLen); +static int ed25519_verify_msg_update_with_sha(const byte* msgSegment, + word32 msgSegmentLen, ed25519_key* key, wc_Sha512 *sha); +#endif + +/* Symbols from ge_low_mem.c, compiled only when ED25519_SMALL is set. */ +extern const ge_p3 ed25519_base; +extern const ge_p3 ed25519_neutral; +extern void ed25519_add(ge_p3 *r, const ge_p3 *a, const ge_p3 *b); +extern void ed25519_double(ge_p3 *r, const ge_p3 *a); + +/* + * Perform one bit-step of a scalar multiplication in extended twisted Edwards + * coordinates (double-and-add, constant-time select). + * + * ctx->r : accumulator (must be initialized to ed25519_neutral before call 0) + * ctx->pt : base point for this multiplication + * ctx->i : current bit index, caller must set to 255 before first call + * scalar : 32-byte reduced scalar + * + * Returns MP_WOULDBLOCK while bits remain (ctx->i >= 0 after decrement), + * or 0 when done (ctx->i fell below 0). + */ +static int ed25519_smult_nb_step(ed25519_nb_ctx_t* ctx, const byte* scalar) +{ + if (ctx->i >= 0) { + const byte bit = (scalar[ctx->i >> 3] >> (ctx->i & 7)) & 1; + ge_p3 s; + + ed25519_double(&ctx->r, &ctx->r); + ed25519_add(&s, &ctx->r, &ctx->pt); + + fe_select(ctx->r.X, ctx->r.X, s.X, bit); + fe_select(ctx->r.Y, ctx->r.Y, s.Y, bit); + fe_select(ctx->r.Z, ctx->r.Z, s.Z, bit); + fe_select(ctx->r.T, ctx->r.T, s.T, bit); + + ctx->i--; + } + return (ctx->i >= 0) ? MP_WOULDBLOCK : 0; +} + +int wc_ed25519_set_nonblock(ed25519_key* key, ed25519_nb_ctx_t* ctx) +{ + if (key == NULL) + return BAD_FUNC_ARG; + + /* If replacing a context, clear the old one first. */ + if (key->nb_ctx != NULL && key->nb_ctx != ctx) { + ForceZero(key->nb_ctx, sizeof(ed25519_nb_ctx_t)); + } + if (ctx != NULL) { + XMEMSET(ctx, 0, sizeof(ed25519_nb_ctx_t)); + } + key->nb_ctx = ctx; + return 0; +} + +/* ----------------------------------------------------------------------- + * Non-blocking verify (state 0 = setup, state 1 = SB smult, state 2 = hA smult) + * ----------------------------------------------------------------------- */ +#ifdef HAVE_ED25519_VERIFY +/* Group order in little-endian (same value as ed25519_order in ge_low_mem.c). */ +static const byte ed25519_order_nb[32] = { + 0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, + 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 +}; + +static int ed25519_verify_nb(const byte* sig, word32 sigLen, const byte* msg, + word32 msgLen, int* res, ed25519_key* key, + byte type, const byte* context, byte contextLen) +{ + ed25519_nb_ctx_t* nb_ctx = key->nb_ctx; + int ret = 0; + + switch (nb_ctx->state) { + case 0: { + /* ---- Setup phase (synchronous) ---- */ + int j; +#ifdef WOLFSSL_ED25519_PERSISTENT_SHA + wc_Sha512* sha = &key->sha; +#else + WC_DECLARE_VAR(sha, wc_Sha512, 1, key->heap); + WC_ALLOC_VAR_EX(sha, wc_Sha512, 1, key->heap, DYNAMIC_TYPE_HASHES, + ret = MEMORY_E; break); + ret = ed25519_hash_init(key, sha); + if (ret != 0) { + WC_FREE_VAR_EX(sha, key->heap, DYNAMIC_TYPE_HASHES); + break; + } +#endif + + /* init + update (domain, sig_R, pub) + update (msg) */ + ret = ed25519_verify_msg_init_with_sha(sig, sigLen, key, sha, + type, context, contextLen); + if (ret == 0) + ret = ed25519_verify_msg_update_with_sha(msg, msgLen, key, sha); + + /* S range check: S must be < group order */ + if (ret == 0) { + if (sigLen != ED25519_SIG_SIZE) { + ret = BAD_FUNC_ARG; + } + } + if (ret == 0) { + for (j = (int)sizeof(ed25519_order_nb) - 1; j >= 0; j--) { + if (sig[ED25519_SIG_SIZE / 2 + j] > ed25519_order_nb[j]) { + ret = BAD_FUNC_ARG; + break; + } + if (sig[ED25519_SIG_SIZE / 2 + j] < ed25519_order_nb[j]) + break; + } + if (ret == 0 && j == -1) /* S == order */ + ret = BAD_FUNC_ARG; + } + + /* Decompress and negate public key -> neg_A */ + if (ret == 0) + ret = ge_frombytes_negate_vartime(&nb_ctx->neg_A, key->p); + + /* hash_final -> h */ + if (ret == 0) + ret = ed25519_hash_final(key, sha, nb_ctx->h); + +#ifndef WOLFSSL_ED25519_PERSISTENT_SHA + ed25519_hash_free(key, sha); + WC_FREE_VAR_EX(sha, key->heap, DYNAMIC_TYPE_HASHES); +#endif + if (ret != 0) + break; + + sc_reduce(nb_ctx->h); + + /* Save sig_S (32 bytes) for first scalar mult */ + XMEMCPY(nb_ctx->sig_S, sig + (ED25519_SIG_SIZE / 2), ED25519_KEY_SIZE); + + /* Init first smult: B * sig_S */ + XMEMCPY(&nb_ctx->r, &ed25519_neutral, sizeof(ge_p3)); + XMEMCPY(&nb_ctx->pt, &ed25519_base, sizeof(ge_p3)); + nb_ctx->i = 255; + nb_ctx->state = 1; + ret = MP_WOULDBLOCK; + break; + } + + case 1: { + /* ---- First scalar mult: ge_scalarmult_base(sig_S) = SB ---- */ + ret = ed25519_smult_nb_step(nb_ctx, nb_ctx->sig_S); + if (ret == MP_WOULDBLOCK) + break; + + /* Save SB, then init second smult: neg_A * h */ + XMEMCPY(&nb_ctx->SB, &nb_ctx->r, sizeof(ge_p3)); + XMEMCPY(&nb_ctx->r, &ed25519_neutral, sizeof(ge_p3)); + XMEMCPY(&nb_ctx->pt, &nb_ctx->neg_A, sizeof(ge_p3)); + nb_ctx->i = 255; + nb_ctx->state = 2; + ret = MP_WOULDBLOCK; + break; + } + + case 2: { + /* ---- Second scalar mult: neg_A * h = hA ---- */ + ret = ed25519_smult_nb_step(nb_ctx, nb_ctx->h); + if (ret == MP_WOULDBLOCK) + break; + + /* SB + hA = SB - H(R,A,M)*A */ + { + ge_p3 sum; + ALIGN16 byte rcheck[ED25519_KEY_SIZE]; + + ed25519_add(&sum, &nb_ctx->SB, &nb_ctx->r); + ge_p3_tobytes(rcheck, &sum); + + ret = ConstantCompare(rcheck, sig, ED25519_SIG_SIZE / 2); + if (ret != 0) + ret = SIG_VERIFY_E; + else + *res = 1; + } + break; + } + + default: + ret = BAD_STATE_E; + break; + } + + if (ret != MP_WOULDBLOCK) { + ForceZero(nb_ctx, sizeof(ed25519_nb_ctx_t)); + } + return ret; +} +#endif /* HAVE_ED25519_VERIFY */ + +#endif /* WC_ED25519_NONBLOCK && ED25519_SMALL */ + #ifdef HAVE_ED25519_MAKE_KEY #if FIPS_VERSION3_GE(6,0,0) /* Performs a Pairwise Consistency Test on an Ed25519 key pair. @@ -899,6 +1106,18 @@ int wc_ed25519_verify_msg_ex(const byte* sig, word32 sigLen, const byte* msg, byte type, const byte* context, byte contextLen) { int ret; +#if defined(WC_ED25519_NONBLOCK) && defined(ED25519_SMALL) + if (key != NULL && key->nb_ctx != NULL) { + if (sig == NULL || msg == NULL || res == NULL || + (context == NULL && contextLen != 0)) { + return BAD_FUNC_ARG; + } + if ((type == Ed25519ph) && (msgLen != WC_SHA512_DIGEST_SIZE)) + return BAD_LENGTH_E; + return ed25519_verify_nb(sig, sigLen, msg, msgLen, res, key, + type, context, contextLen); + } +#endif #ifdef WOLFSSL_SE050 (void)type; (void)context; diff --git a/wolfssl/wolfcrypt/ed25519.h b/wolfssl/wolfcrypt/ed25519.h index 120c210a92f..9ef3178f9e6 100644 --- a/wolfssl/wolfcrypt/ed25519.h +++ b/wolfssl/wolfcrypt/ed25519.h @@ -41,6 +41,13 @@ #include #endif +#ifdef WC_ED25519_NONBLOCK + #ifndef ED25519_SMALL + #error "WC_ED25519_NONBLOCK requires ED25519_SMALL" + #endif + #include +#endif + #ifdef __cplusplus extern "C" { #endif @@ -80,6 +87,28 @@ enum { WC_ED25519_FLAG_DEC_SIGN = 0x01 }; +/* Non-blocking context for Ed25519 verify operations. + * Requires WC_ED25519_NONBLOCK and ED25519_SMALL. + * Tracks state across multiple calls to wc_ed25519_verify_msg() (and + * variants) so the caller can yield between steps of the scalar + * multiplications and resume by calling the same function again with + * identical arguments. Returns MP_WOULDBLOCK while in progress, 0 on + * success, <0 on error. The context is zeroed automatically on any + * non-pending return. + */ +#ifdef WC_ED25519_NONBLOCK +typedef struct ed25519_nb_ctx_t { + int state; /* operation state machine */ + int i; /* bit index for scalar mult (starts 255, ends at -1) */ + ge_p3 r; /* scalar mult accumulator */ + ge_p3 pt; /* current base point for scalar mult */ + ge_p3 neg_A; /* negated public key point */ + ge_p3 SB; /* saved result of first scalar mult (SB) */ + ALIGN16 byte sig_S[ED25519_KEY_SIZE]; /* copy of sig[32..63] */ + ALIGN16 byte h[WC_SHA512_DIGEST_SIZE]; /* reduced H(R,A,M) */ +} ed25519_nb_ctx_t; +#endif /* WC_ED25519_NONBLOCK */ + /* An ED25519 Key */ struct ed25519_key { ALIGN16 byte p[ED25519_PUB_KEY_SIZE]; /* compressed public key */ @@ -108,6 +137,9 @@ struct ed25519_key { #ifdef WOLFSSL_ED25519_PERSISTENT_SHA wc_Sha512 sha; #endif +#ifdef WC_ED25519_NONBLOCK + ed25519_nb_ctx_t* nb_ctx; +#endif }; #ifndef WC_ED25519KEY_TYPE_DEFINED @@ -181,6 +213,24 @@ WOLFSSL_API int wc_ed25519_init_ex(ed25519_key* key, void* heap, int devId); WOLFSSL_API void wc_ed25519_free(ed25519_key* key); + +#ifdef WC_ED25519_NONBLOCK +/*! + \brief Enable non-blocking support for Ed25519 verify operations on a key. + When enabled, wc_ed25519_verify_msg() and its variants return + MP_WOULDBLOCK during each step of the two scalar multiplications, + allowing the caller to yield and resume by calling the same + function again with identical arguments. + Requires WC_ED25519_NONBLOCK and ED25519_SMALL. + + \param key Pointer to ed25519_key to configure + \param ctx Pointer to ed25519_nb_ctx_t context, or NULL to disable + + \return 0 on success, BAD_FUNC_ARG if key is NULL +*/ +WOLFSSL_API +int wc_ed25519_set_nonblock(ed25519_key* key, ed25519_nb_ctx_t* ctx); +#endif /* WC_ED25519_NONBLOCK */ #ifndef WC_NO_CONSTRUCTORS WOLFSSL_API ed25519_key* wc_ed25519_new(void* heap, int devId, int *result_code);