Skip to content

crypto/bn256/google: fix receiver aliasing in point doubling - #35558

Open
4waan wants to merge 3 commits into
ethereum:masterfrom
4waan:bn256-google-double-aliasing
Open

crypto/bn256/google: fix receiver aliasing in point doubling#35558
4waan wants to merge 3 commits into
ethereum:masterfrom
4waan:bn256-google-double-aliasing

Conversation

@4waan

@4waan 4waan commented Aug 20, 2026

Copy link
Copy Markdown

curvePoint.Double and twistPoint.Double in the google bn256 backend break when the receiver aliases the operand. g.Add(g, g) comes back off the curve, in both G1 and G2.

The bug

Both functions end like this:

c.y.Sub(t2, t)             // writes c.y

t.Mul(a.y, a.z, pool)      // reads a.y
c.z.Add(t, t)

z3 = 2*y1*z1 is the last use of a.y, and it runs after c.y is overwritten. When c and a are the same point, a.y holds y3 by then, so you get z3 = 2*y3*z1.

Add hands both-operands-equal straight to Double (if xEqual && yEqual { c.Double(a, pool); return }), so g.Add(g, g) walks right into it. On master:

base := new(G2).ScalarBaseMult(big.NewInt(7))
want := new(G2).ScalarMult(base, big.NewInt(2)).Marshal()

new(G2).Add(base, base)   // == want
base.Add(base, base)      // != want, and not on the curve
want (ScalarMult by 2): 2338fa80...2b02bf
distinct receiver:      2338fa80...2b02bf
aliased receiver:       274284fa...b926b

IsOnCurve rejects the last one in both groups. The fix computes c.z before writing c.y, which is what cloudflare already does. Statement move, formulas untouched, gnark unaffected.

Scope

Every curvePoint / twistPoint operation, swept for receiver aliasing on master:

operation aliased receiver
curvePoint.Double(c, c) broken
twistPoint.Double(c, c) broken
Add(c, c, c) (G1 and G2) broken, via Double
twistPoint.Negative(c, c) broken (SetZero before reading a.y)
Add(c, b) / Add(a, c), distinct points ok
Add(a, a) into a distinct receiver ok
curvePoint.Negative ok (big.Int.Neg is alias-safe)
Mul with an aliased receiver ok

twistPoint.Negative is the same defect and a one-line change, so it is fixed here too. Its only caller passes distinct arguments, so it was latent.

Add itself is fine as written: c.x and c.y are written after the last reads of a.x/a.y, and c.z comes last from a.z/b.z, which the earlier writes do not touch.

The BUG(agl) notes

G1.Add and G2.Add carry // BUG(agl): this function is not complete: a==b fails. Inherited from upstream, and never true here: the doubling branch has been in Add since the code was vendored in 10a57fc, so a == b worked whenever the receiver was distinct. What failed was aliasing, which the note does not mention. cloudflare has the same branch and no such note. Replaced with the aliasing guarantee, happy to drop that hunk.

No consensus impact

all of the 49 bn256 vectors in core/vm/testdata/precompiles/hit their expected output through this backend.. the SHA-256 over every output byte does not move

pre-fix:  9899b86172b75e79c2d399d2f3b118ab5ef2a5a4630e9d8cdbc777a503fa9e88
post-fix: 9899b86172b75e79c2d399d2f3b118ab5ef2a5a4630e9d8cdbc777a503fa9e88

Not included

Add's doubling branch returns early without putting back its pooled temporaries: 10 leaked big.Ints in curvePoint.Add, 20 in twistPoint.Add, per call. Plain free list, so it costs allocations and nothing else, and it is unrelated to this fix. Here or in a follow-up, your call.

curvePoint.Double and twistPoint.Double compute z3 = 2*y1*z1 after they
have already written c.y. When the receiver aliases the operand that
reads back y3 instead of y1, so z3 is wrong and the result is not on the
curve. Add routes to Double whenever both operands are the same point,
so g.Add(g, g) returned garbage in both G1 and G2. Doubling into a
separate receiver was already correct, which is why the existing tests,
the cross-implementation fuzzer and the ECADD precompile all missed it.
Reorder to match the cloudflare backend.. the formulas are unchanged.

twistPoint.Negative had the same bug, zeroing c.y before reading a.y.
Its only caller passes distinct arguments, so nothing depended on it.

The BUG(agl) notes on G1.Add and G2.Add describe an a==b limitation that
never applied here: Add has carried the doubling branch since this code
was vendored, and cloudflare has that branch and no such note.

No consensus impact: ECADD uses fresh points for both operands and the
result, and crypto/bn256 resolves to gnark on amd64/arm64.
@4waan
4waan requested review from fjl and gballet as code owners August 20, 2026 18:23
@4waan

4waan commented Aug 23, 2026

Copy link
Copy Markdown
Author

since theres not much active work going on in this part @gballet could you just do a quick review it'd be really helpful for me to continue a few other work around this!

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.

1 participant