Skip to content

fix: correct sp_384/sp_521 div quotient when borrow hides in t1[0]#10257

Open
MarkAtwood wants to merge 1 commit intowolfSSL:masterfrom
MarkAtwood:fix/sp521-div-quotient-correction
Open

fix: correct sp_384/sp_521 div quotient when borrow hides in t1[0]#10257
MarkAtwood wants to merge 1 commit intowolfSSL:masterfrom
MarkAtwood:fix/sp521-div-quotient-correction

Conversation

@MarkAtwood
Copy link
Copy Markdown

Summary

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 share the same quotient-correction loop structure. At iteration i=0, the loop calls sp_N_norm_N(&t1[1]), which normalises limbs [1..N] but skips t1[0]. A borrow that exists only in t1[0] after the subtraction is therefore invisible to the correction mask (which tests the sign of t1[N]). After the outer sp_N_norm_N(t1) at the end of the function, the hidden borrow resurfaces in the top limb, yielding a quotient one too small and a remainder that is one multiple of the divisor too large.

Fix: after the outer norm, test the sign of the top limb 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 (EdgeCaseEphemeralKey, x² ≡ -3 mod p) returns the wrong shared secret without this fix.

Test plan

  • Wycheproof secp521r1 ECDH tcId 55 returns correct shared secret
  • Wycheproof secp384r1 ECDH edge-case vectors pass
  • Existing P-384 and P-521 ECDH/ECDSA tests unaffected

/cc @wolfSSL-Fenrir-bot please review

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.
@MarkAtwood MarkAtwood requested review from SparkiDev and Copilot April 17, 2026 22:09
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Fixes an off-by-one quotient correction bug in the special-prime division routines for P-384 and P-521 when a borrow is “hidden” in t1[0] during the correction loop.

Changes:

  • Adds a post-normalization correction pass in sp_384_div_7 and sp_521_div_9 (64-bit backend).
  • Adds the same post-normalization correction pass in sp_384_div_15 and sp_521_div_21 (32-bit backend).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
wolfcrypt/src/sp_c64.c Adds an extra sign-based correction step after the final normalization in P-384/P-521 div routines (64-bit).
wolfcrypt/src/sp_c32.c Adds the same extra correction step after the final normalization in P-384/P-521 div routines (32-bit).
Comments suppressed due to low confidence (5)

wolfcrypt/src/sp_c64.c:1

  • mask as computed here will be 0 or 1 when sp_digit is an unsigned type, so sd[i] & mask only preserves the LSB instead of conditionally adding the whole limb. To keep this constant-time and correct, build a full-width mask (all-zeros or all-ones) from the MSB (e.g., by negating the extracted bit) before ANDing with sd[i].
    wolfcrypt/src/sp_c64.c:1
  • Same issue as in sp_384_div_7: if sp_digit is unsigned, this produces a 0/1 mask and will only add the LSB of each sd[i]. Use a full-width all-ones/all-zeros mask derived from the MSB to make the conditional add correct and constant-time.
    wolfcrypt/src/sp_c32.c:1
  • Here mask will evaluate to 0 or 1 under an unsigned sp_digit, so sd[i] & mask does not conditionally add the full limb. Convert the extracted sign bit into a full-width mask (0 or ~0) before applying it to sd[i].
    wolfcrypt/src/sp_c32.c:1
  • Same masking issue as above: as written, this produces a 0/1 mask for unsigned sp_digit and will only add 1-bit from sd[i]. Use a full-width mask derived from the sign bit so sd[i] is either fully added or not added.
    wolfcrypt/src/sp_c64.c:1
  • The comment refers to a “correction mask that checks t1[7]” but the added correction uses t1[6] as the sign/borrow indicator. This mismatch makes the rationale harder to follow; consider rewording to consistently describe which limb is checked in the main loop vs. which limb becomes negative after the outer normalization (or refer to “top/extra limb” instead of hard-coded indices).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@github-actions
Copy link
Copy Markdown

MemBrowse Memory Report

No memory changes detected for:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants