From a8afbb5f79a0cd7c9c4e9bd6023ae91dc5d15084 Mon Sep 17 00:00:00 2001 From: IAvecilla Date: Sat, 20 Apr 2024 13:00:22 -0300 Subject: [PATCH 1/4] First implementation of subgroup check with NAF representation --- precompiles/EcPairing.yul | 54 ++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 6 deletions(-) diff --git a/precompiles/EcPairing.yul b/precompiles/EcPairing.yul index 60d237c..f0153dc 100644 --- a/precompiles/EcPairing.yul +++ b/precompiles/EcPairing.yul @@ -73,6 +73,14 @@ object "EcPairing" { ret := 4965661367192848881 } + /// @notice Constant function for the alt_bn128 curve seed (parameter `x`). + /// @dev See https://eips.ethereum.org/EIPS/eip-196 for further details. + /// @return ret The alt_bn128 curve seed. + function X_NAF() -> ret { + // NAF in binary form + // 010000001000000000010001000000000100000100100001000100010000010000000100100010001000010001000010000100010010000001000100000001 + ret := 21433887637311709106367829048077848833 + } /// @notice Constant function for decimal representation of the NAF for the Millers Loop. /// @dev Millers loop uses to iterate the NAF representation of the value t = 6x^2. Where x = 4965661367192848881 is a parameter of the BN 256 curve. @@ -489,16 +497,50 @@ object "EcPairing" { /// @param xp0, xp1 The x coordinate of the point. /// @param zp0, zp1 The z coordinate of the point. /// @return ret True if the point is in the subgroup, false otherwise. - function g2IsInSubGroup(xp0, xp1, yp0, yp1, zp0, zp1) -> ret { + function g2IsInSubGroup(xp0, xp1, yp0, yp1) -> ret { // P * X - let px_xp0, px_xp1, px_yp0, px_yp1, px_zp0, px_zp1 := g2ScalarMul(xp0, xp1, yp0, yp1, zp0, zp1, X()) + // let px_xp0, px_xp1, px_yp0, px_yp1, px_zp0, px_zp1 := g2ScalarMul(xp0, xp1, yp0, yp1, zp0, zp1, X()) + let mp00, mp01, mp10, mp11 := g2AffineNeg(xp0, xp1, yp0, yp1) + let t00, t01, t10, t11, t20, t21 := g2ProjectiveFromAffine(xp0, xp1, yp0, yp1) + let xp0_a, xp1_a, yp0_a, yp1_a, zp0_a, zp1_a := g2ProjectiveFromAffine(xp0, xp1, yp0, yp1) + // let f000, f001, f010, f011, f020, f021, f100, f101, f110, f111, f120, f121 := FP12_ONE() + let l00, l01, l10, l11, l20, l21, l30, l31, l40, l41, l50, l51 + let naf := X_NAF() + let n_iter := 63 + for {let i := 0} lt(i, n_iter) { i := add(i, 1) } { + // f000, f001, f010, f011, f020, f021, f100, f101, f110, f111, f120, f121 := fp12Square(f000, f001, f010, f011, f020, f021, f100, f101, f110, f111, f120, f121) + + l00, l01, l10, l11, l20, l21, l30, l31, l40, l41, l50, l51, t00, t01, t10, t11, t20, t21 := doubleStep(t00, t01, t10, t11, t20, t21) + // l00, l01 := fp2ScalarMul(l00, l01, yp) + // l30, l31 := fp2ScalarMul(l30, l31, xp) + // f000, f001, f010, f011, f020, f021, f100, f101, f110, f111, f120, f121 := fp12Mul(f000, f001, f010, f011, f020, f021, f100, f101, f110, f111, f120, f121, l00, l01, l10, l11, l20, l21, l30, l31, l40, l41, l50, l51) + + // naf digit = 1 + if and(naf, 1) { + l00, l01, l10, l11, l20, l21, l30, l31, l40, l41, l50, l51, t00, t01, t10, t11, t20, t21 := mixedAdditionStep(xp0, xp1, yp0, yp1, t00, t01, t10, t11, t20, t21) + // l00, l01 := fp2ScalarMul(l00, l01, yp) + // l30, l31 := fp2ScalarMul(l30, l31, xp) + // f000, f001, f010, f011, f020, f021, f100, f101, f110, f111, f120, f121 := fp12Mul(f000, f001, f010, f011, f020, f021, f100, f101, f110, f111, f120, f121, l00, l01, l10, l11, l20, l21, l30, l31, l40, l41, l50, l51) + } + + // naf digit = -1 + if and(naf, 2) { + l00, l01, l10, l11, l20, l21, l30, l31, l40, l41, l50, l51, t00, t01, t10, t11, t20, t21 := mixedAdditionStep(mp00, mp01, mp10, mp11, t00, t01, t10, t11, t20, t21) + // l00, l01 := fp2ScalarMul(l00, l01, yp) + // l30, l31 := fp2ScalarMul(l30, l31, xp) + // f000, f001, f010, f011, f020, f021, f100, f101, f110, f111, f120, f121 := fp12Mul(f000, f001, f010, f011, f020, f021, f100, f101, f110, f111, f120, f121, l00, l01, l10, l11, l20, l21, l30, l31, l40, l41, l50, l51) + } + + naf := shr(2, naf) + } + // P * (X + 1) - let px1_xp0, px1_xp1, px1_yp0, px1_yp1, px1_zp0, px1_zp1 := g2JacobianAdd(px_xp0, px_xp1, px_yp0, px_yp1, px_zp0, px_zp1, xp0, xp1, yp0, yp1, zp0, zp1) + let px1_xp0, px1_xp1, px1_yp0, px1_yp1, px1_zp0, px1_zp1 := g2JacobianAdd(t00, t01, t10, t11, t20, t21, xp0_a, xp1_a, yp0_a, yp1_a, zp0_a, zp1_a) // P * 2X - let p2x_xp0, p2x_xp1, p2x_yp0, p2x_yp1, p2x_zp0, p2x_zp1 := g2JacobianDouble(px_xp0, px_xp1, px_yp0, px_yp1, px_zp0, px_zp1) + let p2x_xp0, p2x_xp1, p2x_yp0, p2x_yp1, p2x_zp0, p2x_zp1 := g2JacobianDouble(t00, t01, t10, t11, t20, t21) // phi(P * X) - let e_px_xp0, e_px_xp1, e_px_yp0, e_px_yp1, e_px_zp0, e_px_zp1 := endomorphism(px_xp0, px_xp1, px_yp0, px_yp1, px_zp0, px_zp1) + let e_px_xp0, e_px_xp1, e_px_yp0, e_px_yp1, e_px_zp0, e_px_zp1 := endomorphism(t00, t01, t10, t11, t20, t21) // phi(phi(P * X)) let e2_px_xp0, e2_px_xp1, e2_px_yp0, e2_px_yp1, e2_px_zp0, e2_px_zp1 := endomorphism(e_px_xp0, e_px_xp1, e_px_yp0, e_px_yp1, e_px_zp0, e_px_zp1) @@ -1700,7 +1742,7 @@ object "EcPairing" { g2_y0 := intoMontgomeryForm(g2_y0) g2_y1 := intoMontgomeryForm(g2_y1) - if iszero(g2IsInSubGroup(g2_x0,g2_x1, g2_y0, g2_y1, MONTGOMERY_ONE(), 0)) { + if iszero(g2IsInSubGroup(g2_x0,g2_x1, g2_y0, g2_y1)) { burnGas() } From 70386c46e2ebd69ba39a1c41d8d9ee7c1f6cad92 Mon Sep 17 00:00:00 2001 From: Javier Chatruc Date: Sat, 20 Apr 2024 13:58:56 -0300 Subject: [PATCH 2/4] Implement NAF multiplication in a separate function --- precompiles/EcPairing.yul | 70 +++++++++++++++++++-------------------- scripts/pairing_utils.py | 45 +++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 36 deletions(-) diff --git a/precompiles/EcPairing.yul b/precompiles/EcPairing.yul index f0153dc..73eeb96 100644 --- a/precompiles/EcPairing.yul +++ b/precompiles/EcPairing.yul @@ -78,8 +78,8 @@ object "EcPairing" { /// @return ret The alt_bn128 curve seed. function X_NAF() -> ret { // NAF in binary form - // 010000001000000000010001000000000100000100100001000100010000010000000100100010001000010001000010000100010010000001000100000001 - ret := 21433887637311709106367829048077848833 + // 010000000100010000100001000100100000010001001000100010000100000001000001000100010010000100000100000000010001000000001000000001 + ret := 21356084665891114007971320526050427393 } /// @notice Constant function for decimal representation of the NAF for the Millers Loop. @@ -475,6 +475,37 @@ object "EcPairing" { xi1 := intoMontgomeryForm(10307601595873709700152284273816112264069230130616436755625194854815875713954) } + /// @notice Multiplies a given G2 point by X in NAF form. + /// @dev The given G2 point is in affine coordinates and Montgomery Form. + /// @return ret G2 Point multiplied by X in Montgomery Form. + function g2TimesXNAF(pa00, pa01, pa10, pa11) -> q00, q01, q10, q11, q20, q21 { + let pan00, pan01, pan10, pan11 := g2AffineNeg(pa00, pa01, pa10, pa11) + let p00, p01, p10, p11, p20, p21 := g2ProjectiveFromAffine(pa00, pa01, pa10, pa11) + let pn00, pn01, pn10, pn11, pn20, pn21 := g2ProjectiveFromAffine(pan00, pan01, pan10, pan11) + + q00, q01, q10, q11, q20, q21 := G2_INFINITY() + + let naf := X_NAF() + let n_iter := 63 + + for {let i := 0} lt(i, n_iter) { i := add(i, 1) } { + // naf digit = 1 + if and(naf, 1) { + q00, q01, q10, q11, q20, q21 := g2JacobianAdd(q00, q01, q10, q11, q20, q21, p00, p01, p10, p11, p20, p21) + } + + // naf digit = -1 + if and(naf, 2) { + q00, q01, q10, q11, q20, q21 := g2JacobianAdd(q00, q01, q10, q11, q20, q21, pn00, pn01, pn10, pn11, pn20, pn21) + } + + p00, p01, p10, p11, p20, p21 := g2JacobianDouble(p00, p01, p10, p11, p20, p21) + pn00, pn01, pn10, pn11, pn20, pn21 := g2JacobianDouble(pn00, pn01, pn10, pn11, pn20, pn21) + + naf := shr(2, naf) + } + } + /// @notice Frobenius endomophism used to G2 sub group check for twisted curve. /// @dev For more datail see https://eprint.iacr.org/2022/348.pdf /// @param xp0, xp1 The x coordinate of the point on twisted curve. @@ -498,41 +529,8 @@ object "EcPairing" { /// @param zp0, zp1 The z coordinate of the point. /// @return ret True if the point is in the subgroup, false otherwise. function g2IsInSubGroup(xp0, xp1, yp0, yp1) -> ret { - // P * X - // let px_xp0, px_xp1, px_yp0, px_yp1, px_zp0, px_zp1 := g2ScalarMul(xp0, xp1, yp0, yp1, zp0, zp1, X()) - let mp00, mp01, mp10, mp11 := g2AffineNeg(xp0, xp1, yp0, yp1) - let t00, t01, t10, t11, t20, t21 := g2ProjectiveFromAffine(xp0, xp1, yp0, yp1) let xp0_a, xp1_a, yp0_a, yp1_a, zp0_a, zp1_a := g2ProjectiveFromAffine(xp0, xp1, yp0, yp1) - // let f000, f001, f010, f011, f020, f021, f100, f101, f110, f111, f120, f121 := FP12_ONE() - let l00, l01, l10, l11, l20, l21, l30, l31, l40, l41, l50, l51 - let naf := X_NAF() - let n_iter := 63 - for {let i := 0} lt(i, n_iter) { i := add(i, 1) } { - // f000, f001, f010, f011, f020, f021, f100, f101, f110, f111, f120, f121 := fp12Square(f000, f001, f010, f011, f020, f021, f100, f101, f110, f111, f120, f121) - - l00, l01, l10, l11, l20, l21, l30, l31, l40, l41, l50, l51, t00, t01, t10, t11, t20, t21 := doubleStep(t00, t01, t10, t11, t20, t21) - // l00, l01 := fp2ScalarMul(l00, l01, yp) - // l30, l31 := fp2ScalarMul(l30, l31, xp) - // f000, f001, f010, f011, f020, f021, f100, f101, f110, f111, f120, f121 := fp12Mul(f000, f001, f010, f011, f020, f021, f100, f101, f110, f111, f120, f121, l00, l01, l10, l11, l20, l21, l30, l31, l40, l41, l50, l51) - - // naf digit = 1 - if and(naf, 1) { - l00, l01, l10, l11, l20, l21, l30, l31, l40, l41, l50, l51, t00, t01, t10, t11, t20, t21 := mixedAdditionStep(xp0, xp1, yp0, yp1, t00, t01, t10, t11, t20, t21) - // l00, l01 := fp2ScalarMul(l00, l01, yp) - // l30, l31 := fp2ScalarMul(l30, l31, xp) - // f000, f001, f010, f011, f020, f021, f100, f101, f110, f111, f120, f121 := fp12Mul(f000, f001, f010, f011, f020, f021, f100, f101, f110, f111, f120, f121, l00, l01, l10, l11, l20, l21, l30, l31, l40, l41, l50, l51) - } - - // naf digit = -1 - if and(naf, 2) { - l00, l01, l10, l11, l20, l21, l30, l31, l40, l41, l50, l51, t00, t01, t10, t11, t20, t21 := mixedAdditionStep(mp00, mp01, mp10, mp11, t00, t01, t10, t11, t20, t21) - // l00, l01 := fp2ScalarMul(l00, l01, yp) - // l30, l31 := fp2ScalarMul(l30, l31, xp) - // f000, f001, f010, f011, f020, f021, f100, f101, f110, f111, f120, f121 := fp12Mul(f000, f001, f010, f011, f020, f021, f100, f101, f110, f111, f120, f121, l00, l01, l10, l11, l20, l21, l30, l31, l40, l41, l50, l51) - } - - naf := shr(2, naf) - } + let t00, t01, t10, t11, t20, t21 := g2TimesXNAF(xp0, xp1, yp0, yp1) // P * (X + 1) let px1_xp0, px1_xp1, px1_yp0, px1_yp1, px1_zp0, px1_zp1 := g2JacobianAdd(t00, t01, t10, t11, t20, t21, xp0_a, xp1_a, yp0_a, yp1_a, zp0_a, zp1_a) diff --git a/scripts/pairing_utils.py b/scripts/pairing_utils.py index ed964de..44facde 100644 --- a/scripts/pairing_utils.py +++ b/scripts/pairing_utils.py @@ -25,3 +25,48 @@ def is_in_twisted_curve(x0, x1, y0, y1): b = fp2.add(*b, *TWISTED_CURVE_COEFFS) c = fp2.exp(y0, y1, 2) return b == c + +def naf(E): + Z = [] + i = 0 + while E > 0: + if E % 2 == 1: + zi = 2 - (E % 4) + E -= zi + else: + zi = 0 + E //= 2 + i += 1 + Z.append(zi) + Z.reverse() + return Z + +def naf_aux(naf): + a = [] + for n in naf: + if n == 0: + a.append("00") + elif n == 1: + a.append("01") + elif n == -1: + a.append("10") + b = "".join(a) + return b + +def naf_aux_to_naf(naf_aux): + naf = [] + for i in range(0, len(naf_aux), 2): + if naf_aux[i] == "0" and naf_aux[i+1] == "0": + naf.append(0) + elif naf_aux[i] == "0" and naf_aux[i+1] == "1": + naf.append(1) + elif naf_aux[i] == "1" and naf_aux[i+1] == "0": + naf.append(-1) + return naf + +x_naf = naf(4965661367192848881) +print(x_naf) +naf_yul_rep = naf_aux(x_naf) +print(naf_yul_rep) +original_naf = naf_aux_to_naf(naf_yul_rep) +print(original_naf) From 18c2fb04d2ad386c62da0045acac38b45ef35123 Mon Sep 17 00:00:00 2001 From: Javier Chatruc Date: Sat, 20 Apr 2024 14:12:06 -0300 Subject: [PATCH 3/4] Optimize a bit further, removing some double operations --- precompiles/EcPairing.yul | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/precompiles/EcPairing.yul b/precompiles/EcPairing.yul index 73eeb96..ef5d445 100644 --- a/precompiles/EcPairing.yul +++ b/precompiles/EcPairing.yul @@ -459,6 +459,21 @@ object "EcPairing" { ny0, ny1 := fp2Neg(y0, y1) } + /// @notice Negates a G2 point in Jacobian coordinates. + /// @dev The coordinates are encoded in Montgomery form. + /// @dev The negation of a point (x, y, z) is (x, -y, z). + /// @param x0, x1 The x coordinate of the point. + /// @param y0, y1 The y coordinate of the point. + /// @param z0, z1 The z coordinate of the point. + /// @return nx0, nx1, ny0, ny1, nz0, nz1 The coordinates of the negated point. + function g2JacobianNeg(x0, x1, y0, y1, z0, z1) -> nx0, nx1, ny0, ny1, nz0, nz1 { + nx0 := x0 + nx1 := x1 + ny0, ny1 := fp2Neg(y0, y1) + nz0 := z0 + nz1 := z1 + } + /// @notice Constant function for the alt_bn128 returning `(xi)^ ((N - 1) // 2)`. Where `xi` is D-type twist param. /// @dev See https://eprint.iacr.org/2022/352.pdf (2 Preliminaries) for further details. /// @return ret Twisted curve `xi2 = (xi)^ ((N - 1) // 2)` value in Montgomery form. @@ -479,9 +494,7 @@ object "EcPairing" { /// @dev The given G2 point is in affine coordinates and Montgomery Form. /// @return ret G2 Point multiplied by X in Montgomery Form. function g2TimesXNAF(pa00, pa01, pa10, pa11) -> q00, q01, q10, q11, q20, q21 { - let pan00, pan01, pan10, pan11 := g2AffineNeg(pa00, pa01, pa10, pa11) let p00, p01, p10, p11, p20, p21 := g2ProjectiveFromAffine(pa00, pa01, pa10, pa11) - let pn00, pn01, pn10, pn11, pn20, pn21 := g2ProjectiveFromAffine(pan00, pan01, pan10, pan11) q00, q01, q10, q11, q20, q21 := G2_INFINITY() @@ -496,11 +509,11 @@ object "EcPairing" { // naf digit = -1 if and(naf, 2) { + let pn00, pn01, pn10, pn11, pn20, pn21 := g2JacobianNeg(p00, p01, p10, p11, p20, p21) q00, q01, q10, q11, q20, q21 := g2JacobianAdd(q00, q01, q10, q11, q20, q21, pn00, pn01, pn10, pn11, pn20, pn21) } p00, p01, p10, p11, p20, p21 := g2JacobianDouble(p00, p01, p10, p11, p20, p21) - pn00, pn01, pn10, pn11, pn20, pn21 := g2JacobianDouble(pn00, pn01, pn10, pn11, pn20, pn21) naf := shr(2, naf) } From 4ac1efb4f32bc895a68d8ca637fe4d57cbc4bcb7 Mon Sep 17 00:00:00 2001 From: Javier Chatruc Date: Sat, 20 Apr 2024 14:28:11 -0300 Subject: [PATCH 4/4] Remove a call to fromAffineCoordinates --- precompiles/EcPairing.yul | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/precompiles/EcPairing.yul b/precompiles/EcPairing.yul index ef5d445..9f18ba8 100644 --- a/precompiles/EcPairing.yul +++ b/precompiles/EcPairing.yul @@ -491,11 +491,9 @@ object "EcPairing" { } /// @notice Multiplies a given G2 point by X in NAF form. - /// @dev The given G2 point is in affine coordinates and Montgomery Form. - /// @return ret G2 Point multiplied by X in Montgomery Form. - function g2TimesXNAF(pa00, pa01, pa10, pa11) -> q00, q01, q10, q11, q20, q21 { - let p00, p01, p10, p11, p20, p21 := g2ProjectiveFromAffine(pa00, pa01, pa10, pa11) - + /// @dev The given G2 point is in Jacobian Coordinates coordinates and Montgomery Form. + /// @return ret G2 Point multiplied by X in Jacobian Coordinates and Montgomery Form. + function g2TimesXNAF(p00, p01, p10, p11, p20, p21) -> q00, q01, q10, q11, q20, q21 { q00, q01, q10, q11, q20, q21 := G2_INFINITY() let naf := X_NAF() @@ -543,7 +541,7 @@ object "EcPairing" { /// @return ret True if the point is in the subgroup, false otherwise. function g2IsInSubGroup(xp0, xp1, yp0, yp1) -> ret { let xp0_a, xp1_a, yp0_a, yp1_a, zp0_a, zp1_a := g2ProjectiveFromAffine(xp0, xp1, yp0, yp1) - let t00, t01, t10, t11, t20, t21 := g2TimesXNAF(xp0, xp1, yp0, yp1) + let t00, t01, t10, t11, t20, t21 := g2TimesXNAF(xp0_a, xp1_a, yp0_a, yp1_a, zp0_a, zp1_a) // P * (X + 1) let px1_xp0, px1_xp1, px1_yp0, px1_yp1, px1_zp0, px1_zp1 := g2JacobianAdd(t00, t01, t10, t11, t20, t21, xp0_a, xp1_a, yp0_a, yp1_a, zp0_a, zp1_a)