Skip to content

Reject Ed25519 signatures with S >= L (GH #1352) - #1355

Merged
noloader merged 1 commit into
weidai11:masterfrom
Coralesoft:fix/issue-1352-ed25519-scalar
Oct 2, 2026
Merged

noloader merged 1 commit into
weidai11:masterfrom
Coralesoft:fix/issue-1352-ed25519-scalar

Conversation

@Coralesoft

@Coralesoft Coralesoft commented May 26, 2026 •

Copy link
Copy Markdown
Contributor

The Ed25519 verifier accepts a signature whose scalar S has had the group order L added to it. Donna only checks that the top three bits of S are clear, which leaves S between L and 2^253 accepted, and the verification equation holds modulo L.

This checks S < L in ed25519Verifier::VerifyAndRestart and ed25519Verifier::VerifyStream, as asked for on #1352. Donna is not touched.

RFC 8032 test vector 1 verifies before and after. The same signature with L added to S verifies on master and is rejected with this change. cryptest.exe v passes.

Refs #1352

@noloader

noloader commented Aug 6, 2026

Copy link
Copy Markdown
Collaborator

Thanks @Coralesoft.

Both the Donna verifier and the NaCl C API verifier had this defect. Both are patched here in separate commits.

I'd be interested to know what Bernstein and Moon think about the change. I'm thinking they already accounted for it by masking the high order byte. Have you performed any research?

Jeff

@Coralesoft

Copy link
Copy Markdown
Contributor Author

Hi Jeff,

It's been a while, but I found my notes. You are right about donna: floodyberry's tree at HEAD still has only the RS[63] & 224 mask and no full S < L check.

The catch is that the mask and canonicity are different bounds. & 224 enforces S < 2^253, while canonical encoding needs S < L, and L is only slightly above 2^252. Every scalar encoding in [L, 2^253) passes that mask, and that is where S + L lands for all but roughly a 2^-127 fraction of canonical scalars.

Chalkias, Garillot and Nikolaenko surveyed the major Ed25519 implementations in "Taming the many EdDSAs" (https://eprint.iacr.org/2020/1244.pdf, SSR 2020) and put donna in the same category as ref10:

The exceptions are ed25519-java, TweetNacl, python-ed25519, ed25519-donna, and ref10, the latter two of which only perform the incomplete fail fast check (as shown in Listing 1.1 line#4), rather than a full check of its size.

Their Listing 1.1 shows how both masks can be used as fast paths around the full comparison:

if s_bytes[31] & 240 == 0 { true }        // succeed fast
else if s_bytes[31] & 224 != 0 { false }  // fail fast
else { full_s_canonicity_check(s_bytes) }

Current libsodium uses the & 240 succeed-fast check before calling sc25519_is_canonical. Its ED25519_COMPAT branch retains the older & 224-only behaviour.

Here is what happens on upstream commit 78242590. Using RFC 8032 test vector 1 and then the same signature with L added to S, both signatures verify:

public    d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a
message   (empty)

original  e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155
          5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b

malleated e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155
          4c8c7872aa064e049dbb3013fbf29380d25bf5f0595bbe24655141438e7a101b

The last byte of S goes from 0x0b to 0x1b, 0x1b & 224 is still zero, and [S+L]B == [S]B leaves the equation unchanged. Two signatures are accepted by current master for one key and one message, though the second is invalid under RFC 8032 section 5.1.7, which requires 0 <= S < L.

The mask is not useless, for what it is worth. It does reject S far above L, which is why the paper records donna and ref10 rejecting its vector 7 while accepting vector 6. This S + L value sits in the band between them.

Their archived test-vector repository is at https://github.com/novifinancial/ed25519-speccheck if you want to check this independently of my patch. Vectors 6 and 7 are the S-out-of-bounds cases, and the repo publishes a pass/fail table across the libraries they tested.

Col

Donna only checks that the top three bits of S are clear, so a signature with the group order added to S still verified. Check S < L in ed25519Verifier::VerifyAndRestart and ed25519Verifier::VerifyStream.

Refs weidai11#1352

@noloader noloader left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, thanks.

@noloader
noloader merged commit 5873a73 into weidai11:master Oct 2, 2026
1 check passed
@Coralesoft
Coralesoft deleted the fix/issue-1352-ed25519-scalar branch October 2, 2026 02:27
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