diff --git a/benchmarks/bench_ec_g1.nim b/benchmarks/bench_ec_g1.nim index 3925474..54b962f 100644 --- a/benchmarks/bench_ec_g1.nim +++ b/benchmarks/bench_ec_g1.nim @@ -48,6 +48,7 @@ proc main() = addBench(ECP_ShortW_Proj[Fp[curve], NotOnTwist], Iters) addBench(ECP_ShortW_Jac[Fp[curve], NotOnTwist], Iters) mixedAddBench(ECP_ShortW_Proj[Fp[curve], NotOnTwist], Iters) + mixedAddBench(ECP_ShortW_Jac[Fp[curve], NotOnTwist], Iters) doublingBench(ECP_ShortW_Proj[Fp[curve], NotOnTwist], Iters) doublingBench(ECP_ShortW_Jac[Fp[curve], NotOnTwist], Iters) separator() diff --git a/benchmarks/bench_ec_g2.nim b/benchmarks/bench_ec_g2.nim index 22a4829..d737db5 100644 --- a/benchmarks/bench_ec_g2.nim +++ b/benchmarks/bench_ec_g2.nim @@ -49,6 +49,7 @@ proc main() = addBench(ECP_ShortW_Proj[Fp2[curve], OnTwist], Iters) addBench(ECP_ShortW_Jac[Fp2[curve], OnTwist], Iters) mixedAddBench(ECP_ShortW_Proj[Fp2[curve], OnTwist], Iters) + mixedAddBench(ECP_ShortW_Jac[Fp2[curve], OnTwist], Iters) doublingBench(ECP_ShortW_Proj[Fp2[curve], OnTwist], Iters) doublingBench(ECP_ShortW_Jac[Fp2[curve], OnTwist], Iters) separator() diff --git a/benchmarks/bench_elliptic_template.nim b/benchmarks/bench_elliptic_template.nim index cd60a7b..6870855 100644 --- a/benchmarks/bench_elliptic_template.nim +++ b/benchmarks/bench_elliptic_template.nim @@ -17,7 +17,11 @@ import ../constantine/config/[curves, common], ../constantine/arithmetic, ../constantine/io/io_bigints, - ../constantine/elliptic/[ec_shortweierstrass_affine, ec_shortweierstrass_projective, ec_scalar_mul, ec_endomorphism_accel], + ../constantine/elliptic/[ + ec_shortweierstrass_affine, + ec_shortweierstrass_projective, + ec_shortweierstrass_jacobian, + ec_scalar_mul, ec_endomorphism_accel], # Helpers ../helpers/[prng_unsafe, static_for], ./platforms, @@ -64,7 +68,10 @@ proc mixedAddBench*(T: typedesc, iters: int) = let P = rng.random_unsafe(T) let Q = rng.random_unsafe(T) var Qaff: ECP_ShortW_Aff[T.F, T.Tw] - Qaff.affineFromProjective(Q) + when Q is ECP_ShortW_Proj: + Qaff.affineFromProjective(Q) + else: + Qaff.affineFromJacobian(Q) bench("EC Mixed Addition " & G1_or_G2, T, iters): r.madd(P, Qaff) diff --git a/constantine.nimble b/constantine.nimble index 7a257a8..9a7236d 100644 --- a/constantine.nimble +++ b/constantine.nimble @@ -75,7 +75,7 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[ # ("tests/t_ec_shortw_jac_g1_mul_sanity.nim", false), # ("tests/t_ec_shortw_jac_g1_mul_distri.nim", false), ("tests/t_ec_shortw_jac_g1_mul_vs_ref.nim", false), - # mixed_add + ("tests/t_ec_shortw_jac_g1_mixed_add.nim", false), # Elliptic curve arithmetic G2 # ---------------------------------------------------------- @@ -107,25 +107,25 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[ # ("tests/t_ec_shortw_jac_g2_mul_sanity_bn254_snarks.nim", false), # ("tests/t_ec_shortw_jac_g2_mul_distri_bn254_snarks.nim", false), ("tests/t_ec_shortw_jac_g2_mul_vs_ref_bn254_snarks.nim", false), - # mixed_add + ("tests/t_ec_shortw_jac_g2_mixed_add_bn254_snarks.nim", false), # ("tests/t_ec_shortw_jac_g2_add_double_bls12_381.nim", false), # ("tests/t_ec_shortw_jac_g2_mul_sanity_bls12_381.nim", false), # ("tests/t_ec_shortw_jac_g2_mul_distri_bls12_381.nim", false), ("tests/t_ec_shortw_jac_g2_mul_vs_ref_bls12_381.nim", false), - # mixed_add + ("tests/t_ec_shortw_jac_g2_mixed_add_bls12_381.nim", false), # ("tests/t_ec_shortw_jac_g2_add_double_bls12_377.nim", false), # ("tests/t_ec_shortw_jac_g2_mul_sanity_bls12_377.nim", false), # ("tests/t_ec_shortw_jac_g2_mul_distri_bls12_377.nim", false), ("tests/t_ec_shortw_jac_g2_mul_vs_ref_bls12_377.nim", false), - # mixed_add + ("tests/t_ec_shortw_jac_g2_mixed_add_bls12_377.nim", false), # ("tests/t_ec_shortw_jac_g2_add_double_bw6_761.nim", false), # ("tests/t_ec_shortw_jac_g2_mul_sanity_bw6_761.nim", false), # ("tests/t_ec_shortw_jac_g2_mul_distri_bw6_761.nim", false), ("tests/t_ec_shortw_jac_g2_mul_vs_ref_bw6_761.nim", false), - # mixed_add + ("tests/t_ec_shortw_jac_g2_mixed_add_bw6_761.nim", false), # Elliptic curve arithmetic vs Sagemath # ---------------------------------------------------------- diff --git a/constantine/arithmetic/limbs.nim b/constantine/arithmetic/limbs.nim index cb46557..01d3342 100644 --- a/constantine/arithmetic/limbs.nim +++ b/constantine/arithmetic/limbs.nim @@ -63,13 +63,14 @@ when UseASM_X86_64: func setZero*(a: var Limbs) = ## Set ``a`` to 0 - zeroMem(a[0].addr, sizeof(a)) + for i in 0 ..< a.len: + a[i] = Zero func setOne*(a: var Limbs) = ## Set ``a`` to 1 a[0] = One - when a.len > 1: - zeroMem(a[1].addr, (a.len - 1) * sizeof(SecretWord)) + for i in 1 ..< a.len: + a[i] = Zero func setUint*(a: var Limbs, n: SomeUnsignedInt) = ## set ``a`` to an unsigned integer ``n`` diff --git a/constantine/elliptic/ec_shortweierstrass_affine.nim b/constantine/elliptic/ec_shortweierstrass_affine.nim index 6d4aa29..71f7fc4 100644 --- a/constantine/elliptic/ec_shortweierstrass_affine.nim +++ b/constantine/elliptic/ec_shortweierstrass_affine.nim @@ -32,6 +32,16 @@ type ## over a field F x*, y*: F +func isInf*(P: ECP_ShortW_Aff): SecretBool = + ## Returns true if P is an infinity point + ## and false otherwise + ## + ## Note: the jacobian coordinates equation is + ## Y² = X³ + aXZ⁴ + bZ⁶ + ## A "zero" point is any point with coordinates X and Z = 0 + ## Y can be anything + result = P.x.isZero() and P.y.isZero() + func curve_eq_rhs*[F](y2: var F, x: F, Tw: static Twisted) = ## Compute the curve equation right-hand-side from field element `x` ## i.e. `y²` in `y² = x³ + a x + b` diff --git a/constantine/elliptic/ec_shortweierstrass_jacobian.nim b/constantine/elliptic/ec_shortweierstrass_jacobian.nim index ffc81ee..5883cc6 100644 --- a/constantine/elliptic/ec_shortweierstrass_jacobian.nim +++ b/constantine/elliptic/ec_shortweierstrass_jacobian.nim @@ -291,6 +291,85 @@ func sum*[F; Tw: static Twisted]( r.ccopy(Q, P.isInf()) r.ccopy(P, Q.isInf()) +func madd*[F; Tw: static Twisted]( + r: var ECP_ShortW_Jac[F, Tw], + P: ECP_ShortW_Jac[F, Tw], + Q: ECP_ShortW_Aff[F, Tw] + ) = + ## Elliptic curve mixed addition for Short Weierstrass curves + ## with p in Jacobian coordinates and Q in affine coordinates + ## + ## R = P + Q + # "madd-2007-bl" mixed addition formula - https://hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-madd-2007-bl + # with conditional copies to handle infinity points + # Assumptions: Z2=1. + # Cost: 7M + 4S + 9add + 3*2 + 1*4. + # Source: 2007 Bernstein–Lange. + # Explicit formulas: + # + # Z1Z1 = Z1² + # U2 = X2*Z1Z1 + # S2 = Y2*Z1*Z1Z1 + # H = U2-X1 + # HH = H2 + # I = 4*HH + # J = H*I + # r = 2*(S2-Y1) + # V = X1*I + # X3 = r²-J-2*V + # Y3 = r*(V-X3)-2*Y1*J + # Z3 = (Z1+H)²-Z1Z1-HH + var Z1Z1 {.noInit.}, H {.noInit.}, HH {.noInit.}, I{.noInit.}, J {.noInit.}: F + + # Preload P and Q in cache + let pIsInf = P.isInf() + let qIsInf = Q.isInf() + + Z1Z1.square(P.z) # Z₁Z₁ = Z₁² + r.z.prod(P.z, Z1Z1) # P.Z is hot in cache, keep it in same register. + r.z *= Q.y # S₂ = Y₂Z₁Z₁Z₁ -- r.z used as S₂ + + H.prod(Q.x, Z1Z1) # U₂ = X₂Z₁Z₁ + H -= P.x # H = U₂ - X₁ + + HH.square(H) # HH = H² + + I.double(HH) + I.double() # I = 4HH + + J.prod(H, I) # J = H*I + r.y.prod(P.x, I) # V = X₁*I -- r.y used as V + + r.z -= P.y # + r.z.double() # r = 2*(S₂-Y₁) -- r.z used as r + + r.x.square(r.z) # r² + r.x -= J + r.x -= r.y + r.x -= r.y # X₃ = r²-J-2*V -- r.x computed + + r.y -= r.x # V-X₃ + r.y *= r.z # r*(V-X₃) + + J *= P.y # Y₁J -- J reused as Y₁J + r.y -= J + r.y -= J # Y₃ = r*(V-X₃) - 2*Y₁J -- r.y computed + + r.z.sum(P.z, H) # Z₁ + H + r.z.square() + r.z -= Z1Z1 + r.z -= HH # Z₃ = (Z1+H)²-Z1Z1-HH + + # Now handle points at infinity + proc one(): F = + result.setOne() + + r.x.ccopy(Q.x, pIsInf) + r.y.ccopy(Q.y, pIsInf) + r.z.ccopy(static(one()), pIsInf) + + r.ccopy(P, qIsInf) + func double*[F; Tw: static Twisted]( r: var ECP_ShortW_Jac[F, Tw], P: ECP_ShortW_Jac[F, Tw] @@ -373,13 +452,13 @@ func diff*(r: var ECP_ShortW_Jac, func affineFromJacobian*[F; Tw]( aff: var ECP_ShortW_Aff[F, Tw], jac: ECP_ShortW_Jac[F, Tw]) = - var invZ {.noInit.}, invZ2: F + var invZ {.noInit.}, invZ2{.noInit.}: F invZ.inv(jac.z) invZ2.square(invZ) aff.x.prod(jac.x, invZ2) aff.y.prod(jac.y, invZ) - aff.y.prod(jac.y, invZ2) + aff.y *= invZ2 func jacobianFromAffine*[F; Tw]( jac: var ECP_ShortW_Jac[F, Tw], diff --git a/tests/t_ec_shortw_jac_g1_mixed_add.nim b/tests/t_ec_shortw_jac_g1_mixed_add.nim new file mode 100644 index 0000000..e90356b --- /dev/null +++ b/tests/t_ec_shortw_jac_g1_mixed_add.nim @@ -0,0 +1,42 @@ +# Constantine +# Copyright (c) 2018-2019 Status Research & Development GmbH +# Copyright (c) 2020-Present Mamy André-Ratsimbazafy +# Licensed and distributed under either of +# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT). +# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). +# at your option. This file may not be copied, modified, or distributed except according to those terms. + +import + # Internals + ../constantine/config/curves, + ../constantine/elliptic/ec_shortweierstrass_jacobian, + ../constantine/arithmetic, + # Test utilities + ./t_ec_template + +const + Iters = 12 + +run_EC_mixed_add_impl( + ec = ECP_ShortW_Jac[Fp[BN254_Snarks], NotOnTwist], + Iters = Iters, + moduleName = "test_ec_shortweierstrass_jacobian_mixed_add_" & $BN254_Snarks + ) + +run_EC_mixed_add_impl( + ec = ECP_ShortW_Jac[Fp[BLS12_381], NotOnTwist], + Iters = Iters, + moduleName = "test_ec_shortweierstrass_jacobian_mixed_add_" & $BLS12_381 + ) + +run_EC_mixed_add_impl( + ec = ECP_ShortW_Jac[Fp[BLS12_377], NotOnTwist], + Iters = Iters, + moduleName = "test_ec_shortweierstrass_jacobian_mixed_add_" & $BLS12_377 + ) + +run_EC_mixed_add_impl( + ec = ECP_ShortW_Jac[Fp[BW6_761], NotOnTwist], + Iters = Iters, + moduleName = "test_ec_shortweierstrass_jacobian_mixed_add_" & $BW6_761 + ) diff --git a/tests/t_ec_template.nim b/tests/t_ec_template.nim index 6df17d0..aa128df 100644 --- a/tests/t_ec_template.nim +++ b/tests/t_ec_template.nim @@ -441,7 +441,10 @@ proc run_EC_mixed_add_impl*( let a = rng.random_point(EC, randZ, gen) let b = rng.random_point(EC, randZ, gen) var bAff: ECP_ShortW_Aff[EC.F, EC.Tw] - bAff.affineFromProjective(b) + when b is ECP_ShortW_Proj: + bAff.affineFromProjective(b) + else: + bAff.affineFromJacobian(b) var r_generic, r_mixed: EC