From 0e4dbfe40010c6f4d3e03b3942ce093bf912f317 Mon Sep 17 00:00:00 2001 From: Mamy Ratsimbazafy Date: Sun, 27 Sep 2020 09:15:14 +0200 Subject: [PATCH] BLS12-377 (#91) * add Sage for constant time tonelli shanks * Fused sqrt and invsqrt via Tonelli Shanks * isolate sqrt in their own folder * Implement constant-time Tonelli Shanks for any prime * Implement Fp2 sqrt for any non-residue * Add tests for BLS12_377 * Lattice decomposition script for BLS12_377 G1 * BLS12-377 G1 GLV ok, G2 GLV issue * Proper endomorphism acceleration support for BLS12-377 * Add naive pairing support for BLS12-377 * Activate more bench for BLS12-377 * Fix MSB computation * Optimize final exponentiation + add benches --- benchmarks/bench_ec_g1.nim | 8 +- benchmarks/bench_ec_g2.nim | 8 +- benchmarks/bench_fp.nim | 7 +- benchmarks/bench_fp12.nim | 2 +- benchmarks/bench_fp2.nim | 4 +- benchmarks/bench_fp6.nim | 2 +- benchmarks/bench_pairing_bls12_377.nim | 51 +++ constantine.nimble | 28 +- constantine/arithmetic.nim | 12 +- constantine/arithmetic/bigints.nim | 17 +- constantine/arithmetic/finite_fields.nim | 148 +------ .../arithmetic/finite_fields_square_root.nim | 269 +++++++++++++ constantine/arithmetic/limbs.nim | 4 + constantine/config/curves_declaration.nim | 3 + .../elliptic/ec_endomorphism_accel.nim | 8 +- .../elliptic/ec_endomorphism_params.nim | 55 +++ constantine/elliptic/ec_scalar_mul.nim | 2 +- constantine/hash_to_curve/cofactors.nim | 16 +- constantine/io/io_fields.nim | 8 +- constantine/isogeny/frobenius.nim | 80 ++++ constantine/pairing/pairing_bls12.nim | 86 +++- constantine/pairing/pairing_bn.nim | 19 +- .../exponentiations.nim | 20 +- sage/curve_family_bls12.sage | 3 + sage/frobenius_bls12_377.sage | 22 +- sage/lattice_decomposition_bls12_377_g1.sage | 223 +++++++++++ sage/lattice_decomposition_bls12_377_g2.sage | 375 ++++++++++++++++++ sage/lattice_decomposition_bls12_381_g1.sage | 20 +- sage/lattice_decomposition_bls12_381_g2.sage | 60 ++- ...lattice_decomposition_bn254_snarks_g1.sage | 16 +- ...lattice_decomposition_bn254_snarks_g2.sage | 16 +- sage/square_root_bls12_377.sage | 263 ++++++++++++ sage/testgen_bls12_377.sage | 109 +++++ tests/t_ec_sage_bls12_377.nim | 331 ++++++++++++++++ tests/t_ec_wstrass_prj_g1_add_double.nim | 6 + tests/t_ec_wstrass_prj_g1_mixed_add.nim | 6 + tests/t_ec_wstrass_prj_g1_mul_distri.nim | 6 + tests/t_ec_wstrass_prj_g1_mul_sanity.nim | 6 + tests/t_ec_wstrass_prj_g1_mul_vs_ref.nim | 6 + ...ec_wstrass_prj_g2_add_double_bls12_377.nim | 29 ++ ..._ec_wstrass_prj_g2_mixed_add_bls12_377.nim | 24 ++ ...ec_wstrass_prj_g2_mul_distri_bls12_377.nim | 31 ++ ...ec_wstrass_prj_g2_mul_sanity_bls12_377.nim | 62 +++ ...ec_wstrass_prj_g2_mul_vs_ref_bls12_377.nim | 31 ++ tests/t_finite_fields_sqrt.nim | 39 +- tests/t_fp12_frobenius.nim | 2 +- tests/t_fp2_sqrt.nim | 12 +- tests/t_pairing_bls12_377_optate.nim | 16 + tests/t_pairing_cyclotomic_fp12.nim | 6 +- tests/t_pairing_mul_fp12_by_lines.nim | 1 + 50 files changed, 2315 insertions(+), 263 deletions(-) create mode 100644 benchmarks/bench_pairing_bls12_377.nim create mode 100644 constantine/arithmetic/finite_fields_square_root.nim create mode 100644 sage/lattice_decomposition_bls12_377_g1.sage create mode 100644 sage/lattice_decomposition_bls12_377_g2.sage create mode 100644 sage/square_root_bls12_377.sage create mode 100644 sage/testgen_bls12_377.sage create mode 100644 tests/t_ec_sage_bls12_377.nim create mode 100644 tests/t_ec_wstrass_prj_g2_add_double_bls12_377.nim create mode 100644 tests/t_ec_wstrass_prj_g2_mixed_add_bls12_377.nim create mode 100644 tests/t_ec_wstrass_prj_g2_mul_distri_bls12_377.nim create mode 100644 tests/t_ec_wstrass_prj_g2_mul_sanity_bls12_377.nim create mode 100644 tests/t_ec_wstrass_prj_g2_mul_vs_ref_bls12_377.nim create mode 100644 tests/t_pairing_bls12_377_optate.nim diff --git a/benchmarks/bench_ec_g1.nim b/benchmarks/bench_ec_g1.nim index 1b4f284..6376d1a 100644 --- a/benchmarks/bench_ec_g1.nim +++ b/benchmarks/bench_ec_g1.nim @@ -35,7 +35,7 @@ const AvailableCurves = [ # Curve25519, # P256, # Secp256k1, - # BLS12_377, + BLS12_377, BLS12_381, # BN446, # FKM12_447, @@ -48,23 +48,17 @@ proc main() = staticFor i, 0, AvailableCurves.len: const curve = AvailableCurves[i] addBench(ECP_SWei_Proj[Fp[curve]], Iters) - separator() mixedAddBench(ECP_SWei_Proj[Fp[curve]], Iters) - separator() doublingBench(ECP_SWei_Proj[Fp[curve]], Iters) separator() scalarMulUnsafeDoubleAddBench(ECP_SWei_Proj[Fp[curve]], MulIters) separator() scalarMulGenericBench(ECP_SWei_Proj[Fp[curve]], window = 2, MulIters) - separator() scalarMulGenericBench(ECP_SWei_Proj[Fp[curve]], window = 3, MulIters) - separator() scalarMulGenericBench(ECP_SWei_Proj[Fp[curve]], window = 4, MulIters) - separator() scalarMulGenericBench(ECP_SWei_Proj[Fp[curve]], window = 5, MulIters) separator() scalarMulEndo(ECP_SWei_Proj[Fp[curve]], MulIters) - separator() scalarMulEndoWindow(ECP_SWei_Proj[Fp[curve]], MulIters) separator() separator() diff --git a/benchmarks/bench_ec_g2.nim b/benchmarks/bench_ec_g2.nim index 01dd424..3054107 100644 --- a/benchmarks/bench_ec_g2.nim +++ b/benchmarks/bench_ec_g2.nim @@ -36,7 +36,7 @@ const AvailableCurves = [ # Curve25519, # P256, # Secp256k1, - # BLS12_377, + BLS12_377, BLS12_381, # BN446, # FKM12_447, @@ -49,23 +49,19 @@ proc main() = staticFor i, 0, AvailableCurves.len: const curve = AvailableCurves[i] addBench(ECP_SWei_Proj[Fp2[curve]], Iters) - separator() mixedAddBench(ECP_SWei_Proj[Fp2[curve]], Iters) - separator() doublingBench(ECP_SWei_Proj[Fp2[curve]], Iters) separator() scalarMulUnsafeDoubleAddBench(ECP_SWei_Proj[Fp2[curve]], MulIters) separator() scalarMulGenericBench(ECP_SWei_Proj[Fp2[curve]], window = 2, MulIters) - separator() scalarMulGenericBench(ECP_SWei_Proj[Fp2[curve]], window = 3, MulIters) - separator() scalarMulGenericBench(ECP_SWei_Proj[Fp2[curve]], window = 4, MulIters) - separator() scalarMulGenericBench(ECP_SWei_Proj[Fp2[curve]], window = 5, MulIters) separator() scalarMulEndo(ECP_SWei_Proj[Fp2[curve]], MulIters) separator() + separator() separator() main() diff --git a/benchmarks/bench_fp.nim b/benchmarks/bench_fp.nim index 2a718ce..2da3501 100644 --- a/benchmarks/bench_fp.nim +++ b/benchmarks/bench_fp.nim @@ -28,12 +28,12 @@ const Iters = 1_000_000 const ExponentIters = 1000 const AvailableCurves = [ # P224, - # BN254_Nogami, + BN254_Nogami, BN254_Snarks, # Curve25519, # P256, # Secp256k1, - # BLS12_377, + BLS12_377, BLS12_381, # BN446, # FKM12_447, @@ -52,7 +52,8 @@ proc main() = sqrBench(Fp[curve], Iters) invEuclidBench(Fp[curve], ExponentIters) invPowFermatBench(Fp[curve], ExponentIters) - invAddChainBench(Fp[curve], ExponentIters) + when curve in {BN254_Snarks, BLS12_381}: + invAddChainBench(Fp[curve], ExponentIters) sqrtBench(Fp[curve], ExponentIters) # Exponentiation by a "secret" of size ~the curve order powBench(Fp[curve], ExponentIters) diff --git a/benchmarks/bench_fp12.nim b/benchmarks/bench_fp12.nim index e09efe3..0d0b7da 100644 --- a/benchmarks/bench_fp12.nim +++ b/benchmarks/bench_fp12.nim @@ -27,7 +27,7 @@ const Iters = 10_000 const InvIters = 1000 const AvailableCurves = [ # Pairing-Friendly curves - # BN254_Nogami, + BN254_Nogami, BN254_Snarks, BLS12_377, BLS12_381 diff --git a/benchmarks/bench_fp2.nim b/benchmarks/bench_fp2.nim index 52d4b7c..cce0550 100644 --- a/benchmarks/bench_fp2.nim +++ b/benchmarks/bench_fp2.nim @@ -27,9 +27,9 @@ const Iters = 1_000_000 const InvIters = 1000 const AvailableCurves = [ # Pairing-Friendly curves - # BN254_Nogami, + BN254_Nogami, BN254_Snarks, - # BLS12_377, + BLS12_377, BLS12_381 # BN446, # FKM12_447, diff --git a/benchmarks/bench_fp6.nim b/benchmarks/bench_fp6.nim index e843bc9..715c90e 100644 --- a/benchmarks/bench_fp6.nim +++ b/benchmarks/bench_fp6.nim @@ -27,7 +27,7 @@ const Iters = 100_000 const InvIters = 1000 const AvailableCurves = [ # Pairing-Friendly curves - # BN254_Nogami, + BN254_Nogami, BN254_Snarks, BLS12_377, BLS12_381 diff --git a/benchmarks/bench_pairing_bls12_377.nim b/benchmarks/bench_pairing_bls12_377.nim new file mode 100644 index 0000000..4818c4b --- /dev/null +++ b/benchmarks/bench_pairing_bls12_377.nim @@ -0,0 +1,51 @@ +# 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/arithmetic, + ../constantine/towers, + # Helpers + ../helpers/static_for, + ./bench_pairing_template, + # Standard library + std/strutils + +# ############################################################ +# +# Benchmark of pairings +# for BLS12-381 +# +# ############################################################ + + +const Iters = 50 +const AvailableCurves = [ + BLS12_377, +] + +proc main() = + separator() + staticFor i, 0, AvailableCurves.len: + const curve = AvailableCurves[i] + lineDoubleBench(curve, Iters) + lineAddBench(curve, Iters) + mulFp12byLine_xyz000_Bench(curve, Iters) + separator() + finalExpEasyBench(curve, Iters) + finalExpHardBLS12Bench(curve, Iters) + separator() + millerLoopBLS12Bench(curve, Iters) + finalExpBLS12Bench(curve, Iters) + separator() + pairingBLS12Bench(curve, Iters) + separator() + +main() +notes() diff --git a/constantine.nimble b/constantine.nimble index 44b910c..cda47e9 100644 --- a/constantine.nimble +++ b/constantine.nimble @@ -65,9 +65,14 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[ ("tests/t_ec_wstrass_prj_g2_mul_sanity_bls12_381.nim", false), ("tests/t_ec_wstrass_prj_g2_mul_distri_bls12_381.nim", false), ("tests/t_ec_wstrass_prj_g2_mul_vs_ref_bls12_381.nim", false), + ("tests/t_ec_wstrass_prj_g2_add_double_bls12_377.nim", false), + ("tests/t_ec_wstrass_prj_g2_mul_sanity_bls12_377.nim", false), + ("tests/t_ec_wstrass_prj_g2_mul_distri_bls12_377.nim", false), + ("tests/t_ec_wstrass_prj_g2_mul_vs_ref_bls12_377.nim", false), # Elliptic curve arithmetic vs Sagemath ("tests/t_ec_frobenius.nim", false), ("tests/t_ec_sage_bn254.nim", false), + ("tests/t_ec_sage_bls12_377.nim", false), ("tests/t_ec_sage_bls12_381.nim", false), # Edge cases highlighted by past bugs ("tests/t_ec_wstrass_prj_edge_cases.nim", false), @@ -76,7 +81,8 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[ ("tests/t_pairing_cyclotomic_fp12.nim", false), ("tests/t_pairing_bn254_nogami_optate.nim", false), ("tests/t_pairing_bn254_snarks_optate.nim", false), - ("tests/t_pairing_bls12_381_optate.nim", false) + ("tests/t_pairing_bls12_377_optate.nim", false), + ("tests/t_pairing_bls12_381_optate.nim", false), ] # For temporary (hopefully) investigation that can only be reproduced in CI @@ -152,11 +158,16 @@ task test, "Run all tests": # Ensure benchmarks stay relevant. Ignore Windows 32-bit at the moment if not defined(windows) or not (existsEnv"UCPU" or getEnv"UCPU" == "i686"): runBench("bench_fp") + runBench("bench_fp_double_width") runBench("bench_fp2") runBench("bench_fp6") runBench("bench_fp12") runBench("bench_ec_g1") runBench("bench_ec_g2") + runBench("bench_pairing_bls12_377") + runBench("bench_pairing_bls12_381") + runBench("bench_pairing_bn254_nogami") + runBench("bench_pairing_bn254_snarks") task test_no_gmp, "Run tests that don't require GMP": # -d:testingCurves is configured in a *.nim.cfg for convenience @@ -449,6 +460,21 @@ task bench_ec_g2_gcc_noasm, "Run benchmark on Elliptic Curve group 𝔾2 - Short task bench_ec_g2_clang_noasm, "Run benchmark on Elliptic Curve group 𝔾2 - Short Weierstrass with Projective Coordinates - Clang no Assembly": runBench("bench_ec_g2", "clang", useAsm = false) +task bench_pairing_bls12_377, "Run pairings benchmarks for BLS12-377 - Default compiler": + runBench("bench_pairing_bls12_377") + +task bench_pairing_bls12_377_gcc, "Run pairings benchmarks for BLS12-377 - GCC": + runBench("bench_pairing_bls12_377", "gcc") + +task bench_pairing_bls12_377_clang, "Run pairings benchmarks for BLS12-377 - Clang": + runBench("bench_pairing_bls12_377", "clang") + +task bench_pairing_bls12_377_gcc_noasm, "Run pairings benchmarks for BLS12-377 - GCC no Assembly": + runBench("bench_pairing_bls12_377", "gcc", useAsm = false) + +task bench_pairing_bls12_377_clang_noasm, "Run pairings benchmarks for BLS12-377 - Clang no Assembly": + runBench("bench_pairing_bls12_377", "clang", useAsm = false) + task bench_pairing_bls12_381, "Run pairings benchmarks for BLS12-381 - Default compiler": runBench("bench_pairing_bls12_381") diff --git a/constantine/arithmetic.nim b/constantine/arithmetic.nim index 0a2f9fe..ed0284b 100644 --- a/constantine/arithmetic.nim +++ b/constantine/arithmetic.nim @@ -8,8 +8,16 @@ import arithmetic/bigints, - arithmetic/[finite_fields, finite_fields_inversion, finite_fields_double_width] + arithmetic/[ + finite_fields, + finite_fields_inversion, + finite_fields_square_root, + finite_fields_double_width + ] export bigints, - finite_fields, finite_fields_inversion, finite_fields_double_width + finite_fields, + finite_fields_inversion, + finite_fields_square_root, + finite_fields_double_width diff --git a/constantine/arithmetic/bigints.nim b/constantine/arithmetic/bigints.nim index b5f5018..6c38c60 100644 --- a/constantine/arithmetic/bigints.nim +++ b/constantine/arithmetic/bigints.nim @@ -130,6 +130,10 @@ func isOdd*(a: BigInt): SecretBool = ## Returns true if a is odd a.limbs.isOdd +func isEven*(a: BigInt): SecretBool = + ## Returns true if a is even + a.limbs.isEven + func isMsbSet*(a: BigInt): SecretBool = ## Returns true if MSB is set ## i.e. if a BigInt is interpreted @@ -138,9 +142,9 @@ func isMsbSet*(a: BigInt): SecretBool = ## This is equivalent to checking ## if the number is negative - # MSB is at announced bits - (wordsRequired - 1) - const msb_pos = BigInt.bits-1 - (BigInt.bits.wordsRequired - 1) - SecretBool((BaseType(a.limbs[a.limbs.len-1]) shr msb_pos) and 1) + # MSB is at announced bits - (wordsRequired-1)*WordBitWidth - 1 + const msb_in_msw = BigInt.bits - (BigInt.bits.wordsRequired-1)*WordBitWidth - 1 + SecretBool((BaseType(a.limbs[a.limbs.len-1]) shr msb_in_msw) and 1) func eq*(a: BigInt, n: SecretWord): SecretBool = ## Returns true if ``a`` is equal @@ -546,6 +550,9 @@ func montyPowUnsafeExponent*[mBits: static int]( var scratchSpace {.noInit.}: array[scratchLen, Limbs[mBits.wordsRequired]] montyPowUnsafeExponent(a.limbs, exponent, M.limbs, one.limbs, negInvModWord, scratchSpace, canUseNoCarryMontyMul, canUseNoCarryMontySquare) +from ../io/io_bigints import exportRawUint +# Workaround recursive dependencies + func montyPow*[mBits, eBits: static int]( a: var BigInt[mBits], exponent: BigInt[eBits], M, one: BigInt[mBits], negInvModWord: static BaseType, windowSize: static int, @@ -560,8 +567,6 @@ func montyPow*[mBits, eBits: static int]( ## ## This is constant-time: the window optimization does ## not reveal the exponent bits or hamming weight - mixin exportRawUint # exported in io_bigints which depends on this module ... - var expBE {.noInit.}: array[(ebits + 7) div 8, byte] expBE.exportRawUint(exponent, bigEndian) @@ -585,8 +590,6 @@ func montyPowUnsafeExponent*[mBits, eBits: static int]( ## ## This uses fixed window optimization ## A window size in the range [1, 5] must be chosen - mixin exportRawUint # exported in io_bigints which depends on this module ... - var expBE {.noInit.}: array[(ebits + 7) div 8, byte] expBE.exportRawUint(exponent, bigEndian) diff --git a/constantine/arithmetic/finite_fields.nim b/constantine/arithmetic/finite_fields.nim index 0bae46c..44084a1 100644 --- a/constantine/arithmetic/finite_fields.nim +++ b/constantine/arithmetic/finite_fields.nim @@ -27,6 +27,7 @@ import ../primitives, ../config/[common, type_fp, curves], + ../io/io_bigints, ./bigints, ./limbs_montgomery when UseASM_X86_64: @@ -307,153 +308,6 @@ func powUnsafeExponent*(a: var Fp, exponent: openarray[byte]) {.inline.} = Fp.C.canUseNoCarryMontySquare() ) -# ############################################################ -# -# Field arithmetic square roots -# -# ############################################################ - -func isSquare*[C](a: Fp[C]): SecretBool {.inline.} = - ## Returns true if ``a`` is a square (quadratic residue) in 𝔽p - ## - ## Assumes that the prime modulus ``p`` is public. - # Implementation: we use exponentiation by (p-1)/2 (Euler's criterion) - # as it can reuse the exponentiation implementation - # Note that we don't care about leaking the bits of p - # as we assume that - var xi {.noInit.} = a # TODO: is noInit necessary? see https://github.com/mratsim/constantine/issues/21 - xi.powUnsafeExponent(C.getPrimeMinus1div2_BE()) - result = not(xi.mres == C.getMontyPrimeMinus1()) - # xi can be: - # - 1 if a square - # - 0 if 0 - # - -1 if a quadratic non-residue - debug: - doAssert: bool( - xi.isZero or - xi.isOne or - xi.mres == C.getMontyPrimeMinus1() - ) - -func sqrt_p3mod4[C](a: var Fp[C]) {.inline.} = - ## Compute the square root of ``a`` - ## - ## This requires ``a`` to be a square - ## and the prime field modulus ``p``: p ≡ 3 (mod 4) - ## - ## The result is undefined otherwise - ## - ## The square root, if it exist is multivalued, - ## i.e. both x² == (-x)² - ## This procedure returns a deterministic result - static: doAssert BaseType(C.Mod.limbs[0]) mod 4 == 3 - a.powUnsafeExponent(C.getPrimePlus1div4_BE()) - -func sqrt_invsqrt_p3mod4[C](sqrt, invsqrt: var Fp[C], a: Fp[C]) {.inline.} = - ## If ``a`` is a square, compute the square root of ``a`` in sqrt - ## and the inverse square root of a in invsqrt - ## - ## This assumes that the prime field modulus ``p``: p ≡ 3 (mod 4) - # TODO: deterministic sign - # - # Algorithm - # - # - # From Euler's criterion: a^((p-1)/2)) ≡ 1 (mod p) if square - # a^((p-1)/2)) * a^-1 ≡ 1/a (mod p) - # a^((p-3)/2)) ≡ 1/a (mod p) - # a^((p-3)/4)) ≡ 1/√a (mod p) # Requires p ≡ 3 (mod 4) - static: doAssert BaseType(C.Mod.limbs[0]) mod 4 == 3 - - invsqrt = a - invsqrt.powUnsafeExponent(C.getPrimeMinus3div4_BE()) - # √a ≡ a * 1/√a ≡ a^((p+1)/4) (mod p) - sqrt.prod(invsqrt, a) - -func sqrt_invsqrt_if_square_p3mod4[C](sqrt, invsqrt: var Fp[C], a: Fp[C]): SecretBool {.inline.} = - ## If ``a`` is a square, compute the square root of ``a`` in sqrt - ## and the inverse square root of a in invsqrt - ## - ## If a is not square, sqrt and invsqrt are undefined - ## - ## This assumes that the prime field modulus ``p``: p ≡ 3 (mod 4) - sqrt_invsqrt_p3mod4(sqrt, invsqrt, a) - var euler {.noInit.}: Fp[C] - euler.prod(sqrt, invsqrt) - result = not(euler.mres == C.getMontyPrimeMinus1()) - -func sqrt_if_square_p3mod4[C](a: var Fp[C]): SecretBool {.inline.} = - ## If ``a`` is a square, compute the square root of ``a`` - ## if not, ``a`` is unmodified. - ## - ## This assumes that the prime field modulus ``p``: p ≡ 3 (mod 4) - ## - ## The result is undefined otherwise - ## - ## The square root, if it exist is multivalued, - ## i.e. both x² == (-x)² - ## This procedure returns a deterministic result - var sqrt {.noInit.}, invsqrt {.noInit.}: Fp[C] - result = sqrt_invsqrt_if_square_p3mod4(sqrt, invsqrt, a) - a.ccopy(sqrt, result) - -func sqrt*[C](a: var Fp[C]) {.inline.} = - ## Compute the square root of ``a`` - ## - ## This requires ``a`` to be a square - ## - ## The result is undefined otherwise - ## - ## The square root, if it exist is multivalued, - ## i.e. both x² == (-x)² - ## This procedure returns a deterministic result - when BaseType(C.Mod.limbs[0]) mod 4 == 3: - sqrt_p3mod4(a) - else: - {.error: "Square root is only implemented for p ≡ 3 (mod 4)".} - -func sqrt_if_square*[C](a: var Fp[C]): SecretBool {.inline.} = - ## If ``a`` is a square, compute the square root of ``a`` - ## if not, ``a`` is unmodified. - ## - ## The square root, if it exist is multivalued, - ## i.e. both x² == (-x)² - ## This procedure returns a deterministic result - when BaseType(C.Mod.limbs[0]) mod 4 == 3: - result = sqrt_if_square_p3mod4(a) - else: - {.error: "Square root is only implemented for p ≡ 3 (mod 4)".} - -func sqrt_invsqrt*[C](sqrt, invsqrt: var Fp[C], a: Fp[C]) {.inline.} = - ## Compute the square root and inverse square root of ``a`` - ## - ## This requires ``a`` to be a square - ## - ## The result is undefined otherwise - ## - ## The square root, if it exist is multivalued, - ## i.e. both x² == (-x)² - ## This procedure returns a deterministic result - when BaseType(C.Mod.limbs[0]) mod 4 == 3: - sqrt_invsqrt_p3mod4(sqrt, invsqrt, a) - else: - {.error: "Square root is only implemented for p ≡ 3 (mod 4)".} - -func sqrt_invsqrt_if_square*[C](sqrt, invsqrt: var Fp[C], a: Fp[C]): SecretBool {.inline.} = - ## Compute the square root and ivnerse square root of ``a`` - ## - ## This returns true if ``a`` is square and sqrt/invsqrt contains the square root/inverse square root - ## - ## The result is undefined otherwise - ## - ## The square root, if it exist is multivalued, - ## i.e. both x² == (-x)² - ## This procedure returns a deterministic result - when BaseType(C.Mod.limbs[0]) mod 4 == 3: - result = sqrt_invsqrt_if_square_p3mod4(sqrt, invsqrt, a) - else: - {.error: "Square root is only implemented for p ≡ 3 (mod 4)".} - # ############################################################ # # Field arithmetic ergonomic primitives diff --git a/constantine/arithmetic/finite_fields_square_root.nim b/constantine/arithmetic/finite_fields_square_root.nim new file mode 100644 index 0000000..24d07e2 --- /dev/null +++ b/constantine/arithmetic/finite_fields_square_root.nim @@ -0,0 +1,269 @@ +# 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 + std/macros, + ../primitives, + ../config/[common, type_fp, curves], + ../io/[io_bigints, io_fields], + ./bigints, ./finite_fields, ./limbs_montgomery + +# ############################################################ +# +# Field arithmetic square roots +# +# ############################################################ + +# Legendre symbol / Euler's Criterion / Kronecker's symbol +# ------------------------------------------------------------ + +func isSquare*[C](a: Fp[C]): SecretBool {.inline.} = + ## Returns true if ``a`` is a square (quadratic residue) in 𝔽p + ## + ## Assumes that the prime modulus ``p`` is public. + # Implementation: we use exponentiation by (p-1)/2 (Euler's criterion) + # as it can reuse the exponentiation implementation + # Note that we don't care about leaking the bits of p + # as we assume that + var xi {.noInit.} = a # TODO: is noInit necessary? see https://github.com/mratsim/constantine/issues/21 + xi.powUnsafeExponent(C.getPrimeMinus1div2_BE()) + result = not(xi.mres == C.getMontyPrimeMinus1()) + # xi can be: + # - 1 if a square + # - 0 if 0 + # - -1 if a quadratic non-residue + debug: + doAssert: bool( + xi.isZero or + xi.isOne or + xi.mres == C.getMontyPrimeMinus1() + ) + +# Specialized routine for p ≡ 3 (mod 4) +# ------------------------------------------------------------ + +func sqrt_p3mod4[C](a: var Fp[C]) {.inline.} = + ## Compute the square root of ``a`` + ## + ## This requires ``a`` to be a square + ## and the prime field modulus ``p``: p ≡ 3 (mod 4) + ## + ## The result is undefined otherwise + ## + ## The square root, if it exist is multivalued, + ## i.e. both x² == (-x)² + ## This procedure returns a deterministic result + static: doAssert BaseType(C.Mod.limbs[0]) mod 4 == 3 + a.powUnsafeExponent(C.getPrimePlus1div4_BE()) + +func sqrt_invsqrt_p3mod4[C](sqrt, invsqrt: var Fp[C], a: Fp[C]) {.inline.} = + ## If ``a`` is a square, compute the square root of ``a`` in sqrt + ## and the inverse square root of a in invsqrt + ## + ## This assumes that the prime field modulus ``p``: p ≡ 3 (mod 4) + # TODO: deterministic sign + # + # Algorithm + # + # + # From Euler's criterion: a^((p-1)/2)) ≡ 1 (mod p) if square + # a^((p-1)/2)) * a^-1 ≡ 1/a (mod p) + # a^((p-3)/2)) ≡ 1/a (mod p) + # a^((p-3)/4)) ≡ 1/√a (mod p) # Requires p ≡ 3 (mod 4) + static: doAssert BaseType(C.Mod.limbs[0]) mod 4 == 3 + + invsqrt = a + invsqrt.powUnsafeExponent(C.getPrimeMinus3div4_BE()) + # √a ≡ a * 1/√a ≡ a^((p+1)/4) (mod p) + sqrt.prod(invsqrt, a) + +func sqrt_invsqrt_if_square_p3mod4[C](sqrt, invsqrt: var Fp[C], a: Fp[C]): SecretBool {.inline.} = + ## If ``a`` is a square, compute the square root of ``a`` in sqrt + ## and the inverse square root of a in invsqrt + ## + ## If a is not square, sqrt and invsqrt are undefined + ## + ## This assumes that the prime field modulus ``p``: p ≡ 3 (mod 4) + sqrt_invsqrt_p3mod4(sqrt, invsqrt, a) + var euler {.noInit.}: Fp[C] + euler.prod(sqrt, invsqrt) + result = not(euler.mres == C.getMontyPrimeMinus1()) + +func sqrt_if_square_p3mod4[C](a: var Fp[C]): SecretBool {.inline.} = + ## If ``a`` is a square, compute the square root of ``a`` + ## if not, ``a`` is unmodified. + ## + ## This assumes that the prime field modulus ``p``: p ≡ 3 (mod 4) + ## + ## The result is undefined otherwise + ## + ## The square root, if it exist is multivalued, + ## i.e. both x² == (-x)² + ## This procedure returns a deterministic result + var sqrt {.noInit.}, invsqrt {.noInit.}: Fp[C] + result = sqrt_invsqrt_if_square_p3mod4(sqrt, invsqrt, a) + a.ccopy(sqrt, result) + +# Tonelli Shanks for any prime +# ------------------------------------------------------------ + +const + # with e = 2adicity + # p == s * 2^e + 1 + # root_of_unity = smallest_quadratic_nonresidue^s + # exponent = (p-1-2^e)/2^e / 2 + TonelliShanks_exponent_BLS12_377 = BigInt[330].fromHex"0x35c748c2f8a21d58c760b80d94292763445b3e601ea271e3de6c45f741290002e16ba88600000010a11" + TonelliShanks_twoAdicity_BLS12_377 = 46 + TonelliShanks_root_of_unity_BLS12_377 = Fp[BLS12_377].fromHex"0x382d3d99cdbc5d8fe9dee6aa914b0ad14fcaca7022110ec6eaa2bc56228ac41ea03d28cc795186ba6b5ef26b00bbe8" + +{.experimental: "dynamicBindSym".} + +macro tsGet(C: static Curve, value: untyped): untyped = + return bindSym("TonelliShanks_" & $value & "_" & $C) + +func precompute_tonelli_shanks[C]( + a_pre_exp: var Fp[C], + a: Fp[C]) = + a_pre_exp = a + a_pre_exp.powUnsafeExponent(C.tsGet(exponent)) + +func isSquare_tonelli_shanks[C]( + a, a_pre_exp: Fp[C]): SecretBool = + ## Returns if `a` is a quadratic residue + ## This uses common precomputation for + ## Tonelli-Shanks based square root and inverse square root + ## + ## a^((p-1-2^e)/(2*2^e)) + const e = C.tsGet(twoAdicity) + var r {.noInit.}: Fp[C] + r.square(a_pre_exp) # a^(2(q-1-2^e)/(2*2^e)) = a^((q-1)/2^e - 1) + r *= a # a^((q-1)/2^e) + for _ in 0 ..< e-1: + r.square() # a^((q-1)/2) + + result = not(r.mres == C.getMontyPrimeMinus1()) + # r can be: + # - 1 if a square + # - 0 if 0 + # - -1 if a quadratic non-residue + debug: + doAssert: bool( + r.isZero or + r.isOne or + r.mres == C.getMontyPrimeMinus1() + ) + +func sqrt_invsqrt_tonelli_shanks[C]( + sqrt, invsqrt: var Fp[C], + a, a_pre_exp: Fp[C]) = + ## Compute the square_root and inverse_square_root + ## of `a` via constant-time Tonelli-Shanks + ## + ## a_pre_exp is a precomputation a^((p-1-2^e)/(2*2^e)) + ## ThItat is shared with the simultaneous isSquare routine + template z: untyped = a_pre_exp + template r: untyped = invsqrt + var t {.noInit.}: Fp[C] + const e = C.tsGet(twoAdicity) + + t.square(z) + t *= a + r = z + var b = t + var root = C.tsGet(root_of_unity) + + var buf {.noInit.}: Fp[C] + + for i in countdown(e, 2, 1): + for j in 1 .. i-2: + b.square() + + let bNotOne = not b.isOne() + buf.prod(r, root) + r.ccopy(buf, bNotOne) + root.square() + buf.prod(t, root) + t.ccopy(buf, bNotOne) + b = t + + sqrt.prod(invsqrt, a) + +# Public routines +# ------------------------------------------------------------ + +func sqrt*[C](a: var Fp[C]) {.inline.} = + ## Compute the square root of ``a`` + ## + ## This requires ``a`` to be a square + ## + ## The result is undefined otherwise + ## + ## The square root, if it exist is multivalued, + ## i.e. both x² == (-x)² + ## This procedure returns a deterministic result + ## This procedure is constant-time + when (BaseType(C.Mod.limbs[0]) and 3) == 3: + sqrt_p3mod4(a) + else: + var a_pre_exp{.noInit.}, sqrt{.noInit.}, invsqrt{.noInit.}: Fp[C] + a_pre_exp.precompute_tonelli_shanks(a) + sqrt_invsqrt_tonelli_shanks(sqrt, invsqrt, a, a_pre_exp) + a = sqrt + +func sqrt_if_square*[C](a: var Fp[C]): SecretBool {.inline.} = + ## If ``a`` is a square, compute the square root of ``a`` + ## if not, ``a`` is unmodified. + ## + ## The square root, if it exist is multivalued, + ## i.e. both x² == (-x)² + ## This procedure returns a deterministic result + ## This procedure is constant-time + when (BaseType(C.Mod.limbs[0]) and 3) == 3: + result = sqrt_if_square_p3mod4(a) + else: + var a_pre_exp{.noInit.}, sqrt{.noInit.}, invsqrt{.noInit.}: Fp[C] + a_pre_exp.precompute_tonelli_shanks(a) + result = isSquare_tonelli_shanks(a, a_pre_exp) + sqrt_invsqrt_tonelli_shanks(sqrt, invsqrt, a, a_pre_exp) + a = sqrt + +func sqrt_invsqrt*[C](sqrt, invsqrt: var Fp[C], a: Fp[C]) {.inline.} = + ## Compute the square root and inverse square root of ``a`` + ## + ## This requires ``a`` to be a square + ## + ## The result is undefined otherwise + ## + ## The square root, if it exist is multivalued, + ## i.e. both x² == (-x)² + ## This procedure returns a deterministic result + when (BaseType(C.Mod.limbs[0]) and 3) == 3: + sqrt_invsqrt_p3mod4(sqrt, invsqrt, a) + else: + var a_pre_exp{.noInit.}: Fp[C] + a_pre_exp.precompute_tonelli_shanks(a) + sqrt_invsqrt_tonelli_shanks(sqrt, invsqrt, a, a_pre_exp) + +func sqrt_invsqrt_if_square*[C](sqrt, invsqrt: var Fp[C], a: Fp[C]): SecretBool {.inline.} = + ## Compute the square root and ivnerse square root of ``a`` + ## + ## This returns true if ``a`` is square and sqrt/invsqrt contains the square root/inverse square root + ## + ## The result is undefined otherwise + ## + ## The square root, if it exist is multivalued, + ## i.e. both x² == (-x)² + ## This procedure returns a deterministic result + when (BaseType(C.Mod.limbs[0]) and 3) == 3: + result = sqrt_invsqrt_if_square_p3mod4(sqrt, invsqrt, a) + else: + var a_pre_exp{.noInit.}: Fp[C] + a_pre_exp.precompute_tonelli_shanks(a) + result = isSquare_tonelli_shanks(a, a_pre_exp) + sqrt_invsqrt_tonelli_shanks(sqrt, invsqrt, a, a_pre_exp) + a = sqrt diff --git a/constantine/arithmetic/limbs.nim b/constantine/arithmetic/limbs.nim index cb2264a..224f4e0 100644 --- a/constantine/arithmetic/limbs.nim +++ b/constantine/arithmetic/limbs.nim @@ -146,6 +146,10 @@ func isOdd*(a: Limbs): SecretBool = ## Returns true if a is odd SecretBool(a[0] and SecretWord(1)) +func isEven*(a: Limbs): SecretBool = + ## Returns true if a is even + not SecretBool(a[0] and SecretWord(1)) + # Bit manipulation # ------------------------------------------------------------ diff --git a/constantine/config/curves_declaration.nim b/constantine/config/curves_declaration.nim index a09db15..3e860d2 100644 --- a/constantine/config/curves_declaration.nim +++ b/constantine/config/curves_declaration.nim @@ -140,9 +140,12 @@ declareCurves: family: BarretoLynnScott # u: 3 * 2^46 * (7 * 13 * 499) + 1 # u: 0x8508c00000000001 + cubicRootOfUnity_mod_p: "0x9b3af05dd14f6ec619aaf7d34594aabc5ed1347970dec00452217cc900000008508c00000000001" # G1 Equation: y² = x³ + 1 # G2 Equation: y² = x³ + 1/ with 𝑗 = √-5 + order: "0x12ab655e9a2ca55660b44d1e5c37b00159aa76fed00000010a11800000000001" + orderBitwidth: 253 eq_form: ShortWeierstrass coef_a: 0 coef_b: 1 diff --git a/constantine/elliptic/ec_endomorphism_accel.nim b/constantine/elliptic/ec_endomorphism_accel.nim index 6e5ac02..058fd46 100644 --- a/constantine/elliptic/ec_endomorphism_accel.nim +++ b/constantine/elliptic/ec_endomorphism_accel.nim @@ -244,9 +244,15 @@ func scalarMulEndo*[scalBits]( # and negate it as well. # # However solution 1 seems to cause issues (TODO) - # with some of the BLS12-381 test cases (6 and 9) + # with some of the BLS12-381 G2 test cases (6 and 9) as one of the miniscalars is 65 bits + # instead of the expected maximum of 64. # - 0x5668a2332db27199dcfb7cbdfca6317c2ff128db26d7df68483e0a095ec8e88f # - 0x644dc62869683f0c93f38eaef2ba6912569dc91ec2806e46b4a3dd6a4421dad1 + # + # Furthermore solution 2 isn't enough on BLS12-377 G2 as test fails when miniScalars[0] is negative + let isNeg0 = miniscalars[0].isMsbSet() + miniscalars[0].cneg(isNeg0) + P.cneg(isNeg0) # 4. Precompute lookup table var lut {.noInit.}: array[1 shl (M-1), ECP_SWei_Proj] diff --git a/constantine/elliptic/ec_endomorphism_params.nim b/constantine/elliptic/ec_endomorphism_params.nim index cec7002..a19d079 100644 --- a/constantine/elliptic/ec_endomorphism_params.nim +++ b/constantine/elliptic/ec_endomorphism_params.nim @@ -52,6 +52,24 @@ const Babai_BN254_Snarks_G1 = ( (BigInt[130].fromHex"0x24ccef014a773d2d25398fd0300ff6565", false) # (6u² + 4u + 1) << 2^256 // r ) +# BLS12-377 G1 +# ---------------------------------------------------------------------------------------- + +const Lattice_BLS12_377_G1 = ( + # (BigInt, isNeg) + ((BigInt[127].fromHex"0x452217cc900000010a11800000000000", false), # u² - 1 + (BigInt[1].fromHex"0x1", true)), # -1 + ((BigInt[1].fromHex"0x1", false), # 1 + (BigInt[127].fromHex"0x452217cc900000010a11800000000001", false)) # u² +) + +const Babai_BLS12_377_G1 = ( + # Vector for Babai rounding + # (BigInt, isNeg) + (BigInt[130].fromHex"0x3b3f7aa969fd371607f72ed32af90182c", false), + (BigInt[4].fromHex"0xd", false) +) + # BLS12-381 G1 # ---------------------------------------------------------------------------------------- @@ -107,6 +125,43 @@ const Babai_BN254_Snarks_G2 = ( (BigInt[128].fromhex"0xc444fab18d269b9af7ae23ce89afae7d", true) # -2x²-x << 2^256 // r ) +# BLS12-377 G2 +# ---------------------------------------------------------------------------------------- + +const Lattice_BLS12_377_G2 = ( + # Curve of order 254 -> mini scalars of size 65 + # x = -0xd201000000010000 + # Value, isNeg + ((BigInt[64].fromHex"0x8508c00000000001", true), # -x + (BigInt[1].fromHex"0x1", false), # 1 + (BigInt[1].fromHex"0x0", false), # 0 + (BigInt[1].fromHex"0x0", false)), # 0 + + ((BigInt[1].fromHex"0x0", false), # 0 + (BigInt[64].fromHex"0x8508c00000000001", true), # -x + (BigInt[1].fromHex"0x1", false), # 1 + (BigInt[1].fromHex"0x0", false)), # 0 + + ((BigInt[1].fromHex"0x0", false), # 0 + (BigInt[1].fromHex"0x0", false), # 0 + (BigInt[64].fromHex"0x8508c00000000001", true), # -x + (BigInt[1].fromHex"0x1", false)), # 1 + + ((BigInt[1].fromHex"0x1", false), # 1 + (BigInt[1].fromHex"0x0", false), # 0 + (BigInt[1].fromHex"0x1", true), # -1 + (BigInt[64].fromHex"0x8508c00000000001", true)) # -x +) + +const Babai_BLS12_377_G2 = ( + # Vector for Babai rounding + # Value, isNeg + (BigInt[193].fromHex"0x1eca0125755aed064f63abaff9084ce152979759b442f60d1", true), + (BigInt[130].fromHex"0x3b3f7aa969fd371607f72ed32af90181f", true), + (BigInt[67].fromhex"0x72030ba8ee9c06415", true), + (BigInt[1].fromhex"0x0", false) +) + # BLS12-381 G2 # ---------------------------------------------------------------------------------------- diff --git a/constantine/elliptic/ec_scalar_mul.nim b/constantine/elliptic/ec_scalar_mul.nim index 475da2c..0042207 100644 --- a/constantine/elliptic/ec_scalar_mul.nim +++ b/constantine/elliptic/ec_scalar_mul.nim @@ -241,7 +241,7 @@ func scalarMul*( ## - 0 <= scalar < curve order ## this will not automatically when BigInt.bits <= ECP_SWei_Proj.F.C.getCurveOrderBitwidth() and - ECP_SWei_Proj.F.C in {BN254_Snarks, BLS12_381}: + ECP_SWei_Proj.F.C in {BN254_Snarks, BLS12_377, BLS12_381}: when ECP_SWei_Proj.F is Fp: P.scalarMulGLV_m2w2(scalar) elif ECP_SWei_Proj.F is Fp2: diff --git a/constantine/hash_to_curve/cofactors.nim b/constantine/hash_to_curve/cofactors.nim index 174c656..0219fba 100644 --- a/constantine/hash_to_curve/cofactors.nim +++ b/constantine/hash_to_curve/cofactors.nim @@ -32,6 +32,12 @@ const Cofactor_Eff_BN254_Snarks_G1 = BigInt[1].fromHex"0x1" const Cofactor_Eff_BN254_Snarks_G2 = BigInt[254].fromHex"0x30644e72e131a029b85045b68181585e06ceecda572a2489345f2299c0f9fa8d" ## G2.order // r +# TODO effective cofactors as per H2C draft like BLS12-381 curve +const Cofactor_Eff_BLS12_377_G1 = BigInt[125].fromHex"0x170b5d44300000000000000000000000" + ## P -> (1 - x) P +const Cofactor_Eff_BLS12_377_G2 = BigInt[502].fromHex"0x26ba558ae9562addd88d99a6f6a829fbb36b00e1dcc40c8c505634fae2e189d693e8c36676bd09a0f3622fba094800452217cc900000000000000000000001" + ## P -> (x^2 - x - 1) P + (x - 1) psi(P) + psi(psi(2P)) + # https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-09#section-8.8 const Cofactor_Eff_BLS12_381_G1 = BigInt[64].fromHex"0xd201000000010001" ## P -> (1 - x) P @@ -58,9 +64,17 @@ func clearCofactorReference*(P: var ECP_SWei_Proj[Fp2[BN254_Snarks]]) {.inline.} # Endomorphism acceleration cannot be used if cofactor is not cleared P.scalarMulGeneric(Cofactor_Eff_BN254_Snarks_G2) +func clearCofactorReference*(P: var ECP_SWei_Proj[Fp[BLS12_377]]) {.inline.} = + ## Clear the cofactor of BLS12_377 G1 + P.scalarMulGeneric(Cofactor_Eff_BLS12_377_G1) + +func clearCofactorReference*(P: var ECP_SWei_Proj[Fp2[BLS12_377]]) {.inline.} = + ## Clear the cofactor of BLS12_377 G2 + # Endomorphism acceleration cannot be used if cofactor is not cleared + P.scalarMulGeneric(Cofactor_Eff_BLS12_377_G2) + func clearCofactorReference*(P: var ECP_SWei_Proj[Fp[BLS12_381]]) {.inline.} = ## Clear the cofactor of BLS12_381 G1 - ## BN curve have a G1 cofactor of 1 so this is a no-op P.scalarMulGeneric(Cofactor_Eff_BLS12_381_G1) func clearCofactorReference*(P: var ECP_SWei_Proj[Fp2[BLS12_381]]) {.inline.} = diff --git a/constantine/io/io_fields.nim b/constantine/io/io_fields.nim index aa7c799..d625b72 100644 --- a/constantine/io/io_fields.nim +++ b/constantine/io/io_fields.nim @@ -64,7 +64,11 @@ func toHex*(f: Fp, order: static Endianness = bigEndian): string = ## - no leaks result.appendHex(f, order) -func fromHex*(dst: var Fp, s: string) {.raises: [ValueError].}= +func fromHex*(dst: var Fp, hexString: string) {.raises: [ValueError].}= ## Convert a hex string to a element of Fp - let raw {.noinit.} = fromHex(dst.mres.typeof, s) + let raw {.noinit.} = fromHex(dst.mres.typeof, hexString) dst.fromBig(raw) + +func fromHex*(T: type Fp, hexString: string): T {.noInit, raises: [ValueError].}= + ## Convert a hex string to a element of Fp + result.fromHex(hexString) diff --git a/constantine/isogeny/frobenius.nim b/constantine/isogeny/frobenius.nim index c24fd0a..f13c5da 100644 --- a/constantine/isogeny/frobenius.nim +++ b/constantine/isogeny/frobenius.nim @@ -66,6 +66,86 @@ template mulCheckSparse[Fp2](a: var Fp2, b: Fp2) = # Frobenius map - on extension fields # ----------------------------------------------------------------- +# c = (SNR^((p-1)/6)^coef). +# Then for frobenius(2): c * conjugate(c) +# And for frobenius(3): c² * conjugate(c) +const FrobMapConst_BLS12_377 = [ + # frobenius(1) + [Fp2[BLS12_377].fromHex( # SNR^((p-1)/6)^0 + "0x1", + "0x0" + ), + Fp2[BLS12_377].fromHex( # SNR^((p-1)/6)^1 + "0x9a9975399c019633c1e30682567f915c8a45e0f94ebc8ec681bf34a3aa559db57668e558eb0188e938a9d1104f2031", + "0x0" + ), + Fp2[BLS12_377].fromHex( # SNR^((p-1)/6)^2 = SNR^((p-1)/3) + "0x9b3af05dd14f6ec619aaf7d34594aabc5ed1347970dec00452217cc900000008508c00000000002", + "0x0" + ), + Fp2[BLS12_377].fromHex( # SNR^((p-1)/6)^3 = SNR^((p-1)/2) + "0x1680a40796537cac0c534db1a79beb1400398f50ad1dec1bce649cf436b0f6299588459bff27d8e6e76d5ecf1391c63", + "0x0" + ), + Fp2[BLS12_377].fromHex( # SNR^((p-1)/6)^4 = SNR^(2(p-1)/3) + "0x9b3af05dd14f6ec619aaf7d34594aabc5ed1347970dec00452217cc900000008508c00000000001", + "0x0" + ), + Fp2[BLS12_377].fromHex( # SNR^((p-1)/6)^5 + "0xcd70cb3fc936348d0351d498233f1fe379531411832232f6648a9a9fc0b9c4e3e21b7467077c05853e2c1be0e9fc32", + "0x0" + )], + # frobenius(2) + [Fp2[BLS12_377].fromHex( # norm(SNR)^((p-1)/6)^1 + "0x1", + "0x0" + ), + Fp2[BLS12_377].fromHex( # norm(SNR)^((p-1)/6)^2 + "0x9b3af05dd14f6ec619aaf7d34594aabc5ed1347970dec00452217cc900000008508c00000000002", + "0x0" + ), + Fp2[BLS12_377].fromHex( + "0x9b3af05dd14f6ec619aaf7d34594aabc5ed1347970dec00452217cc900000008508c00000000001", + "0x0" + ), + Fp2[BLS12_377].fromHex( + "0x1ae3a4617c510eac63b05c06ca1493b1a22d9f300f5138f1ef3622fba094800170b5d44300000008508c00000000000", + "0x0" + ), + Fp2[BLS12_377].fromHex( + "0x1ae3a4617c510eabc8756ba8f8c524eb8882a75cc9bc8e359064ee822fb5bffd1e945779fffffffffffffffffffffff", + "0x0" + ), + Fp2[BLS12_377].fromHex( + "0x1ae3a4617c510eabc8756ba8f8c524eb8882a75cc9bc8e359064ee822fb5bffd1e94577a00000000000000000000000", + "0x0" + )], + # frobenius(3) + [Fp2[BLS12_377].fromHex( + "0x1", + "0x0" + ), + Fp2[BLS12_377].fromHex( + "0x1680a40796537cac0c534db1a79beb1400398f50ad1dec1bce649cf436b0f6299588459bff27d8e6e76d5ecf1391c63", + "0x0" + ), + Fp2[BLS12_377].fromHex( + "0x1ae3a4617c510eac63b05c06ca1493b1a22d9f300f5138f1ef3622fba094800170b5d44300000008508c00000000000", + "0x0" + ), + Fp2[BLS12_377].fromHex( + "0x4630059e5fd9200575d0e552278a89da1f40fdf62334cd620d1860769e389d7db2d8ea700d82721691ea130ec6e39e", + "0x0" + ), + Fp2[BLS12_377].fromHex( + "0x1", + "0x0" + ), + Fp2[BLS12_377].fromHex( + "0x1680a40796537cac0c534db1a79beb1400398f50ad1dec1bce649cf436b0f6299588459bff27d8e6e76d5ecf1391c63", + "0x0" + )]] + # c = (SNR^((p-1)/6)^coef). # Then for frobenius(2): c * conjugate(c) # And for frobenius(3): c² * conjugate(c) diff --git a/constantine/pairing/pairing_bls12.nim b/constantine/pairing/pairing_bls12.nim index d528ab4..d79160b 100644 --- a/constantine/pairing/pairing_bls12.nim +++ b/constantine/pairing/pairing_bls12.nim @@ -7,6 +7,7 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import + std/macros, ../primitives, ../config/[common, curves], ../arithmetic, @@ -45,21 +46,39 @@ import # https://eprint.iacr.org/2009/615.pdf # TODO: should be part of curve parameters -const BLS12_381_param = block: +# The bit count must be exact for the Miller loop +const BLS12_377_ate_param = block: + # BLS Miller loop is parametrized by u + BigInt[64+1].fromHex("0x8508c00000000001") # +1 so that we can take *3 and NAF encode it + +const BLS12_377_ate_param_isNeg = false + +const BLS12_381_ate_param = block: # BLS Miller loop is parametrized by u BigInt[64+2].fromHex("0xd201000000010000") # +2 so that we can take *3 and NAF encode it -const BLS12_381_param_isNeg = true +const BLS12_381_ate_param_isNeg = true # Generic slow pairing implementation # ---------------------------------------------------------------- +const BLS12_377_finalexponent = block: + # (p^12 - 1) / r + # BigInt[4269].fromHex"0x1b2ff68c1abdc48ab4f04ed12cc8f9b2f161b41c7eb8865b9ad3c9bb0571dd94c6bde66548dc13624d9d741024ceb315f46a89cc2482605eb6afc6d8977e5e2ccbec348dd362d59ec2b5bc62a1b467ae44572215548abc98bb4193886ed89cceaedd0221aba84fb33e5584ac29619a87a00c315178155496857c995eab4a8a9af95f4015db27955ae408d6927d0ab37d52f3917c4ddec88f8159f7bcba7eb65f1aae4eeb4e70cb20227159c08a7fdfea9b62bb308918eac3202569dd1bcdd86b431e3646356fc3fb79f89b30775e006993adb629586b6c874b7688f86f11ef7ad94a40eb020da3c532b317232fa56dc564637b331a8e8832eab84269f00b506602c8594b7f7da5a5d8d851fff6ab1d38a354fc8e0b8958e2a9e5ce2d7e50ec36d761d9505fe5e1f317257e2df2952fcd4c93b85278c20488b4ccaee94db3fec1ce8283473e4b493843fa73abe99af8bafce29170b2b863b9513b5a47312991f60c5a4f6872b5d574212bf00d797c0bea3c0f7dfd748e63679fda9b1c50f2df74de38f38e004ae0df997a10db31d209cacbf58ba0678bfe7cd0985bc43258d72d8d5106c21635ae1e527eb01fca3032d50d97756ec9ee756eaba7f21652a808a4e2539e838ef7ec4b178b29e3b976c46bd0ecdd32c1fb75e6e0aef2d8b5661f595a98023f3520381aba8da6cce785dbb0a0bba025478d75ee749619cdb7c42a21098ece86a00c6c2046c1e00000063c69000000000000" + # (p^12 - 1) / r * 3 + BigInt[4271].fromHex"0x518fe3a450394da01ed0ec73865aed18d4251c557c299312d07b5d31105598be5439b32fda943a26e8d85c306e6c1941dd3f9d646d87211c240f5489c67b1a8663c49da97a2880dc48213527e51d370acd05663ffda035ca31c4ba994c89d66c0c97066502f8ef19bb008e047c24cf96e02493f4683ffdc39075cc1c01df9fd0ec1dc0419176c010ac1a83b777201a77f8dab474e99c59ae840de7362f7c231d500aecc1eb52616067540d419f7f9fbfd22831919b4ac04960703d9753698941c95aa2d2a04f4bf26de9d191661a013cbb09227c09424595e2639ae94d35ce708bdec2c10628eb4f981945698ef049502d2a71994fab9898c028c73dd021f13208590be27e78f0f18a88f5ffe40157a9e9fef5aa229c0aa7fdb16a887af2c4a486258bf11fb1a5d945707a89d7bf8f67e5bb28f76a460d9a1e660cbbe91bfc456b8789d5bae1dba8cbef5b03bcd0ea30f6a7b45218292b2bf3b20ed5937cb5e2250eee395821805c6383d0286c7423beb42e79f85dab2a36df8fd154f2d89e5e9aaadaaa00e0a29ecc6e329195761d6063e0a2e136a3fb7671c9134c970a8588a7f3144642a10a5af77c105f5e90987f28c6604c5dcb604c02f7d642f7f819eea6fadb8aace7c4e146a17dab2c644d4372c6979845f261b4a20cd88a20325e0c0fc806bd9f60a8502fa8f466b6919311e232e06fd6a861cb5dc24d69274c7e631cac6b93e0254460d445a0000012b53b000000000000" + const BLS12_381_finalexponent = block: # (p^12 - 1) / r # BigInt[4314].fromHex"0x2ee1db5dcc825b7e1bda9c0496a1c0a89ee0193d4977b3f7d4507d07363baa13f8d14a917848517badc3a43d1073776ab353f2c30698e8cc7deada9c0aadff5e9cfee9a074e43b9a660835cc872ee83ff3a0f0f1c0ad0d6106feaf4e347aa68ad49466fa927e7bb9375331807a0dce2630d9aa4b113f414386b0e8819328148978e2b0dd39099b86e1ab656d2670d93e4d7acdd350da5359bc73ab61a0c5bf24c374693c49f570bcd2b01f3077ffb10bf24dde41064837f27611212596bc293c8d4c01f25118790f4684d0b9c40a68eb74bb22a40ee7169cdc1041296532fef459f12438dfc8e2886ef965e61a474c5c85b0129127a1b5ad0463434724538411d1676a53b5a62eb34c05739334f46c02c3f0bd0c55d3109cd15948d0a1fad20044ce6ad4c6bec3ec03ef19592004cedd556952c6d8823b19dadd7c2498345c6e5308f1c511291097db60b1749bf9b71a9f9e0100418a3ef0bc627751bbd81367066bca6a4c1b6dcfc5cceb73fc56947a403577dfa9e13c24ea820b09c1d9f7c31759c3635de3f7a3639991708e88adce88177456c49637fd7961be1a4c7e79fb02faa732e2f3ec2bea83d196283313492caa9d4aff1c910e9622d2a73f62537f2701aaef6539314043f7bbce5b78c7869aeb2181a67e49eeed2161daf3f881bd88592d767f67c4717489119226c2f011d4cab803e9d71650a6f80698e2f8491d12191a04406fbc8fbd5f48925f98630e68bfb24c0bcb9b55df57510" # (p^12 - 1) / r * 3 BigInt[4316].fromHex"0x8ca592196587127a538fd40dc3e541f9dca04bb7dc671be77cf17715a2b2fe3bea73dfb468d8f473094aecb7315a664019fbd84913caba6579c08fd42009fe1bd6fcbce15eacb2cf3218a165958cb8bfdae2d2d54207282314fc0dea9d6ff3a07dbd34efb77b732ba5f994816e296a72928cfee133bdc3ca9412b984b9783d9c6aa81297ab1cd294a502304773528bbae8706979f28efa0d355b0224e2513d6e4a5d3bb4dde0523678105d9167ff1323d6e99ac312d8a7d762336370c4347bb5a7e405d6f3496b2dd38e722d4c1f3ac25e3167ec2cb543d69430c37c2f98fcdd0dd36caa9f5aa7994cec31b24ed5e515911037b376e521070d29c9d56cfa8c3574363efb20f28c19e4105ab99edd44084bd23725017931d6740bda71e5f07600ce6b407e543c4bc40bcd4c0b600e6c98003bf8548986b14d9098746dc89d154af91ad54f337b31c79222145dd3ed254fdeda0300c49ebcd2352765f533883a3513435f3ee452496f5166c25bf503bd6ec0a0679efda3b46ebf86211d458de749460d4a2a19abe6ea2accb451ab9a096b98465d044dc2a7f86c253a4ee57b6df108eff598a8dbc483bf8b74c2789939db85ffd7e0fd55b32bc26877f5be26fa7d750500ce2fab93c0cbe7336b126a5693d0c16484f37addccc7642590dbe98538990b88637e374d545d9b34b67448d0357e60280bbd8542f1f4e813caa8e8db57364b4e0cc14f35af381dd9b71ec9292b3a3f16e42362d2019e05f30" +{.experimental: "dynamicBindSym".} + +macro get(C: static Curve, value: untyped): untyped = + return bindSym($C & "_" & $value) + func millerLoopGenericBLS12*[C: static Curve]( f: var Fp12[C], P: ECP_SWei_Aff[Fp[C]], @@ -95,9 +114,6 @@ func millerLoopGenericBLS12*[C: static Curve]( # or we special case line addition of T and -T (it's a vertical line) # or we ensure the loop is done for a number of iterations strictly less # than the curve order which is the case for BLS12 curves - - static: doAssert C == BLS12_381, "Only BLS12-381 is supported at the moment" - var T {.noInit.}: ECP_SWei_Proj[Fp2[C]] line {.noInit.}: Line[Fp2[C], C.getSexticTwist()] @@ -107,22 +123,29 @@ func millerLoopGenericBLS12*[C: static Curve]( nQ.neg(Q) f.setOne() - template u: untyped = BLS12_381_param - let u3 = 3*BLS12_381_param + template mul(f, line): untyped = + when C.getSexticTwist() == D_Twist: + f.mul_sparse_by_line_xyz000(line) + else: + f.mul_sparse_by_line_xy000z(line) + + template u: untyped = C.get(ate_param) + let u3 = 3*C.get(ate_param) for i in countdown(u3.bits - 2, 1): f.square() line.line_double(T, P) - f.mul_sparse_by_line_xy000z(line) + + f.mul(line) let naf = u3.bit(i).int8 - u.bit(i).int8 # This can throw exception if naf == 1: line.line_add(T, Q, P) - f.mul_sparse_by_line_xy000z(line) + f.mul(line) elif naf == -1: line.line_add(T, nQ, P) - f.mul_sparse_by_line_xy000z(line) + f.mul(line) - when BLS12_381_param_isNeg: # TODO generic + when C.get(ate_param_isNeg): # In GT, x^-1 == conjugate(x) # Remark 7.1, chapter 7.1.1 of Guide to Pairing-Based Cryptography, El Mrabet, 2017 f.conj() @@ -130,8 +153,7 @@ func millerLoopGenericBLS12*[C: static Curve]( func finalExpGeneric[C: static Curve](f: var Fp12[C]) = ## A generic and slow implementation of final exponentiation ## for sanity checks purposes. - static: doAssert C == BLS12_381, "Only BLS12-381 is supported at the moment" - f.powUnsafeExponent(BLS12_381_finalexponent, window = 3) + f.powUnsafeExponent(C.get(finalexponent), window = 3) func pairing_bls12_reference*[C](gt: var Fp12[C], P: ECP_SWei_Proj[Fp[C]], Q: ECP_SWei_Proj[Fp2[C]]) = ## Compute the optimal Ate Pairing for BLS12 curves @@ -154,7 +176,7 @@ func cycl_sqr_repeated(f: var Fp12, num: int) = for _ in 0 ..< num: f.cyclotomic_square() -func pow_xdiv2(r: var Fp12[BLS12_381], a: Fp12[BLS12_381], invert = BLS12_381_param_isNeg) = +func pow_xdiv2(r: var Fp12[BLS12_381], a: Fp12[BLS12_381], invert = BLS12_381_ate_param_isNeg) = ## f^(x/2) with x the curve parameter ## For BLS12_381 f^-0xd201000000010000 @@ -173,12 +195,41 @@ func pow_xdiv2(r: var Fp12[BLS12_381], a: Fp12[BLS12_381], invert = BLS12_381_pa if invert: r.cyclotomic_inv() -func pow_x(r: var Fp12[BLS12_381], a: Fp12[BLS12_381], invert = BLS12_381_param_isNeg) = +func pow_x(r: var Fp12[BLS12_381], a: Fp12[BLS12_381], invert = BLS12_381_ate_param_isNeg) = ## f^x with x the curve parameter ## For BLS12_381 f^-0xd201000000010000 r.pow_xdiv2(a, invert) r.cyclotomic_square() +func pow_x(r: var Fp12[BLS12_377], a: Fp12[BLS12_377], invert = BLS12_377_ate_param_isNeg) = + ## f^x with x the curve parameter + ## For BLS12_377 f^-0x8508c00000000001 + ## Warning: The parameter is odd and needs a correction + r.cyclotomic_square(a) + r *= a + r.cyclotomic_square() + r *= a + let t111 = r + + r.cycl_sqr_repeated(2) + let t111000 = r + + r *= t111 + let t100011 = r + + r.cyclotomic_square() + r *= t100011 + r *= t111000 + + r.cycl_sqr_repeated(10) + r *= t100011 + + r.cycl_sqr_repeated(46) + r *= a + + if invert: + r.cyclotomic_inv() + func finalExpHard_BLS12*[C: static Curve](f: var Fp12[C]) = ## Hard part of the final exponentiation ## Specialized for BLS12 curves @@ -208,7 +259,10 @@ func finalExpHard_BLS12*[C: static Curve](f: var Fp12[C]) = v2.cyclotomic_square(f) # v2 = f² # (x−1)² - v0.pow_xdiv2(v2) # v0 = (f²)^(x/2) = f^x + when C.get(ate_param).isEven.bool: + v0.pow_xdiv2(v2) # v0 = (f²)^(x/2) = f^x + else: + v0.pow_x(f) v1.cyclotomic_inv(f) # v1 = f^-1 v0 *= v1 # v0 = f^(x-1) v1.pow_x(v0) # v1 = (f^(x-1))^x diff --git a/constantine/pairing/pairing_bn.nim b/constantine/pairing/pairing_bn.nim index ea5efbe..1a2256c 100644 --- a/constantine/pairing/pairing_bn.nim +++ b/constantine/pairing/pairing_bn.nim @@ -43,15 +43,16 @@ import # https://eprint.iacr.org/2009/615.pdf # TODO: should be part of curve parameters +# The bit count must be exact for the Miller loop const BN254_Snarks_ate_param = block: # BN Miller loop is parametrized by 6u+2 - BigInt[67].fromHex"0x19d797039be763ba8" + BigInt[65+2].fromHex"0x19d797039be763ba8" const BN254_Snarks_ate_param_isNeg = false const BN254_Nogami_ate_param = block: # BN Miller loop is parametrized by 6u+2 - BigInt[67].fromHex"0x18300000000000004" # 65+2 bit for NAF x3 encoding + BigInt[65+2].fromHex"0x18300000000000004" # 65+2 bit for NAF x3 encoding const BN254_Nogami_ate_param_isNeg = true @@ -116,22 +117,28 @@ func millerLoopGenericBN*[C: static Curve]( nQ.neg(Q) f.setOne() + template mul(f, line): untyped = + when C.getSexticTwist() == D_Twist: + f.mul_sparse_by_line_xyz000(line) + else: + f.mul_sparse_by_line_xy000z(line) + template u: untyped = C.get(ate_param) let u3 = 3*C.get(ate_param) for i in countdown(u3.bits - 2, 1): f.square() line.line_double(T, P) - f.mul_sparse_by_line_xyz000(line) + f.mul(line) let naf = u3.bit(i).int8 - u.bit(i).int8 # This can throw exception if naf == 1: line.line_add(T, Q, P) - f.mul_sparse_by_line_xyz000(line) + f.mul(line) elif naf == -1: line.line_add(T, nQ, P) - f.mul_sparse_by_line_xyz000(line) + f.mul(line) - when C.get(ate_param_isNeg): # TODO generic + when C.get(ate_param_isNeg): # In GT, x^-1 == conjugate(x) # Remark 7.1, chapter 7.1.1 of Guide to Pairing-Based Cryptography, El Mrabet, 2017 f.conj() diff --git a/constantine/tower_field_extensions/exponentiations.nim b/constantine/tower_field_extensions/exponentiations.nim index ec48454..f6861e5 100644 --- a/constantine/tower_field_extensions/exponentiations.nim +++ b/constantine/tower_field_extensions/exponentiations.nim @@ -221,16 +221,18 @@ func isSquare*(a: QuadraticExt): SecretBool = # # https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-08#appendix-G.5 # https://eprint.iacr.org/2012/685 - - mixin fromComplexExtension # TODO: relax this - static: doAssert a.fromComplexExtension() + mixin fromComplexExtension var tv1{.noInit.}, tv2{.noInit.}: typeof(a.c0) tv1.square(a.c0) # a0² tv2.square(a.c1) # - β a1² with β = 𝑖² in a complex extension field + when a.fromComplexExtension(): + tv1 += tv2 # a0 - (-1) a1² + else: + tv2 *= NonResidue + tv1 -= tv2 - tv1 += tv2 # a0 - (-1) a1² result = tv1.isSquare() func sqrt_if_square*(a: var QuadraticExt): SecretBool = @@ -243,16 +245,18 @@ func sqrt_if_square*(a: var QuadraticExt): SecretBool = # # Implementation via the complex method (which confusingly does not require a complex field) # We make it constant-time via conditional copies - - mixin fromComplexExtension # TODO: relax this - static: doAssert a.fromComplexExtension() + mixin fromComplexExtension var t1{.noInit.}, t2{.noInit.}, t3{.noInit.}: typeof(a.c0) t1.square(a.c0) # a0² t2.square(a.c1) # - β a1² with β = 𝑖² in a complex extension field + when a.fromComplexExtension(): + t1 += t2 # a0 - (-1) a1² + else: + t2 *= NonResidue + t1 -= t2 - t1 += t2 # a0 - (-1) a1² result = t1.sqrt_if_square() t2.sum(a.c0, t1) diff --git a/sage/curve_family_bls12.sage b/sage/curve_family_bls12.sage index 8a8f61c..f6ac6ba 100644 --- a/sage/curve_family_bls12.sage +++ b/sage/curve_family_bls12.sage @@ -92,7 +92,10 @@ def compute_curve_characteristic(x_str): if __name__ == "__main__": # Usage + # BLS12-381 # sage sage/curve_family_bls12.sage '-(2^63 + 2^62 + 2^60 + 2^57 + 2^48 + 2^16)' + # BLS12-377 + # sage sage/curve_family_bls12.sage '3 * 2^46 * (7 * 13 * 499) + 1' from argparse import ArgumentParser diff --git a/sage/frobenius_bls12_377.sage b/sage/frobenius_bls12_377.sage index 473f17a..8b0499a 100644 --- a/sage/frobenius_bls12_377.sage +++ b/sage/frobenius_bls12_377.sage @@ -41,9 +41,27 @@ G2 = EllipticCurve(Fp2, [0, b/SNR]) # Utilities def fp2_to_hex(a): v = vector(a) - return Integer(v[0]).hex() + ' + β * ' + Integer(v[1]).hex() + return '0x' + Integer(v[0]).hex() + ' + β * ' + '0x' + Integer(v[1]).hex() -# Frobenius constants (D type: use SNR, M type use 1/SNR) +# Frobenius map constants +print('\nFrobenius extension field constants') +FrobConst_map = SNR^((p-1)/6) +FrobConst_map_list = [] +cur = Fp2([1, 0]) + +for i in range(6): + FrobConst_map_list.append(cur) + print(f'FrobConst_map_{i} : {fp2_to_hex(cur)}') + cur *= FrobConst_map +print('') +for i in range(6): + print(f'FrobConst_map_{i}_pow2 : {fp2_to_hex(FrobConst_map_list[i]*conjugate(FrobConst_map_list[i]))}') +print('') +for i in range(6): + print(f'FrobConst_map_{i}_pow3 : {fp2_to_hex(FrobConst_map_list[i]**2 * conjugate(FrobConst_map_list[i]))}') + +# Frobenius psi constants (D type: use SNR, M type use 1/SNR) +print('\nψ (Psi) - Untwist-Frobenius-Twist constants') FrobConst_psi = SNR^((p-1)/6) FrobConst_psi_2 = FrobConst_psi * FrobConst_psi FrobConst_psi_3 = FrobConst_psi_2 * FrobConst_psi diff --git a/sage/lattice_decomposition_bls12_377_g1.sage b/sage/lattice_decomposition_bls12_377_g1.sage new file mode 100644 index 0000000..63f5492 --- /dev/null +++ b/sage/lattice_decomposition_bls12_377_g1.sage @@ -0,0 +1,223 @@ +# 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. + +# ############################################################ +# +# BLS12-381 GLS Endomorphism +# Lattice Decomposition +# +# ############################################################ + +# Parameters +x = 3 * 2^46 * (7 * 13 * 499) + 1 +p = (x - 1)^2 * (x^4 - x^2 + 1)//3 + x +r = x^4 - x^2 + 1 +print('p : ' + p.hex()) +print('r : ' + r.hex()) + +# Cube root of unity (mod r) formula for any BLS12 curves +lambda1_r = x^2 - 1 +assert lambda1_r^3 % r == 1 +print('λᵩ1 : ' + lambda1_r.hex()) +print('λᵩ1+r: ' + (lambda1_r+r).hex()) + +lambda2_r = x^4 +assert lambda2_r^3 % r == 1 +print('λᵩ2 : ' + lambda2_r.hex()) + +# Finite fields +F = GF(p) + +# Curves +b = 1 +G1 = EllipticCurve(F, [0, b]) + +cofactorG1 = G1.order() // r + +print('') +print('cofactor G1: ' + cofactorG1.hex()) +print('') + +(phi1, phi2) = (root for root in GF(p)(1).nth_root(3, all=True) if root != 1) +print('𝜑1 :' + Integer(phi1).hex()) +print('𝜑2 :' + Integer(phi2).hex()) +assert phi1^3 % p == 1 +assert phi2^3 % p == 1 + +def clearCofactorG1(P): + return cofactorG1 * P + +# Test generator +set_random_seed(1337) + +# Check +def checkEndo(): + Prand = G1.random_point() + P = clearCofactorG1(Prand) + assert P != G1([0, 1, 0]) # Infinity + + (Px, Py, Pz) = P + Qendo1 = G1([Px*phi1 % p, Py, Pz]) + Qendo2 = G1([Px*phi2 % p, Py, Pz]) + + Q1 = lambda1_r * P + Q2 = lambda2_r * P + + assert P != Q1 + assert P != Q2 + + assert (F(Px)*F(phi1))^3 == F(Px)^3 + assert (F(Px)*F(phi2))^3 == F(Px)^3 + + assert Q1 == Qendo1 + assert Q2 == Qendo1 + + print('Endomorphism OK with 𝜑1') + +checkEndo() + +# Decomposition generated by LLL-algorithm and Babai rounding +# to solve the Shortest (Basis) Vector Problem +# Lattice from Guide to Pairing-Based Cryptography +Lat = [ + [x^2-1, -1], + [1, x^2] +] +ahat = [x^2, 1] +n = int(r).bit_length() +n = int(((n + 64 - 1) // 64) * 64) # round to next multiple of 64 +v = [Integer(a << n) // r for a in ahat] + +def pretty_print_lattice(Lat): + latHex = [['0x' + x.hex() if x >= 0 else '-0x' + (-x).hex() for x in vec] for vec in Lat] + maxlen = max([len(cell) for row in latHex for cell in row]) + for row in latHex: + row = ' '.join(cell.rjust(maxlen + 2) for cell in row) + print(row) + +print('\nLattice') +pretty_print_lattice(Lat) + +print('\nbasis:') +print(' 𝛼\u03050: 0x' + v[0].hex()) +print(' 𝛼\u03051: 0x' + v[1].hex()) +print('') + +maxInfNorm = abs(x^2 + 1) +print('\nmax infinity norm:') +print(' ||(a0 , a1)||∞ ≤ 0x' + str(maxInfNorm.hex())) +print(' infinity norm bitlength: ' + str(int(maxInfNorm).bit_length())) + +# Contrary to Faz2013 paper, we use the max infinity norm +# to properly dimension our recoding instead of ⌈log2 r/m⌉ + 1 +# which fails for some inputs +# +# +1 for signed column +# Optional +1 for handling negative miniscalars +L = int(maxInfNorm).bit_length() + 1 +L += 1 + +def getGLV1_decomp(scalar): + + maxLen = (int(r).bit_length() + 1) // 2 + 1 + + a0 = (v[0] * scalar) >> n + a1 = (v[1] * scalar) >> n + + k0 = scalar - a0 * Lat[0][0] - a1 * Lat[1][0] + k1 = 0 - a0 * Lat[0][1] - a1 * Lat[1][1] + + assert int(k0).bit_length() <= maxLen + assert int(k1).bit_length() <= maxLen + + assert scalar == (k0 + k1 * (lambda1_r % r)) % r + assert scalar == (k0 + k1 * (lambda2_r % r)) % r + + return k0, k1 + +def recodeScalars(k): + m = 2 + + b = [[0] * L, [0] * L] + b[0][L-1] = 0 + for i in range(0, L-1): # l-2 inclusive + b[0][i] = 1 - ((k[0] >> (i+1)) & 1) + for j in range(1, m): + for i in range(0, L): + b[j][i] = k[j] & 1 + k[j] = k[j]//2 + (b[j][i] & b[0][i]) + + return b + +def buildLut(P0, P1): + m = 2 + lut = [0] * (1 << (m-1)) + lut[0] = P0 + lut[1] = P0 + P1 + return lut + +def pointToString(P): + (Px, Py, Pz) = P + return '(x: ' + Integer(Px).hex() + ', y: ' + Integer(Py).hex() + ', z: ' + Integer(Pz).hex() + ')' + +def scalarMulEndo(scalar, P0): + m = 2 + + print('L: ' + str(L)) + + print('scalar: ' + Integer(scalar).hex()) + + k0, k1 = getGLV1_decomp(scalar) + print('k0: ' + k0.hex()) + print('k1: ' + k1.hex()) + + P1 = (lambda1_r % r) * P0 + (Px, Py, Pz) = P0 + P1_endo = G1([Px*phi1 % p, Py, Pz]) + assert P1 == P1_endo + + expected = scalar * P0 + decomp = k0*P0 + k1*P1 + assert expected == decomp + + print('------ recode scalar -----------') + even = k0 & 1 == 0 + if even: + k0 += 1 + + b = recodeScalars([k0, k1]) + print('b0: ' + str(list(reversed(b[0])))) + print('b1: ' + str(list(reversed(b[1])))) + + print('------------ lut ---------------') + + lut = buildLut(P0, P1) + + print('------------ mul ---------------') + # b[0][L-1] is always 0 + Q = lut[b[1][L-1]] + for i in range(L-2, -1, -1): + Q *= 2 + Q += (1 - 2 * b[0][i]) * lut[b[1][i]] + + if even: + Q -= P0 + + print('final Q: ' + pointToString(Q)) + print('expected: ' + pointToString(expected)) + assert Q == expected + +# Test generator +set_random_seed(1337) + +for i in range(1): + print('---------------------------------------') + scalar = randrange(r) # Pick an integer below curve order + P = G1.random_point() + P = clearCofactorG1(P) + scalarMulEndo(scalar, P) diff --git a/sage/lattice_decomposition_bls12_377_g2.sage b/sage/lattice_decomposition_bls12_377_g2.sage new file mode 100644 index 0000000..155bee6 --- /dev/null +++ b/sage/lattice_decomposition_bls12_377_g2.sage @@ -0,0 +1,375 @@ +# 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. + +# ############################################################ +# +# BLS12-381 GLS Endomorphism +# Lattice Decomposition +# +# ############################################################ + +# Parameters +x = 3 * 2^46 * (7 * 13 * 499) + 1 +p = (x - 1)^2 * (x^4 - x^2 + 1)//3 + x +r = x^4 - x^2 + 1 +t = x+1 +print(' Prime modulus p: 0x' + p.hex()) +print(' Curve order r: 0x' + r.hex()) +print(' trace t: 0x' + t.hex()) + +# Finite fields +Fp = GF(p) +K2. = PolynomialRing(Fp) +Fp2. = Fp.extension(u^2+5) + +SNR = Fp2([0, 1]) # Sextic Non-Residue for Sextic Twist + +# Curves +b = 1 +G1 = EllipticCurve(Fp, [0, b]) +G2 = EllipticCurve(Fp2, [0, b/SNR]) + +# https://crypto.stackexchange.com/questions/64064/order-of-twisted-curve-in-pairings +# https://math.stackexchange.com/questions/144194/how-to-find-the-order-of-elliptic-curve-over-finite-field-extension +cofactorG1 = G1.order() // r +cofactorG2 = G2.order() // r + +print('') +print('cofactor G1: ' + cofactorG1.hex()) +print('cofactor G2: ' + cofactorG2.hex()) +print('') + +# Frobenius constants (D type: use SNR, M type use 1/SNR) +FrobConst_psi = SNR^((p-1)/6) +FrobConst_psi_2 = FrobConst_psi * FrobConst_psi +FrobConst_psi_3 = FrobConst_psi_2 * FrobConst_psi +FrobConst_psi2_2 = FrobConst_psi_2 * FrobConst_psi_2^p +FrobConst_psi2_3 = FrobConst_psi_3 * FrobConst_psi_3^p + +def psi(P): + (Px, Py, Pz) = P + return G2([ + FrobConst_psi_2 * Px.frobenius(1), + FrobConst_psi_3 * Py.frobenius(1) + # Pz.frobenius() - Always 1 after extract + ]) + +def psi2(P): + (Px, Py, Pz) = P + return G2([ + FrobConst_psi2_2 * Px.frobenius(2), + FrobConst_psi2_3 * Py.frobenius(2) + # Pz - Always 1 after extract + ]) + +def clearCofactorG2(P): + return cofactorG2 * P + +# Test generator +set_random_seed(1337) + +# Check +def checkEndo(): + P = G2.random_point() + P = clearCofactorG2(P) + + (Px, Py, Pz) = P + + # Galbraith-Lin-Scott, 2008, Theorem 1 + assert psi(psi(P)) - t*psi(P) + p*P == G2([0, 1, 0]) + # Galbraith-Scott, 2008, Lemma 1 + # k-th cyclotomic polynomial with k = 12 + assert psi2(psi2(P)) - psi2(P) + P == G2([0, 1, 0]) + + assert p % r == (t-1) % r + # assert (p^4 - p^2 + 1) % r == 0 + assert ((t-1)^4 - (t-1)^2 + 1) % r == 0 + assert (t-1)*P == (p % r)*P + assert (t-1)*P == psi(P) + + print('Endomorphism OK') + +checkEndo() + +def subgroup_check(P): + ppP = psi2(P) + assert x * psi(ppP) - ppP + P == G2([0,1,0]) + +# Decomposition generated by LLL-algorithm and Babai rounding +# to solve the Shortest (Basis) Vector Problem +# +# TODO: This lattice is generating wrong result +# Lattice from Guide to Pairing-Based Cryptography +# Lat = [ +# [ x, 1, 0, 0], +# [ 0, x, 1, 0], +# [ 0, 0, x, 1], +# [ 1, 0,-1, x] +# ] +# ahat = [x*(x^2+1), -(x^2+1), x, -1] + +# Lattice from my own LLL+Babai rounding routines +Lat = Matrix([ + [-x, 1, 0, 0], + [ 0,-x, 1, 0], + [ 0, 0,-x, 1], + [ 1, 0,-1,-x] +]) +# print('Lat: ' + str(Lat)) +ahat = vector([r, 0, 0, 0]) * Lat.inverse() +# print('ahat: ' + str(ahat)) + +n = int(r).bit_length() +n = int(((n + 64 - 1) // 64) * 64) # round to next multiple of 64 +v = [Integer(int(a) << n) // r for a in ahat] + +def pretty_print_lattice(Lat): + latHex = [['0x' + x.hex() if x >= 0 else '-0x' + (-x).hex() for x in vec] for vec in Lat] + maxlen = max([len(cell) for row in latHex for cell in row]) + for row in latHex: + row = ' '.join(cell.rjust(maxlen + 2) for cell in row) + print(row) + +print('\nLattice') +pretty_print_lattice(Lat) + +print('\nbasis:') +print(' 𝛼\u03050: 0x' + v[0].hex()) +print(' 𝛼\u03051: 0x' + v[1].hex()) +print(' 𝛼\u03052: 0x' + v[2].hex()) +print(' 𝛼\u03053: 0x' + v[3].hex()) +print('') + +maxInfNorm = abs(x + 2) +print('\nmax infinity norm:') +print(' ||(a0, a1, a2, a3)||∞ ≤ 0x' + str(maxInfNorm.hex())) +print(' infinity norm bitlength: ' + str(int(maxInfNorm).bit_length())) + +# Contrary to Faz2013 paper, we use the max infinity norm +# to properly dimension our recoding instead of ⌈log2 r/m⌉ + 1 +# which fails for some inputs +# +1 for signed column +# Optional +1 for handling negative miniscalars +L = int(maxInfNorm).bit_length() + 1 +L += 1 + +lambda1 = (t-1) % r +lambda2 = lambda1^2 % r +lambda3 = lambda1^3 % r + +def getGLV2_decomp(scalar): + + maxLen = (int(r).bit_length() + 3) // 4 + 1 + maxLen += 1 # Deal with negative miniscalars + + a0 = (v[0] * scalar) >> n + a1 = (v[1] * scalar) >> n + a2 = (v[2] * scalar) >> n + a3 = (v[3] * scalar) >> n + + k0 = scalar - a0 * Lat[0][0] - a1 * Lat[1][0] - a2 * Lat[2][0] - a3 * Lat[3][0] + k1 = 0 - a0 * Lat[0][1] - a1 * Lat[1][1] - a2 * Lat[2][1] - a3 * Lat[3][1] + k2 = 0 - a0 * Lat[0][2] - a1 * Lat[1][2] - a2 * Lat[2][2] - a3 * Lat[3][2] + k3 = 0 - a0 * Lat[0][3] - a1 * Lat[1][3] - a2 * Lat[2][3] - a3 * Lat[3][3] + + print("k0.bitlength(): " + str(int(k0).bit_length())) + print("k1.bitlength(): " + str(int(k1).bit_length())) + print("k2.bitlength(): " + str(int(k2).bit_length())) + print("k3.bitlength(): " + str(int(k3).bit_length())) + + print('k0: ' + k0.hex()) + print('k1: ' + k1.hex()) + print('k2: ' + k2.hex()) + print('k3: ' + k3.hex()) + + assert scalar == (k0 + k1*lambda1 + k2*lambda2 + k3*lambda3) % r + + assert int(k0).bit_length() <= maxLen + assert int(k1).bit_length() <= maxLen + assert int(k2).bit_length() <= maxLen + assert int(k3).bit_length() <= maxLen + + return k0, k1, k2, k3 + +def recodeScalars(k): + m = 4 + + b = [[0] * L, [0] * L, [0] * L, [0] * L] + b[0][L-1] = 0 + for i in range(0, L-1): # l-2 inclusive + b[0][i] = 1 - ((k[0] >> (i+1)) & 1) + for j in range(1, m): + for i in range(0, L): + b[j][i] = k[j] & 1 + k[j] = k[j]//2 + (b[j][i] & b[0][i]) + + return b + +def clearBit(v, bit): + return v & ~int(1 << bit) + +def buildLut(P0, P_endos): + m = 4 + assert len(P_endos) == m-1 + lut = [0] * (1 << (m-1)) + lut[0] = P0 + + lutS = [''] * (1 << (m-1)) + lutS[0] = 'P0' + endoS = ['P1', 'P2', 'P3'] + + for u in range(1, 1 << (m-1)): + msb = u.bit_length() - 1 + idx = clearBit(u, msb) + lut[u] = lut[clearBit(u, msb)] + P_endos[msb] + + lutS[u] = lutS[clearBit(u, msb)] + ' + ' + endoS[msb] + + print('LUT: ' + str(lutS)) + return lut + +def pointToString(P): + (Px, Py, Pz) = P + vPx = vector(Px) + vPy = vector(Py) + result = 'Point(\n' + result += ' Px: ' + Integer(vPx[0]).hex() + ' + β * ' + Integer(vPx[1]).hex() + '\n' + result += ' Py: ' + Integer(vPy[0]).hex() + ' + β * ' + Integer(vPy[1]).hex() + '\n' + result += ')' + return result + +def getIndex(glvRecoding, bit): + m = 4 + index = 0 + for k in range(1, m): + index |= ((glvRecoding[k][bit] & 1) << (k-1)) + return index + +def updateFactors(factors, recoded, bit): + index = getIndex(recoded, bit) + if recoded[0][bit] == 0: # Positive + factors[0] += 1 + factors[1] += (index >> 0) & 1 + factors[2] += (index >> 1) & 1 + factors[3] += (index >> 2) & 1 + else: + factors[0] -= 1 + factors[1] -= (index >> 0) & 1 + factors[2] -= (index >> 1) & 1 + factors[3] -= (index >> 2) & 1 + +def doubleFactors(factors): + for i in range(len(factors)): + factors[i] *= 2 + +def printFactors(factors): + print('Multiplication done: ') + for i in range(len(factors)): + print(f' f{i}: {factors[i].hex()}') + +def scalarMulEndo(scalar, P0): + m = 4 + + print('L: ' + str(L)) + + print('scalar: ' + Integer(scalar).hex()) + + k0, k1, k2, k3 = getGLV2_decomp(scalar) + + P1 = psi(P0) + P2 = psi2(P0) + P3 = psi(P2) + + expected = scalar * P0 + decomp = k0*P0 + k1*P1 + k2*P2 + k3*P3 + print('expected: ' + pointToString(expected)) + print('decomp: ' + pointToString(decomp)) + assert expected == decomp + + # Alternative to adding an extra bit + # to deal with miniscalars + # if k0 < 0: k0 = -k0; P0 = -P0 + # if k1 < 0: k1 = -k1; P1 = -P1 + # if k2 < 0: k2 = -k2; P2 = -P2 + # if k3 < 0: k3 = -k3; P3 = -P3 + # assert expected == k0*P0 + k1*P1 + k2*P2 + k3*P3 + + # Somehow the recoding doesn't cope with first scalar being negative + if k0 < 0: + k0 = -k0 + P0 = -P0 + + print('------ recode scalar -----------') + even = k0 & 1 == 0 + print('was even: ' + str(even)) + if even: + k0 += 1 + + b = recodeScalars([k0, k1, k2, k3]) + print('b0: ' + str(list(reversed(b[0])))) + print('b1: ' + str(list(reversed(b[1])))) + print('b2: ' + str(list(reversed(b[2])))) + print('b3: ' + str(list(reversed(b[3])))) + print('------------ lut ---------------') + + lut = buildLut(P0, [P1, P2, P3]) + + print('------------ mul ---------------') + # b[0][L-1] is always 0 + print(f'L-1: {getIndex(b, L-1)}') + print(f'L-2: {getIndex(b, L-2)}') + print(f'L-3: {getIndex(b, L-3)}') + print(f'L-4: {getIndex(b, L-4)}') + print(f'L-5: {getIndex(b, L-5)}') + print(f'L-6: {getIndex(b, L-6)}') + + factors = [0, 0, 0, 0] # Track the decomposed scalar applied (debugging) + updateFactors(factors, b, L-1) + + Q = lut[getIndex(b, L-1)] + for bit in range(L-2, -1, -1): + Q *= 2 + Q += (1 - 2 * b[0][bit]) * lut[getIndex(b, bit)] + + doubleFactors(factors) + updateFactors(factors, b, bit) + + if even: + Q -= P0 + print('----') + print('final Q: ' + pointToString(Q)) + print('expected: ' + pointToString(expected)) + print('----') + printFactors(factors) + print('Mul expected:') + print(' k0: ' + k0.hex()) + print(' k1: ' + k1.hex()) + print(' k2: ' + k2.hex()) + print(' k3: ' + k3.hex()) + + assert Q == expected + +# Test generator +set_random_seed(1337) + +for i in range(1): + print('---------------------------------------') + scalar = randrange(r) # Pick an integer below curve order + # P = G2.random_point() + # P = clearCofactorG2(P) + + scalar = Integer('0x9d432eb58ec68bbc09d10961451d99c7796fb2f795eca603d6feaf3e2a1634b') + P = G2([ + Fp2([Integer('0x267401f3ef554fe74ae131d56a10edf14ae40192654901b4618d2bf7af22e77c2a9b79e407348dbd4aad13ca73b33a'), + Integer('0x12dcca838f46a3e0418e5dd8b978362757a16bfd78f0b77f4a1916ace353938389ae3ea228d0eb5020a0aaa58884aec')]), + Fp2([Integer('0x11799118d2e054aabd9f74c0843fecbdc1c0d56f61c61c5854c2507ae2416e48a6b2cd3bc8bf7495a4d3d8270eafe2b'), + Integer('0x823b9f8fb9f8297734a14359fa2c2a0de275e7e638197eaaaa7cff28f9cb3101bdabb570016672455f1ecae625e294')]) + ]) + + subgroup_check(P) + scalarMulEndo(scalar, P) diff --git a/sage/lattice_decomposition_bls12_381_g1.sage b/sage/lattice_decomposition_bls12_381_g1.sage index eaf580f..5c69196 100644 --- a/sage/lattice_decomposition_bls12_381_g1.sage +++ b/sage/lattice_decomposition_bls12_381_g1.sage @@ -108,7 +108,20 @@ print(' 𝛼\u03050: 0x' + v[0].hex()) print(' 𝛼\u03051: 0x' + v[1].hex()) print('') -def getGLV2_decomp(scalar): +maxInfNorm = abs(x + 2) +print('\nmax infinity norm:') +print(' ||(a0, a1)||∞ ≤ 0x' + str(maxInfNorm.hex())) +print(' infinity norm bitlength: ' + str(int(maxInfNorm).bit_length())) + +# Contrary to Faz2013 paper, we use the max infinity norm +# to properly dimension our recoding instead of ⌈log2 r/m⌉ + 1 +# which fails for some inputs +# +1 for signed column +# Optional +1 for handling negative miniscalars +L = int(maxInfNorm).bit_length() + 1 +L += 1 + +def getGLV1_decomp(scalar): maxLen = (int(r).bit_length() + 1) // 2 + 1 @@ -128,7 +141,6 @@ def getGLV2_decomp(scalar): def recodeScalars(k): m = 2 - L = ((int(r).bit_length() + m-1) // m) + 1 # l = ⌈log2 r/m⌉ + 1 b = [[0] * L, [0] * L] b[0][L-1] = 0 @@ -154,13 +166,11 @@ def pointToString(P): def scalarMulEndo(scalar, P0): m = 2 - L = ((int(r).bit_length() + m-1) // m) + 1 # l = ⌈log2 r/m⌉ + 1 - print('L: ' + str(L)) print('scalar: ' + Integer(scalar).hex()) - k0, k1 = getGLV2_decomp(scalar) + k0, k1 = getGLV1_decomp(scalar) print('k0: ' + k0.hex()) print('k1: ' + k1.hex()) diff --git a/sage/lattice_decomposition_bls12_381_g2.sage b/sage/lattice_decomposition_bls12_381_g2.sage index 8c39431..e5b18d3 100644 --- a/sage/lattice_decomposition_bls12_381_g2.sage +++ b/sage/lattice_decomposition_bls12_381_g2.sage @@ -96,6 +96,10 @@ def checkEndo(): checkEndo() +def subgroup_check(P): + ppP = psi2(P) + assert x * psi(ppP) - ppP + P == G2([0,1,0]) + # Decomposition generated by LLL-algorithm and Babai rounding # to solve the Shortest (Basis) Vector Problem # @@ -141,6 +145,19 @@ print(' 𝛼\u03052: 0x' + v[2].hex()) print(' 𝛼\u03053: 0x' + v[3].hex()) print('') +maxInfNorm = abs(x + 2) +print('\nmax infinity norm:') +print(' ||(a0 , a1 , a2 , a3)||∞ ≤ 0x' + str(maxInfNorm.hex())) +print(' infinity norm bitlength: ' + str(int(maxInfNorm).bit_length())) + +# Contrary to Faz2013 paper, we use the max infinity norm +# to properly dimension our recoding instead of ⌈log2 r/m⌉ + 1 +# which fails for some inputs +# +1 for signed column +# Optional +1 for handling negative miniscalars +L = int(maxInfNorm).bit_length() + 1 +L += 1 + lambda1 = (t-1) % r lambda2 = lambda1^2 % r lambda3 = lambda1^3 % r @@ -181,8 +198,6 @@ def getGLV2_decomp(scalar): def recodeScalars(k): m = 4 - L = ((int(r).bit_length() + m-1) // m) + 1 # l = ⌈log2 r/m⌉ + 1 - L += 1 # Deal with negative miniscalars b = [[0] * L, [0] * L, [0] * L, [0] * L] b[0][L-1] = 0 @@ -259,9 +274,8 @@ def printFactors(factors): def scalarMulEndo(scalar, P0): m = 4 - L = ((int(r).bit_length() + m-1) // m) + 1 # l = ⌈log2 r/m⌉ + 1 - L += 1 # Deal with negative miniscalars + print('r bits: ' + str(int(r).bit_length())) print('L: ' + str(L)) print('scalar: ' + Integer(scalar).hex()) @@ -278,6 +292,16 @@ def scalarMulEndo(scalar, P0): print('decomp: ' + pointToString(decomp)) assert expected == decomp + # Alternative to adding an extra bit + # to deal with miniscalars, unfortunately broken + # for some input + # for example 0x5668a2332db27199dcfb7cbdfca6317c2ff128db26d7df68483e0a095ec8e88f + # which is missing bits for b[2] + # if k0 < 0: k0 = -k0; P0 = -P0 + # if k1 < 0: k1 = -k1; P1 = -P1 + # if k2 < 0: k2 = -k2; P2 = -P2 + # if k3 < 0: k3 = -k3; P3 = -P3 + print('------ recode scalar -----------') even = k0 & 1 == 0 print('was even: ' + str(even)) @@ -363,13 +387,29 @@ for i in range(1): # Integer('0xf5d6d74f1dd3d9c07451340b8f6990fe93a28fe5e176564eb920bf17eb02df8b6f1e626eda5542ff415f89d51943001')]) # ]) - scalar = Integer('0x5668a2332db27199dcfb7cbdfca6317c2ff128db26d7df68483e0a095ec8e88f') + # The following input fails in Constantine when negating the base point + # but not when adding an extra bit + + # scalar = Integer('0x5668a2332db27199dcfb7cbdfca6317c2ff128db26d7df68483e0a095ec8e88f') + # P = G2([ + # Fp2([Integer('0xa8c5649d2df1bae84fd9e8bfcde5113937b3acea22d67ddfedaf1fb8de8c1ef4c70591cf505c24c31e54020c2c510c3'), + # Integer('0xa0553f98229a6a067489c3ee204161c11e96f421b3e9c145dc3865b03e9d4ff6cab14c5b5308ecd31173f954463690c')]), + # Fp2([Integer('0xb29d8dfe18dc41b4826c3a102c1bf8f306cb42433cc36ee38080f47a324c02a678f9daed0a2bc577c18b9865de029f0'), + # Integer('0x558cdabf11e37c5c5e8abd668bbdd71bb3f07f320948ccaac8a207359fffe38424bfd9b1ef1d24b28b2fbb9f76faff1')]) + # ]) + + # The following fails when we have both extra bit and negation of the first + # scalar if it is negative. + # it also uses 65 bits instead of teh expected max of 64 + # And triggers an off by 1 when negating + + scalar = Integer('0x6448f296d9b1a8d81319a0b789df04c587c6165776ccf39f50a354204aabe0da') P = G2([ - Fp2([Integer('0xa8c5649d2df1bae84fd9e8bfcde5113937b3acea22d67ddfedaf1fb8de8c1ef4c70591cf505c24c31e54020c2c510c3'), - Integer('0xa0553f98229a6a067489c3ee204161c11e96f421b3e9c145dc3865b03e9d4ff6cab14c5b5308ecd31173f954463690c')]), - Fp2([Integer('0xb29d8dfe18dc41b4826c3a102c1bf8f306cb42433cc36ee38080f47a324c02a678f9daed0a2bc577c18b9865de029f0'), - Integer('0x558cdabf11e37c5c5e8abd668bbdd71bb3f07f320948ccaac8a207359fffe38424bfd9b1ef1d24b28b2fbb9f76faff1')]) + Fp2([Integer('0x5adc112fb04bf4ca642d5a7d7343ccd6b93546442d2fff5b9d32c15e456d54884cba49dd7f94ce4ddaad4018e55d0f2'), + Integer('0x5d1c5bbf5d7a833dc76ba206bfa99c281fc37941be050e18f8c6d267b2376b3634d8ad6eb951e52a6d096315abd17d6')]), + Fp2([Integer('0x15a959e54981fab9ac3c6f5bfd6fb60a50a916bd43d96a09922a54309b84812736581bfa728670cba864b08b9e391bb9'), + Integer('0xf5d6d74f1dd3d9c07451340b8f6990fe93a28fe5e176564eb920bf17eb02df8b6f1e626eda5542ff415f89d51943001')]) ]) - + subgroup_check(P) scalarMulEndo(scalar, P) diff --git a/sage/lattice_decomposition_bn254_snarks_g1.sage b/sage/lattice_decomposition_bn254_snarks_g1.sage index 6065aec..42f8e43 100644 --- a/sage/lattice_decomposition_bn254_snarks_g1.sage +++ b/sage/lattice_decomposition_bn254_snarks_g1.sage @@ -101,6 +101,19 @@ print(' 𝛼\u03050: 0x' + v[0].hex()) print(' 𝛼\u03051: 0x' + v[1].hex()) print('') +maxInfNorm = abs(6*x^2+6*x+2) +print('\nmax infinity norm:') +print(' ||(a0, a1)||∞ ≤ 0x' + str(maxInfNorm.hex())) +print(' infinity norm bitlength: ' + str(int(maxInfNorm).bit_length())) + +# Contrary to Faz2013 paper, we use the max infinity norm +# to properly dimension our recoding instead of ⌈log2 r/m⌉ + 1 +# which fails for some inputs +# +1 for signed column +# Optional +1 for handling negative miniscalars +L = int(maxInfNorm).bit_length() + 1 +L += 1 + def getGLV2_decomp(scalar): maxLen = (int(r).bit_length() + 1) // 2 + 1 @@ -121,7 +134,6 @@ def getGLV2_decomp(scalar): def recodeScalars(k): m = 2 - L = ((int(r).bit_length() + m-1) // m) + 1 # l = ⌈log2 r/m⌉ + 1 b = [[0] * L, [0] * L] b[0][L-1] = 0 @@ -147,8 +159,6 @@ def pointToString(P): def scalarMulEndo(scalar, P0): m = 2 - L = ((int(r).bit_length() + m-1) // m) + 1 # l = ⌈log2 r/m⌉ + 1 - print('L: ' + str(L)) print('scalar: ' + Integer(scalar).hex()) diff --git a/sage/lattice_decomposition_bn254_snarks_g2.sage b/sage/lattice_decomposition_bn254_snarks_g2.sage index 11f71a6..43ff16a 100644 --- a/sage/lattice_decomposition_bn254_snarks_g2.sage +++ b/sage/lattice_decomposition_bn254_snarks_g2.sage @@ -142,6 +142,19 @@ print(' 𝛼\u03052: 0x' + v[2].hex()) print(' 𝛼\u03053: 0x' + v[3].hex()) print('') +maxInfNorm = abs(5*x + 3) +print('\nmax infinity norm:') +print(' ||(a0 , a1 , a2 , a3)||∞ ≤ 0x' + str(maxInfNorm.hex())) +print(' infinity norm bitlength: ' + str(int(maxInfNorm).bit_length())) + +# Contrary to Faz2013 paper, we use the max infinity norm +# to properly dimension our recoding instead of ⌈log2 r/m⌉ + 1 +# which fails for some inputs +# +1 for signed column +# Optional +1 for handling negative miniscalars +L = int(maxInfNorm).bit_length() + 1 +L += 1 + lambda1 = (t-1) % r lambda2 = lambda1^2 % r lambda3 = lambda1^3 % r @@ -204,7 +217,6 @@ def getGLV2_decomp(scalar): def recodeScalars(k): m = 4 - L = ((int(r).bit_length() + m-1) // m) + 1 # l = ⌈log2 r/m⌉ + 1 b = [[0] * L, [0] * L, [0] * L, [0] * L] b[0][L-1] = 0 @@ -280,8 +292,6 @@ def printFactors(factors): def scalarMulEndo(scalar, P0): m = 4 - L = ((int(r).bit_length() + m-1) // m) + 1 # l = ⌈log2 r/m⌉ + 1 - print('L: ' + str(L)) print('scalar: ' + Integer(scalar).hex()) diff --git a/sage/square_root_bls12_377.sage b/sage/square_root_bls12_377.sage new file mode 100644 index 0000000..29be3fb --- /dev/null +++ b/sage/square_root_bls12_377.sage @@ -0,0 +1,263 @@ +# 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. + +# ############################################################ +# +# BLS12-377 +# Constant-time Square Root +# +# ############################################################ + +# Parameters +x = 3 * 2^46 * (7 * 13 * 499) + 1 +p = (x - 1)^2 * (x^4 - x^2 + 1)//3 + x +r = x^4 - x^2 + 1 +t = x + 1 +print('x : ' + x.hex()) +print('p : ' + p.hex()) +print('r : ' + r.hex()) +print('t : ' + t.hex()) + +def modCheck(p, pow): + ## Find q mod 2^s != 1 + q = p^pow + s = 4 + while q % s == 1: + s *= 2 + if s > q: + raise ValueError('Uh Oh') + if pow == 1: + print(f'Found: p mod {s} = {q % s}') + else: + print(f'Found: p^{pow} mod {s} = {q % s}') + +modCheck(p, 1) # Found: p mod 140737488355328 = 70368744177665 +modCheck(p, 2) # Found: p^2 mod 281474976710656 = 140737488355329 + +# On Fp +# a^((p-70368744177665+140737488355328)/140737488355328) +# would lead to a square root but there would be +# log2(140737488355328)-1 candidates +# which must be checked constant time + +def precomp_tonelli_shanks(p): + ## Precompute constants for + ## constant-time Tonelli Shanks algorithm + ## with q = p^pow returns: + ## 1. c1, the largest integer such that 2^c1 divides q - 1. + ## 2. c2 = (q - 1) / (2^c1) in ℕ + ## 3. c3 = (c2 - 1) / 2 in ℕ + ## 4. c4, a non-square value in Fq + ## 5. c5 = c4^c2 in Fq + q = p + c1 = 0 + c2 = q-1 + while c2 & 1 == 0: + c2 >>= 1 + c1 += 1 + c3 = (c2 - 1) // 2 + c4 = 1 + while kronecker(c4, q) == 1: + c4 += 1 + c5 = GF(p)(c4)^c2 + return (c1,c2,c3,c4,c5) + +def ccopy(a, b, ctl): + ## `b` if `ctl` is true, `a` if false + return int(not(bool(ctl)))*a + int(bool(ctl))*b + +def sqrt_tonelli_shanks(x, p): + ## Returns z = x² (p^pow) + (c1, c2, c3, c4, c5) = precomp_tonelli_shanks(p) + + x = GF(p)(x) + + z = x^c3 + t = z*z*x + z *= x + b = t + c = c5 + for i in range(c1, 1, -1): # c1 ... 2 + for j in range(1, i-1): # 1 ... i-2 + b *= b + z = ccopy(z, z*c, b != 1) + c *= c + t = ccopy(t, t*c, b != 1) + b = t + return z + +# for a in range(2, 30): +# if kronecker(a, p) != 1: +# continue +# # print(f'{a}^(p-1)/2 = ' + str(GF(p)(a)^((p-1)/2))) +# print(f'{a} is a quadratic residue mod p') +# b = sqrt_tonelli_shanks(a, p) +# # print(f'{b}² = {a} mod p') +# # print('b*b = ' + str(b*b)) +# assert b*b == a + +# Optimized Tonelli Shanks +# -------------------------------------------------------- + +# Finite fields +Fp = GF(p) +K2. = PolynomialRing(Fp) +Fp2. = Fp.extension(u^2+5) + +def precomp_ts(Fq): + ## From q = p^m with p the prime characteristic of the field Fp^m + ## + ## Returns (s, e) such as + ## q == s * 2^e + 1 + s = Fq.order() - 1 + e = 0 + while s & 1 == 0: + s >>= 1 + e += 1 + return s, e + +def find_any_qnr(Fq): + ## Find a quadratic Non-Residue + ## in GF(p^m) + qnr = Fq(Fq.gen()) + r = Fq.order() + while qnr.is_square(): + qnr += 1 + return qnr + +def sqrt_exponent_precomp(Fq, e): + ## Returns precomputation a^((q-1-2^e)/(2*2^e)) + ## + ## With 2^e the largest power of 2 that divides q-1 + ## + ## For all sqrt related functions + ## - legendre symbol + ## - SQRT + ## - inverse SQRT + r = Fq.order() + precomp = (r - 1) >> e # (q-1) / 2^e + precomp = (precomp - 1) >> 1 # ((q-1) / 2^e) - 1) / 2 = (q-1-2^e)/2^e / 2 + return precomp + +s, e = precomp_ts(Fp) +qnr = find_any_qnr(Fp) +root_unity = qnr^s +exponent = sqrt_exponent_precomp(Fp, e) + +# print('tonelli s: 0x' + Integer(s).hex()) +print('tonelli e (2-adicity): ' + str(e)) +print('tonelli root: 0x' + Integer(root_unity).hex()) +print('tonelli exponent: 0x' + Integer(exponent).hex()) + +def legendre_symbol_impl(a, e, a_pre_exp): + ## Legendre symbol χ(a) = a^(q-1)/2 + ## -1 if a is non-square + ## 0 if a is 0 + ## 1 if a is square + ## + ## a_pre_exp = a^((q-1-2^e)/(2*2^e)) + ## with + ## s and e, precomputed values + ## such as q == s * 2^e + 1 + ## + ## a_pre_exp is used in square root + ## and or inverse square root computation + ## + ## for fused operations + r = a_pre_exp * a_pre_exp # a^((q-1-2^e)/2^e) = a^((q-1)/2^e - 1) + r *= a # a^((q-1)/2^e) + for i in range(0, e-1): + r *= r # a^((q-1)/2) + + return r + +def legendre_symbol(a): + a_pre_exp = a^exponent + return legendre_symbol_impl(a, e, a_pre_exp) + +for a in range(20): + assert kronecker(a, p) == legendre_symbol(GF(p)(a)) + +def sqrt_tonelli_shanks_impl(a, a_pre_exp, s, e, root_of_unity): + ## Square root for any `a` in a field of prime characteristic p + ## + ## a_pre_exp = a^((q-1-2^e)/(2*2^e)) + ## with + ## s and e, precomputed values + ## such as q == s * 2^e + 1 + z = a_pre_exp + t = z*z*a + r = z * a + b = t + root = root_of_unity + for i in range(e, 1, -1): # e .. 2 + for j in range(1, i-1): # 1 .. i-2 + b *= b + doCopy = b != 1 + r = ccopy(r, r * root, doCopy) + root *= root + t = ccopy(t, t * root, doCopy) + b = t + return r + +def sqrt_tonelli_shanks_opt(a): + a_pre_exp = a^exponent + return sqrt_tonelli_shanks_impl(a, a_pre_exp, s, e, root_unity) + +# for a in range(2, 30): +# if kronecker(a, p) != 1: +# continue +# # print(f'{a}^(p-1)/2 = ' + str(GF(p)(a)^((p-1)/2))) +# print(f'{a} is a quadratic residue mod p') +# b = sqrt_tonelli_shanks_opt(GF(p)(a)) +# # print(f'{b}² = {a} mod p') +# # print('b*b = ' + str(b*b)) +# assert b*b == a + +def sqrt_inv_sqrt_tonelli_shanks_impl(a, a_pre_exp, s, e, root_of_unity): + ## Square root and inverse square root for any `a` in a field of prime characteristic p + ## + ## a_pre_exp = a^((q-1-2^e)/(2*2^e)) + ## with + ## s and e, precomputed values + ## such as q == s * 2^e + 1 + + # Implementation + # 1/√a * a = √a + # Notice that in Tonelli Shanks, the result `r` is bootstrapped by "z*a" + # We bootstrap it instead by just z to get invsqrt for free + + z = a_pre_exp + t = z*z*a + r = z + b = t + root = root_of_unity + for i in range(e, 1, -1): # e .. 2 + for j in range(1, i-1): # 1 .. i-2 + b *= b + doCopy = b != 1 + r = ccopy(r, r * root, doCopy) + root *= root + t = ccopy(t, t * root, doCopy) + b = t + return r*a, r + +def sqrt_invsqrt_tonelli_shanks_opt(a): + a_pre_exp = a^exponent + return sqrt_inv_sqrt_tonelli_shanks_impl(a, a_pre_exp, s, e, root_unity) + +for a in range(2, 30): + if kronecker(a, p) != 1: + continue + # print(f'{a}^(p-1)/2 = ' + str(GF(p)(a)^((p-1)/2))) + print(f'{a} is a quadratic residue mod p') + b, invb = sqrt_invsqrt_tonelli_shanks_opt(GF(p)(a)) + # print(f'{b}² = {a} mod p') + # print('b*b = ' + str(b*b)) + assert b*b == a + assert invb*a == b diff --git a/sage/testgen_bls12_377.sage b/sage/testgen_bls12_377.sage new file mode 100644 index 0000000..875f420 --- /dev/null +++ b/sage/testgen_bls12_377.sage @@ -0,0 +1,109 @@ +# 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. + +# ############################################################ +# +# BN254 test generator +# +# ############################################################ + +# Parameters +x = 3 * 2^46 * (7 * 13 * 499) + 1 +p = (x - 1)^2 * (x^4 - x^2 + 1)//3 + x +r = x^4 - x^2 + 1 + +# Finite fields +Fp = GF(p) +K2. = PolynomialRing(Fp) +Fp2. = Fp.extension(u^2+5) + +# Curves +b = 1 +SNR = Fp2([0, 1]) +G1 = EllipticCurve(Fp, [0, b]) +G2 = EllipticCurve(Fp2, [0, b/SNR]) + +# https://crypto.stackexchange.com/questions/64064/order-of-twisted-curve-in-pairings +# https://math.stackexchange.com/questions/144194/how-to-find-the-order-of-elliptic-curve-over-finite-field-extension +cofactorG1 = G1.order() // r +cofactorG2 = G2.order() // r + +print('') +print('cofactor G1: ' + cofactorG1.hex()) +print('cofactor G2: ' + cofactorG2.hex()) +print('') + +def clearCofactorG1(P): + return cofactorG1 * P + +def clearCofactorG2(P): + return cofactorG2 * P + +# Test generator +set_random_seed(1337) + +print('=========================================') +print('G1 vectors: ') +for i in range(10): + print(f'--- test {i} ------------------------------') + Prand = G1.random_point() + P = clearCofactorG1(Prand) + + (Px, Py, Pz) = P + print('Px: ' + Integer(Px).hex()) + print('Py: ' + Integer(Py).hex()) + # print('Pz: ' + Integer(Pz).hex()) + exponent = randrange(r) # Pick an integer below curve order + print('scalar: ' + Integer(exponent).hex()) + + Q = exponent * P + (Qx, Qy, Qz) = Q + print('Qx: ' + Integer(Qx).hex()) + print('Qy: ' + Integer(Qy).hex()) + # print('Qz: ' + Integer(Qz).hex()) +print('=========================================') +print('G2 vectors: ') + +for i in range(10): + print(f'--- test {i} ------------------------------') + Prand = G2.random_point() + P = clearCofactorG2(Prand) + + (Px, Py, Pz) = P + vPx = vector(Px) + vPy = vector(Py) + # Pz = vector(Pz) + print('Px: ' + Integer(vPx[0]).hex() + ' + β * ' + Integer(vPx[1]).hex()) + print('Py: ' + Integer(vPy[0]).hex() + ' + β * ' + Integer(vPy[1]).hex()) + + exponent = randrange(r) # Pick an integer below curve order + print('scalar: ' + Integer(exponent).hex()) + + Q = exponent * P + (Qx, Qy, Qz) = Q + Qx = vector(Qx) + Qy = vector(Qy) + print('Qx: ' + Integer(Qx[0]).hex() + ' + β * ' + Integer(Qx[1]).hex()) + print('Qy: ' + Integer(Qy[0]).hex() + ' + β * ' + Integer(Qy[1]).hex()) +print('=========================================') + +# CurveOrder sanity check +# +# P = G1.random_point() +# (Px, Py, Pz) = P +# print('Px: ' + Integer(Px).hex()) +# print('Py: ' + Integer(Py).hex()) +# print('Pz: ' + Integer(Pz).hex()) +# +# print('order: ' + Integer(r).hex()) +# +# Q = (r * cofactor) * P +# (Qx, Qy, Qz) = Q +# print('Qx: ' + Integer(Qx).hex()) +# print('Qy: ' + Integer(Qy).hex()) +# print('Qz: ' + Integer(Qz).hex()) diff --git a/tests/t_ec_sage_bls12_377.nim b/tests/t_ec_sage_bls12_377.nim new file mode 100644 index 0000000..48298f6 --- /dev/null +++ b/tests/t_ec_sage_bls12_377.nim @@ -0,0 +1,331 @@ +# 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 + # Standard library + std/[unittest, times], + # Internals + ../constantine/config/[common, curves], + ../constantine/arithmetic, + ../constantine/towers, + ../constantine/io/[io_bigints, io_ec], + ../constantine/elliptic/[ec_weierstrass_projective, ec_scalar_mul, ec_endomorphism_accel], + # Test utilities + ./support/ec_reference_scalar_mult + +echo "\n------------------------------------------------------\n" + +proc test( + id: int, + EC: typedesc[ECP_SWei_Proj], + Px, Py: string, + scalar: string, + Qx, Qy: string + ) = + + test "test " & $id: + var P: EC + let pOK = P.fromHex(Px, Py) + doAssert pOK + + var Q: EC + let qOK = Q.fromHex(Qx, Qy) + + let exponent = BigInt[EC.F.C.getCurveOrderBitwidth()].fromHex(scalar) + + var + impl = P + reference = P + endo = P + endoW = P + + impl.scalarMulGeneric(exponent) + reference.unsafe_ECmul_double_add(exponent) + endo.scalarMulEndo(exponent) + endoW.scalarMulGLV_m2w2(exponent) + + doAssert: bool(Q == reference) + doAssert: bool(Q == impl) + doAssert: bool(Q == endo) + doAssert: bool(Q == endoW) + +suite "Scalar Multiplication (cofactor cleared): BLS12_377 implementation vs SageMath" & " [" & $WordBitwidth & "-bit mode]": + # Generated via sage sage/testgen_bls12_377.sage + test( + id = 0, + EC = ECP_SWei_Proj[Fp[BLS12_377]], + Px = "4e7e6dfa01ed0ceb6e66708b07cb5c6cd30a42eeb13d7b76f8d103a0a4d491450be6f526fc12f15209b792220c041e", + Py = "c782515159b7e7b9371e0e0caa387951317e993b1625d91869d4346621058a0960ef1b8b6eabb33cd5719694908a05", + scalar = "cf815cb4d44d3d691b7c82a40b4b70caa9b0e8fe9586648abf3f1e2e639ca1b", + Qx = "4a1203db4af8f0efc18c7ceb24999eb6e0dbdfc8f44a9edd5ba2f9eced38e81ecae287ab1c184eea8e8753d1178604", + Qy = "f7509cdce5de473e37f36c69d93ff1b2ab04c3ae25a3b5e41f2c138cdcc69ed5eaf4ac95a7a857eef336ebac19bcd3" + ) + + test( + id = 1, + EC = ECP_SWei_Proj[Fp[BLS12_377]], + Px = "13d735b28405253dcc0bc60bcdc13633475ffc187d38a9b97655b0d0fa1d56c4548f11ea0a795391ee85c953aaf9b83", + Py = "1693101123fd13a20f9c0569c52c29507ba1c8b6dd412660bc82e7974022f1a10f9137b4ba59d3f0aab67027cefec19", + scalar = "913aa7b9fa2f940b70b6dcf538cc08da1a369809ab86a8ee49cead0ed6bfef6", + Qx = "10e0e8582ec3456f7569473892c23997d004f2542d914fa75db8f1798ed8ce505836e8b7af5cf1503e14d85fadd65ee", + Qy = "d5151ae60e43100e20aad7eaf8659e690e8034910d7717078031520fcbf6f9a00b22c6a9894aec88c9182f13335639" + ) + + test( + id = 2, + EC = ECP_SWei_Proj[Fp[BLS12_377]], + Px = "6dd2211113cada09d3b96848cbf21ed14f6fc238b581c0afd49aa776980101e4ee279a256ce8f1428d3ed3f70afd85", + Py = "3b406b4433a3f44f8e196012f50c520e876412fbcae2651916f133c1fd3899c79f676e1abba01d84bab7ad100c9295", + scalar = "4cf47669aeb0f30b6c6c5aa02808a87dc787fba22da32875e614a54a50b6a0c", + Qx = "3caf55e1f82d746df2bf47defbee127a3e6f7e79a9575929704b25489ffb801dbce07999acbddfd79352b0633a2708", + Qy = "3fc275c136b07456f0b6ee814508876037add5e95357fae6b4195744ba5ccccaf93581e34feb2babe652a9a704731b" + ) + + test( + id = 3, + EC = ECP_SWei_Proj[Fp[BLS12_377]], + Px = "18648abbe3261f93cbf679777eac66e419035041a967d1e6a0f0afdb92810d7122e0984f1d6efc8fe518500464ee803", + Py = "7b6f518d4d06309aad4d60d29118310b6c8b17c7bf5db2251f4701b13b89a8c0f04bb5d0386785e55ffbbd7ecc445e", + scalar = "69498486a06c18f836a8e9ed507bbb563d6d03545e03e08f628e8fbd2e5d098", + Qx = "6a871d872673879fd23ec8c150f8d63e8130dc60343fc0c2ec9ff1b02e769e20eeec0288102ccec6ff2f6ac6973b4", + Qy = "cd95a31afa4f0bbcc063a1585fec4a6c51812b2baaee42a04525fd55b3a9db3bdb7cbc77f7cd32f42d2e0eb26ddfa2" + ) + + test( + id = 4, + EC = ECP_SWei_Proj[Fp[BLS12_377]], + Px = "745e9944549522486f3446676cc62fb666682e10c661a13b8110b42cb9a37676b6f33a46f9495f4fafb342d5809db5", + Py = "bc595854bc42ccec60dd9ec573608d736aa59996cef1c2e8f6c5d424f525a6f3e3d4beeedfac6b959dbd71ced95b13", + scalar = "6e08d8714102a5aa3e9f46e33c70a759c27253c7b0196c3e46c7cb42671197e", + Qx = "18a967a80785de8ec6ac9d98cffa06a8c633b5fa0f36431a32f7bd955946edc3d55f79bfdf5335db405560a6cbe2415", + Qy = "4552ff2eb6ade0c6b33c3603460d9d62099201d842c9883b33b7ed1147cb17268338a77a9417776ddbd774e91a8d2" + ) + + test( + id = 5, + EC = ECP_SWei_Proj[Fp[BLS12_377]], + Px = "f7b56cf212a8906ed77a758164c0bd05ce1fbd3ee3c4357e7a09b3aedc748a29ace254f1f6df35b8cb74361060337f", + Py = "2640ef641d20fea19b28947833e53faceeafa57a8761b807049f3d707d70c01f1c69a57edd993d301a64517bf47f77", + scalar = "a5c46d7a52938fed7f3093d5867f65361dc8b48c83bd7db490c26736196e20e", + Qx = "434f024c4afd7b9a44375011d663af0ae0fe79442e9caf36518e053bb13d49998ec1d2da4bc1c4a812812119b3221f", + Qy = "32c959e2a678cf93f77e621fdf887454a5f9cb7a67f29065669f48234c25e321ceb5758dfba987431a0c2caec94616" + ) + + test( + id = 6, + EC = ECP_SWei_Proj[Fp[BLS12_377]], + Px = "43b387c12e4cc91fb1a5593b7671356dceb0fe6e6ac666d5bac94f2a44c8db54976b649a678aae038b21144de04e23", + Py = "1a6366a50f1f9ba64eef73d82bec86177bf184be048a9d66326ccb0122203569ddcb8cf74445cadaff7f47a66d1b1a2", + scalar = "bddd07231bc7fe89ee4a859a00ea1f9d236be9e7fd561303d566904c1b0a07c", + Qx = "8ddb7a959483b51a1471de988146b7d5b166f660734b4d55166c5a23d781e923261927b1012dce73b822bb6e56bfd2", + Qy = "dc7178bf5597ef31209a5b0409fc42c64b81260a0046a562ca08c7a9dc67b444c25e94fb97b9a6bb4c137cadd81021" + ) + + test( + id = 7, + EC = ECP_SWei_Proj[Fp[BLS12_377]], + Px = "6984c9385a67081f97b3d33444077466cca1d0442a4da8c083a0957578e0b21011435b126a5ab143da9da1cf5b216f", + Py = "18a87c7f5f6c5a8101773e63956b9addd4becf5177acc560d548e5331638121934842fdc9f654b3f456a7df5a2e471a", + scalar = "b72c41a6ffaff0aacb5d62e3dcb16acfec66b6a9639e19d3128fd43c18e7dbe", + Qx = "19279201c3c7a9d50b546aa99d3e1a6625fe2a7bc64a09625b683534638b9e87b9102d4dba6684956b6be7668a658c6", + Qy = "195f15ad1edc05f8b289c7eee7bd8f78116a2d5ba8b83643d9e7cb2cdc6550bcf8c2145008e900ca9cba4d5040e9f4" + ) + + test( + id = 8, + EC = ECP_SWei_Proj[Fp[BLS12_377]], + Px = "efc34595823e7333616f7768bc82f407268df6f029bf4c94d15f1785dc7ccc08f22e7301dfc48dadc0ea383c8bb3e", + Py = "2459bd9f71977ef122d2102e8bfd07a5737066075058cfa8bcaa9f9690ed065919c844363ceaea6f9bb650906a535f", + scalar = "8f90f6ab0ffa6e4acc601b44c062745f2935b3dc153d0da07977470080d5c18", + Qx = "35d08b33e02579581905941975c8b1cc5be1c9670a7f7ef390daa363b0abd3571a802d8c27f156fba40573094f6c7a", + Qy = "111ba3bdfa4260dc2b636479edb1fcf2fc9478aa722da0118908e1db1551cf7e131c8521b2f3708ef670684dbe8d181" + ) + + test( + id = 9, + EC = ECP_SWei_Proj[Fp[BLS12_377]], + Px = "3eec93c2a9c5fd03f0de5ede2fdac9e361090fbaea38e4a0f1828745f1d14a057d9fd7c46b9168bd95a45a182a3a62", + Py = "e912dc7e95f90d91e3274ec5639edacb88be1b092c47c13d31a29ecd579885cc09f197f8207d23b2260ab10c94d5f5", + scalar = "203300e949aff816d084a388f07c74b9152bb11b523543afd65c805a389980", + Qx = "8c23ae9c51e7c92c6a59f0ed07a59f148b4d79394fc026931c264612041eedd3782e4f249bbfad1799212788c1a00d", + Qy = "191628b702ebef397bb7c52102ee4522f864ff594b24dc385ece081e8066bcde20aa5c7dfd00fb1f3cea221b3ad4ea2" + ) + + +proc test( + id: int, + EC: typedesc[ECP_SWei_Proj], + Px0, Px1, Py0, Py1: string, + scalar: string, + Qx0, Qx1, Qy0, Qy1: string + ) = + + test "test " & $id: + var P: EC + let pOK = P.fromHex(Px0, Px1, Py0, Py1) + doAssert pOK + + var Q: EC + let qOK = Q.fromHex(Qx0, Qx1, Qy0, Qy1) + + let exponent = BigInt[EC.F.C.getCurveOrderBitwidth()].fromHex(scalar) + + var + impl = P + reference = P + endo = P + + impl.scalarMulGeneric(exponent) + reference.unsafe_ECmul_double_add(exponent) + endo.scalarMulEndo(exponent) + + doAssert: bool(Q == reference) + doAssert: bool(Q == impl) + doAssert: bool(Q == endo) + +suite "Scalar Multiplication G2: BLS12-381 implementation vs SageMath" & " [" & $WordBitwidth & "-bit mode]": + # Generated via sage sage/testgen_bls12_377.sage + test( + id = 0, + EC = ECP_SWei_Proj[Fp2[BLS12_377]], + Px0 = "267401f3ef554fe74ae131d56a10edf14ae40192654901b4618d2bf7af22e77c2a9b79e407348dbd4aad13ca73b33a", + Px1 = "12dcca838f46a3e0418e5dd8b978362757a16bfd78f0b77f4a1916ace353938389ae3ea228d0eb5020a0aaa58884aec", + Py0 = "11799118d2e054aabd9f74c0843fecbdc1c0d56f61c61c5854c2507ae2416e48a6b2cd3bc8bf7495a4d3d8270eafe2b", + Py1 = "823b9f8fb9f8297734a14359fa2c2a0de275e7e638197eaaaa7cff28f9cb3101bdabb570016672455f1ecae625e294", + scalar = "9d432eb58ec68bbc09d10961451d99c7796fb2f795eca603d6feaf3e2a1634b", + Qx0 = "12cfcb50345d43271d2a20e8208789c8ca82f2b8732fae4e7cccd87eb0883741d5e77166971c38c54170bf7635ca2f3", + Qx1 = "17467c786368f2f6eabd78f1c24aa668eae9cd00f6045bfd86b1b3c40966f023a71927026ae5a1281b432ac980d2b6b", + Qy0 = "6f5f1068b6e6a8fafd96894d8b0a8548acee24319d8e2d8b6e6982b1ced8970d9fe33155e74b33d6c2a7835196ca9", + Qy1 = "dc3c7c91c712f9a2631d2b5e49497d8cdf2ea4b30859b43edd716e84cd6a8b61e63cf708d263a7845913cdfb9c7f3b" + ) + + test( + id = 1, + EC = ECP_SWei_Proj[Fp2[BLS12_377]], + Px0 = "3a3055d6a46901c1b2227a0e334ffa9f654e62a6d3608f3a672e5816f9e9b04c0e668c3e9f8c807b269422afdc7de9", + Px1 = "25803bd55f37b254865d5fc7ac9843fb306c2eb09d34ee0c4ecb705b5e10f6911f07fd707a2e28681a421f45b1a4d", + Py0 = "15a0594a3c9dddc535472c4827aa443774a06f77bec2d20837c6574aa5fac35a279bac756531fa75f979a7a97f297d6", + Py1 = "15e8c3e013cbc64110f075295f39a4f85c9591a52b8e4903047b1f4b44bb5216b6339788c82fd90e82b1027756e7987", + scalar = "a0067f5b4294fbacd24730fed25c936a08b5f1a77824149ad6c2ce476518d17", + Qx0 = "6280dbf019f8d94acac4a39213e03f221be732849b45437edef7617460e83f972883c20791a86670a8a4c6c0125629", + Qx1 = "ab3a185addbd6c7db907170d75bd2f1997666e93d8cedbef9e10a103110d4ea09d9a889f505d7026e5c498b0355c1", + Qy0 = "1a6dd15b4c2bd6bd97858e5a75ac4365f8becaa885c4e3267d0ae256655cd057061d61b2b9acbd10bdf678a9c1e7f8c", + Qy1 = "16204b5825eec903cb7798cb62ccadb9004032d72bb815958fcd2f613c77e147656449fdaa210994338978676ad6b0e" + ) + + test( + id = 2, + EC = ECP_SWei_Proj[Fp2[BLS12_377]], + Px0 = "18ebf549cd24a64badff4ec177205be121757b68589020434aba816756d2e6fa95fdb389c8639d4bf77575122e1c04e", + Px1 = "11cd0f0976658a6f2ec113034428ef1605befdaa5642944f5c4e571b24fc166c368c30473e25ab148209be4c0b4e37", + Py0 = "1190d818317495201732feb2cfc8f507adac6273debff46bb6aea4a3f3e3fe7d28d893c90b21a6f28d2fbc72d9fc528", + Py1 = "df5fcb2daa302a5c64aeef96835e0a6b39f5d7bf0e70cc10401f966745a6b3fa682b7e5b45d9295e744e1dd7855fd4", + scalar = "7a49802ba58c87c30b631b2f90a3b876c7143e09b542c9c14706bddf9bd4117", + Qx0 = "6cdff80576d8695a646f915caba5bc6748eee1707bb0d4ebcabaf8d236b780aef6a953a07f48ef4696f02db4906c71", + Qx1 = "6b64ca5f0d46702e7e9e7beb1993b698c9b3e9991f545241d5fd44a27dc692d5f5a4c2fe6871af0653128f960307d4", + Qy0 = "cfad02b664495d42d1c6b598306229c67bcf76cc923abb13ae038c90e959a7611161a98d607729577d55ec18bf1e74", + Qy1 = "e433d9dd7d47d94e9f61a5bc27a688c63efa05408d6fd40a5c4e2711a37d011dc80f5dbaaafd939a07235c770feead" + ) + + test( + id = 3, + EC = ECP_SWei_Proj[Fp2[BLS12_377]], + Px0 = "52b54c30c1dcadbcb698d0cb7fd65de1eb8f7590c7afe46e019bdc9e0ef8bc4c060339220d3615e4b1f2b12ffa6d83", + Px1 = "dd53b483c2ab1aaa7ed22ef619b5e979237ae95476436f2c51c8b70da39a4e54a989f10f6d12ee098154911aa052f6", + Py0 = "2a8c96662a7c76cb7d8ca6571a5b99abfd2d7343dd668425e5fa4a8c880b313b70f09a15a825fac63ae7065c0c51d", + Py1 = "7bd93bb9d5bfdd15636a34a13385e9abbc1088ecc02c86cbc733122ccbbb657ec8d75e47102ce46d35a9612c01fc3d", + scalar = "e0c564e69ad68343fd4ec3d4dafdf6a92f44c13fc70a9aad95d10b2c96ee747", + Qx0 = "126f6039b7031b73a7f926bd443ace416f62e56f03864d660bbe1f1a2a994672d408f9936293f367d3fb5d8e275ad40", + Qx1 = "1654a2c26479ace1eb68e279eceac6aa10680549b09f42d288767f6296c98ba4299fa6df37a946b88823a5deebc231c", + Qy0 = "23026839059191f71e2abcf582234aa2ce4b4225c3d503e8fc6c119df1168192e894f33ed48bc571e5527365dd92b5", + Qy1 = "11f8fcce53f5e3211290e6c72736eb1c9d2e159dd243f3e493c430f0411864e109af09a3b9379c4b332815e012caa80" + ) + + test( + id = 4, + EC = ECP_SWei_Proj[Fp2[BLS12_377]], + Px0 = "fb6dd822e327a60c9e55d760c4f5d26783617a06f868835c2f46e902108eaca5d60fd64c0a0c8a6dc12f9352dfef33", + Px1 = "85e97eef3b4e42a93b8421644e9334f4543a25a36ccd41d448c385146baf3b1efbaeac49c202b04cdbf8b50f3fd962", + Py0 = "8db78b315a524f17ac3d69333604e6bc8aa0b2f138f9adf7edb19f49c847eda6b64df9fe2576b7687e7b55cb5f0bd0", + Py1 = "d4a0c7e5e7aaacab0e0e60c6c49624971a161de4f0daa7968f80998fb7b4761b1196964b26fefa9337fd133784e3d9", + scalar = "1abce9e9c079c686864304d479d68087db33f9edb3f7b2e4655fe0c1da4993f", + Qx0 = "1238814ea497bc1b65ba1c0fbfc394427b10b2d8783bf6a04e01505bc165b577cb2366c19c24c555235ff9b16e7233a", + Qx1 = "b156205cad5d2d97d24c45fe218cd412f47248f26d579fb368af5ba2d80b8c95e2a68f4c84540a8b78b2f0a33a48a4", + Qy0 = "12154ce4a569b39ccfe464a7559b8baf03d3fb6462e67dc0c4ae79a3c33b7756e0c558eff64f9558ebd6500086684f8", + Qy1 = "74571a18fc091f4d77cc7d363bf7a34ab4d0dca86c160b261b383e416f68fa553d7b8b8f6317c8e340ce454daffb5f" + ) + + test( + id = 5, + EC = ECP_SWei_Proj[Fp2[BLS12_377]], + Px0 = "b56e23f2558c3c124f024952e1858067223541f51a8885d161133beb7bf8d64f22163769e604afbf7fbfb02e1fcb43", + Px1 = "e10c4366d667b40488a9ae32daf3e2a5cc8ddf25ed407afe1a38b855f3ac4f7ea20455924e71369eed07114613b633", + Py0 = "f42d3ab4716d10384bcdfec38cba997c2bafb8d8de32a47225a3e2d2835be1ad02a63323d3dd4145db21e3cbf5cc7a", + Py1 = "15f48a901f3dfd3b812a455f1297e8311c4608869ea02d3d16e9aafdf610d7e72f34b02830de8cc0d0f4e909af5827d", + scalar = "401e70df8ccaa504da72b1d649c067f3b752156dcea5b48dcb601710ab0baa3", + Qx0 = "949099ea1ece1be6fa3f3537e8ba39587b40003fb409a4beb831bb1d9d999b7c621b9a1ced3223f710e0bd05f4a018", + Qx1 = "cdfc0d47788bca3672126e0facc05c19a4fb24a43eee32c8e08a6e5152f6af1d7c2efa48907046f90a721ed28fcf7", + Qy0 = "11d608a01cfe1a40806b9951f85a7bb1b6df6e4c4e2c7c50c17e8fad6ed397a430aff9b4742408367d6536fe503692a", + Qy1 = "19c25d8a782b9d8d9f067d345fd03afe50439c82f99a06abca790f2fae944677d8095d2e276fcb7a75cfb2cbccf89d7" + ) + + test( + id = 6, + EC = ECP_SWei_Proj[Fp2[BLS12_377]], + Px0 = "137001541ab8a479362ee39f818fc38788231e5288ceea5ebe0c08d0bbca3be9519aa415bde98428df26d361b7311ea", + Px1 = "411b1a4f0abb9fd255a7ae26ac39c1f2c88a48f82c7623b6f225aec9755206e27084b23cbc98f31399405a6599dc54", + Py0 = "833ef097986116cab669c4fddff9b831535d100644f732fb0da0a2dce17d69beaeed67230dd66392e840679afbae1e", + Py1 = "f63e809bd55617c31570d686dbc9b94b3cb96c154f9983181cb913a0671c0c16143332087e44a0283ebf01eea6d73b", + scalar = "10697220c755c800861d377c55b22ae48c25dc144e1fa7a1e5bbdf82993aa33c", + Qx0 = "10fc7772bb2bc946e3ff659c01a0452ebf5dd1472bd1aa71e198597fd05361bf0d173817f91fcb8a1b96b46c35d2675", + Qx1 = "d7b00ddf32f9d8e549c05dd605931ef737d8a4bc62795f73044482957b8093b1cdad0f55c7d4edf6f59594a13f9b67", + Qy0 = "148756a592dec0bf2fda41ac219c4a891ef427665ba1cf47b58bed4996cf8937fb07929c041f10e8a55b2238944fd30", + Qy1 = "e4a240f5c32dab76575ace4d035cbd1e2b87d7585fee7bfde9c88e918a7ad2cd8eb4982acfacfac58c33c8a54ddac7" + ) + + test( + id = 7, + EC = ECP_SWei_Proj[Fp2[BLS12_377]], + Px0 = "1580ffccc21b057dfe4831f950cbb8f1436f999df657404ecec20225a929a5d56920e8662abc426de23402643087308", + Px1 = "8bf8ff20713a9054aa1cce9635eb3d6ac8371fc052b747a8414595708ff9462d64a0a11ff2c1c5121f4ecc5f22df5e", + Py0 = "e7a5f7df0cec03de25da415fdda485ecc1443b95352e47b23e5506b820c9bc9ea9c2d9e99dd48e1f74e1befe58ce80", + Py1 = "12b5638a0f7e508016fddd2dae2d84736efa59c53f2a5d979b9a29fc7c9c406407bb5f5788bcea3529ac1a763c72e8b", + scalar = "bd936c7066316d3f86b077419a0c0fb9867d4612208241fc004b548f1f54fad", + Qx0 = "69e3411725062858ca7e5fc4d648037baf10e34ba6830ff0090e7188932112b9458c4f4b82f2f0f06c6501fcdaa86c", + Qx1 = "127b75c55c90a872c1d749d05609d2499065ac3c9eb96bd6a9816bd7a2315795ddf40944baef1907676395df306860e", + Qy0 = "13fd0e314159909d3091e382017b35ec21ee2004e9082cd24338817046ed2202ffed52e7714cf436b953f95f49f9856", + Qy1 = "5a8ccdf4a1600fafcc86c646d83199c41cbfebf7a1fa2b1cb9c408352be46a33d0f411b175348f42382d170e17ec2b" + ) + + test( + id = 8, + EC = ECP_SWei_Proj[Fp2[BLS12_377]], + Px0 = "10898a074b6931cc4ada152b64dd1c6b2bb47912e6502b9e88f638e489c144f8aa9b9f915bb428e082ec84676607f40", + Px1 = "18484823648fe1699468e2d265cd2f2e381a0e67f35f8d192259e2a14573692b4e1fbdeed639e9b6eb0731be820b166", + Py0 = "1840bc6fcb224efe00e32f827fa4f9694cd4186493089c66a936e912b50346b75542b6edbe51ba95c88d3b0fcac34ed", + Py1 = "18cb4201510933776a28ff1c44d356ceab065880f5242d8cc7cdf86874568df679b20f34b6a216d0047d2e1c1a16b85", + scalar = "bdb46c0eece9c8bac8a382def90b522b1d5197f09cf9f7cd98b710845ddb7b4", + Qx0 = "102779766b0068b176e118348e4163e89e3171e6eaad0e47f00cad626f8db025ec3ea990363813882c3dcfcbb642240", + Qx1 = "131eb563d2b0145eee52ee7586acd0254f103f02a20de3bf0202d1cffec2c3628501ec20f9f2ae5f461b59604afc04c", + Qy0 = "1463527db149d2c76f30c082786edb3fe1a9c7f4e04f9496e7adb40aab89340d9792f7d75cf3f5f81b0a28f09625175", + Qy1 = "f3190261db082c207ccd88a3ba762d2c0ba330548444afca03c8535ff11c7c90659a867dda3712a83f02ad184eb24a" + ) + + test( + id = 9, + EC = ECP_SWei_Proj[Fp2[BLS12_377]], + Px0 = "13c3a392307124afb5f219ba0f8062fa9b75654d3fff12bc924592d284af550f039b6ac58880d2c6fea146b3982f03c", + Px1 = "188169bc937fcc20cc9c289adef30580188f64ecb126faadb5b888f31b813727ff7046d1a19b81abeea6609b8b208c6", + Py0 = "2f9bde8fdd43c5f4de30f335a480dca3bf0858464d8368984406f10ddc1ecabb15fcfd11cebd4fef426e7ca9411221", + Py1 = "25505653e199c354f2da8a13ed9de47f9b51a06ad2fa8826c4b8a9c61c6e75268807d7053e06bfc2899d1a3d6deeb4", + scalar = "91c6249ee16ef94d0b905575982419a7cf31d125775e7ede5c0ab4f86defd33", + Qx0 = "1967547943b197311a7d6723dcc37c3e649f173cec1c88ffca27df86518b3392ae0ee13dbca375c928b94d129226852", + Qx1 = "18b793916195f2d7d2a47c3f8243e5bdc239e78a3eb26971dac01b5f4ce14081a06529ab66cda8e8d5537808223fd00", + Qy0 = "a784902a14ad39adcfdc52bc30d8b8711b93dd869af2375a1f408ba8610b5c558bbebfe9f8f9875954780f4b4262c0", + Qy1 = "fb1c766957c89e4d67747549f650070983e6c19d0208b7e65478e4dd4f72fc54fca450d96329182e3f00d16ae0f3a" + ) diff --git a/tests/t_ec_wstrass_prj_g1_add_double.nim b/tests/t_ec_wstrass_prj_g1_add_double.nim index c8d7826..1bd0376 100644 --- a/tests/t_ec_wstrass_prj_g1_add_double.nim +++ b/tests/t_ec_wstrass_prj_g1_add_double.nim @@ -32,3 +32,9 @@ run_EC_addition_tests( Iters = Iters, moduleName = "test_ec_weierstrass_projective_g1_add_double_" & $BLS12_381 ) + +run_EC_addition_tests( + ec = ECP_SWei_Proj[Fp[BLS12_377]], + Iters = Iters, + moduleName = "test_ec_weierstrass_projective_g1_add_double_" & $BLS12_377 + ) diff --git a/tests/t_ec_wstrass_prj_g1_mixed_add.nim b/tests/t_ec_wstrass_prj_g1_mixed_add.nim index df6da2b..feeb7ee 100644 --- a/tests/t_ec_wstrass_prj_g1_mixed_add.nim +++ b/tests/t_ec_wstrass_prj_g1_mixed_add.nim @@ -28,3 +28,9 @@ run_EC_mixed_add_impl( Iters = Iters, moduleName = "test_ec_weierstrass_projective_mixed_add_" & $BLS12_381 ) + +run_EC_mixed_add_impl( + ec = ECP_SWei_Proj[Fp[BLS12_377]], + Iters = Iters, + moduleName = "test_ec_weierstrass_projective_mixed_add_" & $BLS12_377 + ) diff --git a/tests/t_ec_wstrass_prj_g1_mul_distri.nim b/tests/t_ec_wstrass_prj_g1_mul_distri.nim index 6e669e7..1fa17d8 100644 --- a/tests/t_ec_wstrass_prj_g1_mul_distri.nim +++ b/tests/t_ec_wstrass_prj_g1_mul_distri.nim @@ -34,3 +34,9 @@ run_EC_mul_distributive_tests( ItersMul = ItersMul, moduleName = "test_ec_weierstrass_projective_g1_mul_distributive_" & $BLS12_381 ) + +run_EC_mul_distributive_tests( + ec = ECP_SWei_Proj[Fp[BLS12_377]], + ItersMul = ItersMul, + moduleName = "test_ec_weierstrass_projective_g1_mul_distributive_" & $BLS12_377 + ) diff --git a/tests/t_ec_wstrass_prj_g1_mul_sanity.nim b/tests/t_ec_wstrass_prj_g1_mul_sanity.nim index 007d711..965c617 100644 --- a/tests/t_ec_wstrass_prj_g1_mul_sanity.nim +++ b/tests/t_ec_wstrass_prj_g1_mul_sanity.nim @@ -78,3 +78,9 @@ run_EC_mul_sanity_tests( ItersMul = ItersMul, moduleName = "test_ec_weierstrass_projective_g1_mul_sanity_" & $BLS12_381 ) + +run_EC_mul_sanity_tests( + ec = ECP_SWei_Proj[Fp[BLS12_377]], + ItersMul = ItersMul, + moduleName = "test_ec_weierstrass_projective_g1_mul_sanity_" & $BLS12_377 + ) diff --git a/tests/t_ec_wstrass_prj_g1_mul_vs_ref.nim b/tests/t_ec_wstrass_prj_g1_mul_vs_ref.nim index 2d16ba2..0426ecd 100644 --- a/tests/t_ec_wstrass_prj_g1_mul_vs_ref.nim +++ b/tests/t_ec_wstrass_prj_g1_mul_vs_ref.nim @@ -34,3 +34,9 @@ run_EC_mul_vs_ref_impl( ItersMul = ItersMul, moduleName = "test_ec_weierstrass_projective_g1_mul_vs_ref_" & $BLS12_381 ) + +run_EC_mul_vs_ref_impl( + ec = ECP_SWei_Proj[Fp[BLS12_377]], + ItersMul = ItersMul, + moduleName = "test_ec_weierstrass_projective_g1_mul_vs_ref_" & $BLS12_377 + ) diff --git a/tests/t_ec_wstrass_prj_g2_add_double_bls12_377.nim b/tests/t_ec_wstrass_prj_g2_add_double_bls12_377.nim new file mode 100644 index 0000000..f0cc43e --- /dev/null +++ b/tests/t_ec_wstrass_prj_g2_add_double_bls12_377.nim @@ -0,0 +1,29 @@ +# 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 + # Standard library + std/[unittest, times], + # Internals + ../constantine/config/[common, curves], + ../constantine/arithmetic, + ../constantine/towers, + ../constantine/io/io_bigints, + ../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective], + # Test utilities + ../helpers/prng_unsafe, + ./t_ec_template + +const + Iters = 8 + +run_EC_addition_tests( + ec = ECP_SWei_Proj[Fp2[BLS12_377]], + Iters = Iters, + moduleName = "test_ec_weierstrass_projective_g2_add_double_" & $BLS12_377 + ) diff --git a/tests/t_ec_wstrass_prj_g2_mixed_add_bls12_377.nim b/tests/t_ec_wstrass_prj_g2_mixed_add_bls12_377.nim new file mode 100644 index 0000000..1b346a9 --- /dev/null +++ b/tests/t_ec_wstrass_prj_g2_mixed_add_bls12_377.nim @@ -0,0 +1,24 @@ +# 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_weierstrass_projective, + ../constantine/towers, + # Test utilities + ./t_ec_template + +const + Iters = 12 + +run_EC_mixed_add_impl( + ec = ECP_SWei_Proj[Fp2[BLS12_377]], + Iters = Iters, + moduleName = "test_ec_weierstrass_projective_mixed_add_" & $BLS12_377 + ) diff --git a/tests/t_ec_wstrass_prj_g2_mul_distri_bls12_377.nim b/tests/t_ec_wstrass_prj_g2_mul_distri_bls12_377.nim new file mode 100644 index 0000000..356238d --- /dev/null +++ b/tests/t_ec_wstrass_prj_g2_mul_distri_bls12_377.nim @@ -0,0 +1,31 @@ +# 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 + # Standard library + std/[unittest, times], + # Internals + ../constantine/config/[common, curves], + ../constantine/arithmetic, + ../constantine/towers, + ../constantine/io/io_bigints, + ../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective, ec_scalar_mul], + # Test utilities + ../helpers/prng_unsafe, + ./support/ec_reference_scalar_mult, + ./t_ec_template + +const + Iters = 12 + ItersMul = Iters div 4 + +run_EC_mul_distributive_tests( + ec = ECP_SWei_Proj[Fp2[BLS12_377]], + ItersMul = ItersMul, + moduleName = "test_ec_weierstrass_projective_g2_mul_distributive_" & $BLS12_377 + ) diff --git a/tests/t_ec_wstrass_prj_g2_mul_sanity_bls12_377.nim b/tests/t_ec_wstrass_prj_g2_mul_sanity_bls12_377.nim new file mode 100644 index 0000000..92d4a80 --- /dev/null +++ b/tests/t_ec_wstrass_prj_g2_mul_sanity_bls12_377.nim @@ -0,0 +1,62 @@ +# 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 + # Standard library + std/[unittest, times], + # Internals + ../constantine/config/[common, curves], + ../constantine/arithmetic, + ../constantine/towers, + ../constantine/io/io_bigints, + ../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective, ec_scalar_mul], + # Test utilities + ../helpers/prng_unsafe, + ./support/ec_reference_scalar_mult, + ./t_ec_template + +const + Iters = 12 + ItersMul = Iters div 4 + +run_EC_mul_sanity_tests( + ec = ECP_SWei_Proj[Fp2[BLS12_377]], + ItersMul = ItersMul, + moduleName = "test_ec_weierstrass_projective_g2_mul_sanity_" & $BLS12_377 + ) + +# TODO: the order on E'(Fp2) for BLS curves is ??? with r the order on E(Fp) +# +# test "EC mul [Order]P == Inf": +# var rng: RngState +# let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32 +# rng.seed(seed) +# echo "test_ec_weierstrass_projective_g1_mul_sanity_extra_curve_order_mul_sanity xoshiro512** seed: ", seed +# +# proc test(EC: typedesc, bits: static int, randZ: static bool) = +# for _ in 0 ..< ItersMul: +# when randZ: +# let a = rng.random_unsafe_with_randZ(EC) +# else: +# let a = rng.random_unsafe(EC) +# +# let exponent = F.C.getCurveOrder() +# +# var +# impl = a +# reference = a +# +# impl.scalarMulGeneric(exponent) +# reference.unsafe_ECmul_double_add(exponent) +# +# check: +# bool(impl.isInf()) +# bool(reference.isInf()) +# +# test(ECP_SWei_Proj[Fp2[BLS12_377]], bits = BLS12_377.getCurveOrderBitwidth(), randZ = false) +# test(ECP_SWei_Proj[Fp2[BLS12_377]], bits = BLS12_377.getCurveOrderBitwidth(), randZ = true) diff --git a/tests/t_ec_wstrass_prj_g2_mul_vs_ref_bls12_377.nim b/tests/t_ec_wstrass_prj_g2_mul_vs_ref_bls12_377.nim new file mode 100644 index 0000000..3125fca --- /dev/null +++ b/tests/t_ec_wstrass_prj_g2_mul_vs_ref_bls12_377.nim @@ -0,0 +1,31 @@ +# 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 + # Standard library + std/[unittest, times], + # Internals + ../constantine/config/[common, curves], + ../constantine/arithmetic, + ../constantine/towers, + ../constantine/io/io_bigints, + ../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective, ec_scalar_mul], + # Test utilities + ../helpers/prng_unsafe, + ./support/ec_reference_scalar_mult, + ./t_ec_template + +const + Iters = 12 + ItersMul = Iters div 4 + +run_EC_mul_vs_ref_impl( + ec = ECP_SWei_Proj[Fp2[BLS12_377]], + ItersMul = ItersMul, + moduleName = "test_ec_weierstrass_projective_g2_mul_vs_ref_" & $BLS12_377 + ) diff --git a/tests/t_finite_fields_sqrt.nim b/tests/t_finite_fields_sqrt.nim index 2f42f82..9387f3b 100644 --- a/tests/t_finite_fields_sqrt.nim +++ b/tests/t_finite_fields_sqrt.nim @@ -27,8 +27,8 @@ echo "test_finite_fields_sqrt xoshiro512** seed: ", seed static: doAssert defined(testingCurves), "This modules requires the -d:testingCurves compile option" -proc exhaustiveCheck_p3mod4(C: static Curve, modulus: static int) = - test "Exhaustive square root check for p ≡ 3 (mod 4) on " & $Curve(C): +proc exhaustiveCheck(C: static Curve, modulus: static int) = + test "Exhaustive square root check for " & $Curve(C): var squares_to_roots: Table[uint16, set[uint16]] # Create all squares @@ -102,9 +102,9 @@ template testImpl(a: untyped): untyped {.dirty.} = bool(r == s) bool(r == a or r == na) -proc randomSqrtCheck_p3mod4(C: static Curve) = - test "Random square root check for p ≡ 3 (mod 4) on " & $Curve(C): - for _ in 0 ..< Iters: +proc randomSqrtCheck(C: static Curve) = + test "Random square root check for " & $Curve(C): + for _ in 0 ..< 1: # Iters: let a = rng.random_unsafe(Fp[C]) testImpl(a) @@ -118,20 +118,21 @@ proc randomSqrtCheck_p3mod4(C: static Curve) = proc main() = suite "Modular square root" & " [" & $WordBitwidth & "-bit mode]": - exhaustiveCheck_p3mod4 Fake103, 103 - exhaustiveCheck_p3mod4 Fake10007, 10007 - exhaustiveCheck_p3mod4 Fake65519, 65519 - randomSqrtCheck_p3mod4 Mersenne61 - randomSqrtCheck_p3mod4 Mersenne127 - randomSqrtCheck_p3mod4 BN254_Nogami - randomSqrtCheck_p3mod4 BN254_Snarks - randomSqrtCheck_p3mod4 P256 - randomSqrtCheck_p3mod4 Secp256k1 - randomSqrtCheck_p3mod4 BLS12_381 - randomSqrtCheck_p3mod4 BN446 - randomSqrtCheck_p3mod4 FKM12_447 - randomSqrtCheck_p3mod4 BLS12_461 - randomSqrtCheck_p3mod4 BN462 + exhaustiveCheck Fake103, 103 + exhaustiveCheck Fake10007, 10007 + exhaustiveCheck Fake65519, 65519 + randomSqrtCheck Mersenne61 + randomSqrtCheck Mersenne127 + randomSqrtCheck BN254_Nogami + randomSqrtCheck BN254_Snarks + randomSqrtCheck P256 + randomSqrtCheck Secp256k1 + randomSqrtCheck BLS12_377 # p ≢ 3 (mod 4) + randomSqrtCheck BLS12_381 + randomSqrtCheck BN446 + randomSqrtCheck FKM12_447 + randomSqrtCheck BLS12_461 + randomSqrtCheck BN462 suite "Modular square root - 32-bit bugs highlighted by property-based testing " & " [" & $WordBitwidth & "-bit mode]": test "FKM12_447 - #30": diff --git a/tests/t_fp12_frobenius.nim b/tests/t_fp12_frobenius.nim index 24931da..c858bf1 100644 --- a/tests/t_fp12_frobenius.nim +++ b/tests/t_fp12_frobenius.nim @@ -16,7 +16,7 @@ import const TestCurves = [ BN254_Nogami, BN254_Snarks, - # BLS12_377, + BLS12_377, BLS12_381, # BN446 # FKM12_447 diff --git a/tests/t_fp2_sqrt.nim b/tests/t_fp2_sqrt.nim index b9f30aa..9536dce 100644 --- a/tests/t_fp2_sqrt.nim +++ b/tests/t_fp2_sqrt.nim @@ -21,7 +21,9 @@ import const Iters = 8 TestCurves = [ + BN254_Nogami, BN254_Snarks, + BLS12_377, BLS12_381 ] @@ -45,7 +47,7 @@ func random_elem(rng: var RngState, F: typedesc, gen: RandomGen): F {.inline, no else: result = rng.random_long01Seq(F) -proc randomSqrtCheck_p3mod4(C: static Curve, gen: RandomGen) = +proc randomSqrtCheck(C: static Curve, gen: RandomGen) = for _ in 0 ..< Iters: let a = rng.random_elem(Fp2[C], gen) var na{.noInit.}: Fp2[C] @@ -70,10 +72,10 @@ proc randomSqrtCheck_p3mod4(C: static Curve, gen: RandomGen) = proc main() = suite "Modular square root" & " [" & $WordBitwidth & "-bit mode]": staticFor(curve, TestCurves): - test "[𝔽p2] Random square root check for p ≡ 3 (mod 4) on " & $curve: - randomSqrtCheck_p3mod4(curve, gen = Uniform) - randomSqrtCheck_p3mod4(curve, gen = HighHammingWeight) - randomSqrtCheck_p3mod4(curve, gen = Long01Sequence) + test "[𝔽p2] Random square root check for " & $curve: + randomSqrtCheck(curve, gen = Uniform) + randomSqrtCheck(curve, gen = HighHammingWeight) + randomSqrtCheck(curve, gen = Long01Sequence) suite "Modular square root - 32-bit bugs highlighted by property-based testing " & " [" & $WordBitwidth & "-bit mode]": test "sqrt_if_square invalid square BLS12_381 - #64": diff --git a/tests/t_pairing_bls12_377_optate.nim b/tests/t_pairing_bls12_377_optate.nim new file mode 100644 index 0000000..f963b79 --- /dev/null +++ b/tests/t_pairing_bls12_377_optate.nim @@ -0,0 +1,16 @@ +# 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 + ../constantine/config/common, + ../constantine/config/curves, + ../constantine/pairing/pairing_bls12, + # Test utilities + ./t_pairing_template + +runPairingTests(4, BLS12_377, pairing_bls12) diff --git a/tests/t_pairing_cyclotomic_fp12.nim b/tests/t_pairing_cyclotomic_fp12.nim index cf1c9fb..845a599 100644 --- a/tests/t_pairing_cyclotomic_fp12.nim +++ b/tests/t_pairing_cyclotomic_fp12.nim @@ -23,9 +23,9 @@ import const Iters = 4 TestCurves = [ - # BN254_Nogami, - # BN254_Snarks, - # BLS12_377, + BN254_Nogami, + BN254_Snarks, + BLS12_377, BLS12_381 ] diff --git a/tests/t_pairing_mul_fp12_by_lines.nim b/tests/t_pairing_mul_fp12_by_lines.nim index 474e582..29a3811 100644 --- a/tests/t_pairing_mul_fp12_by_lines.nim +++ b/tests/t_pairing_mul_fp12_by_lines.nim @@ -27,6 +27,7 @@ const TestCurves = [ BN254_Nogami, BN254_Snarks, + BLS12_377, BLS12_381 ]