Conversation
Signed-off-by: Ivo Kubjas <ivo.kubjas@consensys.com>
Signed-off-by: Ivo Kubjas <ivo.kubjas@consensys.com>
Signed-off-by: Ivo Kubjas <ivo.kubjas@consensys.com>
Signed-off-by: Ivo Kubjas <ivo.kubjas@consensys.com>
yelhousni
requested changes
Oct 5, 2026
yelhousni
left a comment
Contributor
There was a problem hiding this comment.
LGTM real vulnerability, sound fix. Verified: test fails on unfixed code (all 5 curves), PR head green, +30 constraints exact (7,769 → 7,799).
A few asks though if it makes sense:
- Document the divergence from gnark-crypto. Native validatePublicKeyPoint requires full subgroup membership; the circuit now requires only [cofactor]A ≠ O, so mixed-order keys are accepted in-circuit and rejected natively. Godoc should say so.
- Warn identity-using callers. Mixed-order acceptance means one secret key → 8 valid public keys (cofactor 8). examples/rollup/circuit.go:169 hashes A.X, A.Y as the account identity, so this is live in-tree.
- Fix IsValid's contract wording. It still says "returns 1 ... and 0 otherwise," but off-curve inputs now make the circuit unsatisfiable, not 0. One clause.
- Add a positive test for a mixed-order key to pin the deliberate design decision.
Signed-off-by: Ivo Kubjas <ivo.kubjas@consensys.com>
Signed-off-by: Ivo Kubjas <ivo.kubjas@consensys.com>
Signed-off-by: Ivo Kubjas <ivo.kubjas@consensys.com>
Contributor
Author
Thanks for the comments! I implemented. I kept the rollup example as is -- it is an old example and actually it doesn't create TX malleability -- the cofactor public keys map to different identities. Which ... may even be good in the rollup context, adds more obfuscation :) |
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.
Description
Fixes a signature-forgery vulnerability in
std/signature/eddsa(Verify/IsValid).Bug. The verifier never validated the public key. For a small-order public
key
A(any torsion-subgroup point, including the identity(0,1)), the term[cofactor]·[H(R,A,M)]·Avanishes, so the verification equation[cofactor]·([S]G − [H]A − R) = Ono longer depends on the secret key. Anyonecould prove a "valid" signature over an arbitrary message without the secret
key, e.g. with
A = (0,1),S = 1,R = G.Fix.
IsValidreturns 0 whenever[cofactor]A = O. This is exactly the torsion subgroup — precisely theclass for which the equation degenerates. For
[cofactor]A ≠ Otheequation is a Schnorr relation in the prime-order subgroup, so forgery
remains equivalent to discrete log. Cost: 2–3 point doublings.
AandRare on the curve. The twisted Edwardsgroup formulas use unchecked divisions which are undefined for off-curve
inputs (a vanishing denominator makes intermediate values
prover-controlled), and off-curve points would bypass the small-order
check.
Scope notes:
A = A_r + torsion) remain accepted, intentionally: thecofactored equation annihilates the torsion component, so such a key is
functionally its prime-order projection. Enforcing full subgroup membership
(
[order]A = O) would cost a full scalar multiplication for no soundnessgain.
unaffected — no breaking change for valid use.
Type of change
How has this been tested?
TestEddsaSmallOrderPublicKey: all 5 supportedcurve configurations (BN254, BLS12-381, Bandersnatch, BLS12-377, BW6-761 —
covering both cofactor-4 and cofactor-8 branches) × Groth16/PLONK. Honest
witness accepted; identity
A, order-2A, off-curveA, and off-curveRrejected.S=1,R=G, full Groth16prove/verify) no longer yields a satisfying witness.
go test ./std/signature/eddsa/ ./std/algebra/native/twistededwards/ ./examples/rollup/pass (rollup is the only in-repo caller).How has this been benchmarked?
No performance-critical paths changed.
Checklist:
Verify/IsValid)golangci-lintdoes not output errors locallyNote
High Risk
Fixes a signature-forgery vulnerability in cryptographic verification logic; behavior change for adversarial public keys but honest gnark-crypto keys remain valid.
Overview
Hardens in-circuit EdDSA verification (
std/signature/eddsa) against a forgery class where small-order public keys (e.g. identity) let anyone satisfy the cofactored equation without the secret key.IsValidnow assertsAandRare on-curve (bad inputs make the circuit unsatisfiable), factors cofactor clearing intoclearCofactor, and returns invalid when[cofactor]Ais the identity while still requiring the usual signature equation. Mixed-order keys ([sk]Gplus torsion) remain accepted; godoc calls out the divergence from gnark-crypto native verification and implications for hashing public-key coordinates.Adds circuit tests across supported curves for small-order/off-curve rejection and for mixed-order acceptance, with native
bigPointhelpers to craft witnesses.Reviewed by Cursor Bugbot for commit fa81278. Bugbot is set up for automated code reviews on this repo. Configure here.