From 1b000469527ef32ebf345c1d5743f89740bc4653 Mon Sep 17 00:00:00 2001 From: Mark Atwood Date: Fri, 17 Apr 2026 14:49:22 -0700 Subject: [PATCH] fix: correct sp_384/sp_521 div quotient when borrow hides in t1[0] sp_521_div_9 (sp_c64.c), sp_521_div_21 (sp_c32.c), sp_384_div_7 (sp_c64.c), and sp_384_div_15 (sp_c32.c) all use a quotient-correction loop that at i=0 calls sp_N_norm_N(&t1[1]), skipping t1[0]. A borrow hiding in t1[0] is invisible to the correction mask, resurfaces in the top limb after the outer norm, and yields a quotient one too small and a remainder p too large. Fix: after the outer norm, test the top limb sign and add sd back once more when negative, then renormalise. sp_256_div uses a two-pass subtraction strategy and is unaffected. Reproducer: Wycheproof secp521r1 ECDH tcId 55 returns the wrong shared secret without this fix. --- wolfcrypt/src/sp_c32.c | 16 ++++++++++++++++ wolfcrypt/src/sp_c64.c | 16 ++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/wolfcrypt/src/sp_c32.c b/wolfcrypt/src/sp_c32.c index 16889893c91..eaf3c2f3890 100644 --- a/wolfcrypt/src/sp_c32.c +++ b/wolfcrypt/src/sp_c32.c @@ -31321,6 +31321,14 @@ static int sp_384_div_15(const sp_digit* a, const sp_digit* d, sp_384_norm_15(&t1[i + 1]); } sp_384_norm_15(t1); + /* Correction: at loop i=0, sp_384_norm_15(&t1[1]) skips t1[0], so a + * borrow there is invisible to the correction mask that checks t1[15]. + * After the outer sp_384_norm_15(t1) the borrow lands in t1[14]. + * Add sd back when t1[14] is negative. */ + mask = (sp_digit)(t1[14] >> 31); + for (i = 0; i < 15; i++) + t1[i] += sd[i] & mask; + sp_384_norm_15(t1); sp_384_rshift_15(r, t1, 6); } @@ -39032,6 +39040,14 @@ static int sp_521_div_21(const sp_digit* a, const sp_digit* d, sp_521_norm_21(&t1[i + 1]); } sp_521_norm_21(t1); + /* Correction: at loop i=0, sp_521_norm_21(&t1[1]) skips t1[0], so a + * borrow there is invisible to the correction mask that checks t1[21]. + * After the outer sp_521_norm_21(t1) the borrow lands in t1[20]. + * Add sd back when t1[20] is negative. */ + mask = (sp_digit)(t1[20] >> 31); + for (i = 0; i < 21; i++) + t1[i] += sd[i] & mask; + sp_521_norm_21(t1); sp_521_rshift_21(r, t1, 4); } diff --git a/wolfcrypt/src/sp_c64.c b/wolfcrypt/src/sp_c64.c index 089b8fca839..7a002874ccc 100644 --- a/wolfcrypt/src/sp_c64.c +++ b/wolfcrypt/src/sp_c64.c @@ -31191,6 +31191,14 @@ static int sp_384_div_7(const sp_digit* a, const sp_digit* d, sp_384_norm_7(&t1[i + 1]); } sp_384_norm_7(t1); + /* Correction: at loop i=0, sp_384_norm_7(&t1[1]) skips t1[0], so a + * borrow there is invisible to the correction mask that checks t1[7]. + * After the outer sp_384_norm_7(t1) the borrow lands in t1[6]. + * Add sd back when t1[6] is negative. */ + mask = (sp_digit)(t1[6] >> 63); + for (i = 0; i < 7; i++) + t1[i] += sd[i] & mask; + sp_384_norm_7(t1); sp_384_rshift_7(r, t1, 1); } @@ -38216,6 +38224,14 @@ static int sp_521_div_9(const sp_digit* a, const sp_digit* d, sp_521_norm_9(&t1[i + 1]); } sp_521_norm_9(t1); + /* Correction: at loop i=0, sp_521_norm_9(&t1[1]) skips t1[0], so a + * borrow there is invisible to the correction mask that checks t1[9]. + * After the outer sp_521_norm_9(t1) the borrow lands in t1[8]. + * Add sd back when t1[8] is negative. */ + mask = (sp_digit)(t1[8] >> 63); + for (i = 0; i < 9; i++) + t1[i] += sd[i] & mask; + sp_521_norm_9(t1); sp_521_rshift_9(r, t1, 1); }