Era

ZKsync
FoundryLayer 2
500,000 USDC
View results
Submission Details
Severity: low
Valid

Incorrect Z-coordinate usage in G2 scalar multiplication causes invalid pairing computations

Summary

In g2ScalarMul function in the EcPairing precompile, when performing scalar multiplication by 2, the function incorrectly passes the y-coordinate (yp1) instead of the z-coordinate (zp1) as the last parameter to g2JacobianDouble. This incorrect coordinate usage corrupts the resulting point computation affecting the pairing-based cryptographic operations in the ZKSync Era protocol.

Proof of Concept

https://github.com/Cyfrin/2024-10-zksync/blob/cfc1251de29379a9548eeff1eea3c78267288356/era-contracts/system-contracts/contracts/precompiles/EcPairing.yul#L683

679: function g2ScalarMul(xp0, xp1, yp0, yp1, zp0, zp1, scalar) -> xr0, xr1, yr0, yr1, zr0, zr1 {
680: let scalarBitIndex := bitLen(scalar)
681: switch scalar
682: case 0x02 {
683: @> xr0, xr1, yr0, yr1, zr0, zr1 := g2JacobianDouble(xp0, xp1, yp0, yp1, zp0, yp1)
684: }
685: default {
686: xr0 := 0
687: xr1 := 0
688: yr0 := MONTGOMERY_ONE()
689: yr1 := 0
690: zr0 := 0
691: zr1 := 0
692: for {} scalarBitIndex {} {
693: scalarBitIndex := sub(scalarBitIndex, 1)
694: xr0, xr1, yr0, yr1, zr0, zr1 := g2JacobianDouble(xr0, xr1, yr0, yr1, zr0, zr1)
695: let bitindex := checkBit(scalarBitIndex, scalar)
696: if bitindex {
697: xr0, xr1, yr0, yr1, zr0, zr1 := g2JacobianAdd(xp0, xp1, yp0, yp1, zp0, zp1, xr0, xr1, yr0, yr1, zr0, zr1)
698: }
699:
700: }
701: }
702: }

The impact of using yp1 instead of zp1 in the g2JacobianDouble call is a severe because:

In Jacobian coordinates, a point (X:Y:Z) represents the affine point:

Using the wrong Z-coordinate (yp1 instead of zp1) will result in completely incorrect affine points.

Consider a point P on the G2 curve in Jacobian coordinates (X:Y:Z) where:

P = (X:Y:Z) = (xp0,xp1 : yp0,yp1 : zp0,zp1)

When we double this point (multiply by 2), with the bug:

// BUGGY version
g2JacobianDouble(xp0, xp1, yp0, yp1, zp0, yp1) // Uses yp1 instead of zp1

Let's say we have these values:

xp0 = 1
xp1 = 2
yp0 = 3
yp1 = 4 // This will be incorrectly used as Z coordinate!
zp0 = 5
zp1 = 6 // This should have been used

In the g2JacobianDouble function, it performs calculations like:

function g2JacobianDouble(xp0, xp1, yp0, yp1, zp0, zp1) {
// Key calculations that will be wrong:
let t80, t81 := fp2Mul(yp0, yp1, zp0, zp1) // Computes Y1*Z1
zr0, zr1 := fp2Add(t80, t81, t80, t81) // Computes Z3 = 2*t8
...
}

The bug causes:

  1. Wrong Z-coordinate multiplication: Y1*Z1 uses (zp0,yp1) instead of (zp0,zp1)

  2. This affects the computation of the new Z-coordinate (Z3)

  3. Since in Jacobian coordinates (X:Y:Z) represents the affine point (X/Z², Y/Z³), the error propagates exponentially

For example:

Correct calculation would use:
Z1 = (5,6)
Y1*Z1 = (yp0,yp1)*(5,6)
Incorrect calculation uses:
Z1 = (5,4) // Wrong!
Y1*Z1 = (yp0,yp1)*(5,4)

This leads to wrong Z-coordinate in the result. When converting back to affine coordinates (X/Z², Y/Z³), the point will be completely wrong. Any subsequent pairing calculations using this point will fail or produce invalid results

The impact is magnified because:

Affine x = X/
Affine y = Y/

So the error in Z is amplified quadratically and cubically in the final affine coordinates.
Using the example above,

With correct Z = (5,6):
= (5,6= (25-36, 60) = (-11,60)
With incorrect Z = (5,4):
= (5,4= (25-16, 40) = (9,40)
This gives us completely different affine coordinates:
Correct: x_affine = X/(-11,60)
Incorrect: x_affine = X/(9,40)

This shows how a single wrong/misplaced coordinate in the Jacobian double operation leads to completely invalid results in the G2 curve arithmetic, which would break any pairing-based cryptographic operations in the protocol.

Recommendation

--- a/era-contracts/system-contracts/contracts/precompiles/EcPairing.yul
+++ b/era-contracts/system-contracts/contracts/precompiles/EcPairing.yul
@@ -682,7 +682,7 @@ object "EcPairing" {
let scalarBitIndex := bitLen(scalar)
switch scalar
case 0x02 {
- xr0, xr1, yr0, yr1, zr0, zr1 := g2JacobianDouble(xp0, xp1, yp0, yp1, zp0, yp1)
+ xr0, xr1, yr0, yr1, zr0, zr1 := g2JacobianDouble(xp0, xp1, yp0, yp1, zp0, zp1)
}
...SNIP
}
Updates

Lead Judging Commences

inallhonesty Lead Judge 7 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Valid proof will be rejected once `case 0x02` code is triggered

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.