Repository navigation
Conversation
Verification: - Reject public keys that are not canonical field elements, u >= p, which includes the encodings with the most significant bit set, as the XEdDSA specification requires. - Reject public keys of small order, signatures for those keys can be forged without the private key. The specification does not reject them. - Keep rejecting signatures with s >= q, stricter than the specification, to prevent malleable signatures. - Document that the signature format used by libsignal, and by the curve25519_sign function of libsignal-protocol-c, is not accepted. It is not part of the specification. Signing: - Derive the nonce like the xed25519_sign function of libsignal-protocol-c, using the clamped bytes of the private key when it is not negated, instead of the encoding of k (mod q). The nonce is not visible to verifiers, and both choices are equally secure, but this one generates the same signatures as xed25519_sign, so its test vectors can be used. - Select the private scalar and the nonce key in constant time in calculate_key_pair, as the specification recommends, instead of branching on the sign bit of the twisted Edwards public key. - Use crypto/rand.Reader in GenerateKey and Sign when rand is nil. - Encode R only once. Keys: - Compare private keys in constant time in PrivateKey.Equal. - Return an untyped nil from PrivateKey.Public on invalid keys. Document the differences with the specification in Sign and Verify, fix doc comments, and remove a redundant sign bit mask. Add the libsignal-protocol-c XEdDSA test vectors, including the 10000 iterations pseudorandom tests, which are skipped in short mode, tests that the libsignal signature format is rejected, and regression tests for non-canonical and small order public keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L8VgknTCaG4zTDd1oBuC3T
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Name of feature:
Harden x25519 XEdDSA verification.
Pain or issue this feature alleviates:
x25519.Verifyaccepted public keys that the XEdDSA specification rejects, and keys for which signatures can be forged without the private key:u >= p, including encodings with the most significant bit set) were accepted. The specification rejects them.Verifyis now stricter than the specification in this case.Other fixes:
PrivateKey.Equalnow compares private keys in constant time.PrivateKey.Publicreturns an untypednilfor invalid keys, instead of a typed nil that compares!= nil.GenerateKeyandSignusecrypto/rand.Readerwhenrandomis nil, instead of panicking.calculate_key_pairselects the private scalar in constant time, as the specification recommends, instead of branching on the sign bit of the twisted Edwards public key.Why is this important to the project (if not answered above):
step-ca's Nebula provisioner verifies XEdDSA tokens with
jose.Verify, which callsx25519.Verify.Is there documentation on how to use this feature? If so, where?
The
SignandVerifydoc comments now document the differences with the specification:Verifyrejectss >= q; the specification only rejectss >= 2^253. This prevents creating a second valid signature for the same message without the private key. This behavior is unchanged from master.Verifyrejects small order public keys.Signderives the nonce likexed25519_signin libsignal-protocol-c: it hashes the clamped bytes ofkwhen the key is not negated, instead of the encoding ofk (mod q). The nonce is not visible to verifiers and both choices are equally secure, but this one generates the same signatures as libsignal-protocol-c, so its test vectors can be used.curve25519_signin libsignal-protocol-c, is not part of the specification and is not accepted. That format keeps the sign bit of the twisted Edwards public key in the most significant bit ofs.In what environments or workflows is this feature supported?
All. Signatures still use the format of the specification, so step and step-ca interoperate across versions in both directions. Keys generated by
GenerateKey, or by any X25519 implementation, are canonical and never of small order, so they're not affected by the new checks.In what environments or workflows is this feature explicitly NOT supported (if any)?
Verify. Honest keys never have this form.Signgenerates different, still valid, signature bytes than before for about half of the keys. This only matters to tests that compare signature bytes generated with a fixed random reader. There are none in this repository, smallstep/cli, or smallstep/certificates.ToEd25519is unchanged and still accepts non-canonical and small order keys.Supporting links/other PRs/issues:
Verification
New tests:
These checks were run locally and are not part of this PR:
xed25519_signandxed25519_verify.Signmatchedxed25519_signbyte for byte for 3000 random keys.Verifyresults, from valid, tampered, libsignal format, and random signatures, matchedxed25519_verify. The only differences are the two intended ones:s + q, and signatures forged for small order keys.ed25519.Verify: for canonical keys that are not of small order, including keys with a small order component,VerifymatchedToEd25519followed byed25519.Verifyin 27000 cases.🤖 Generated with Claude Code