From 2f839cb1bf04e07dae9f5ed757cfa59565f42fd5 Mon Sep 17 00:00:00 2001 From: Mamy Ratsimbazafy Date: Mon, 13 Apr 2020 19:25:59 +0200 Subject: [PATCH] Initial support for Elliptic Curve (#24) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Elliptic curve and Twisted curve templates - initial commit * Support EC Add on G2 (Sextic Twisted curve for BN and BLS12 families) * Refactor the config parser to prepare for elliptic coefficient support * Add elliptic curve parameter for BN254 (Snarks), BLS12-381 and Zexe curve BLS12-377 * Add accessors to curve parameters * Allow computing the right-hand-side of of Weierstrass equation "y² = x³ + a x + b" * Randomized test infrastructure for elliptic curves * Start a testing suite on ellptic curve addition (failing) * detail projective addition * Fix EC addition test (forgot initializing Z=1 and that there ar emultiple infinity points) * Test with random Z coordinate + add elliptic curve test to test suite * fix reference to the (deactivated) addchain inversion for BN curves [skip ci] * .nims file leftover [skip ci] --- constantine.nimble | 14 +- constantine/arithmetic/finite_fields.nim | 82 ++++ .../arithmetic/finite_fields_inversion.nim | 2 +- constantine/config/curves.nim | 330 +++------------ constantine/config/curves_declaration.nim | 201 +++++++++ constantine/config/curves_derived.nim | 150 +++++++ constantine/config/curves_parser.nim | 387 +++++++++++++----- constantine/elliptic/README.md | 129 ++++++ .../elliptic/ec_weierstrass_affine.nim | 53 +++ .../elliptic/ec_weierstrass_projective.nim | 193 +++++++++ constantine/towers.nim | 21 + helpers/prng.nim | 52 ++- tests/test_ec_weierstrass_projective_g1.nim | 142 +++++++ 13 files changed, 1358 insertions(+), 398 deletions(-) create mode 100644 constantine/config/curves_declaration.nim create mode 100644 constantine/config/curves_derived.nim create mode 100644 constantine/elliptic/ec_weierstrass_affine.nim create mode 100644 constantine/elliptic/ec_weierstrass_projective.nim create mode 100644 constantine/towers.nim create mode 100644 tests/test_ec_weierstrass_projective_g1.nim diff --git a/constantine.nimble b/constantine.nimble index b8b0678..360213d 100644 --- a/constantine.nimble +++ b/constantine.nimble @@ -54,7 +54,10 @@ task test, "Run all tests": test "", "tests/test_fp6.nim" test "", "tests/test_fp12.nim" - if sizeof(int) == 8: # 32-bit tests + # Elliptic curve arithmetic + test "", "tests/test_ec_weierstrass_projective_g1.nim" + + if sizeof(int) == 8: # 32-bit tests on 64-bit arch # Primitives test "-d:Constantine32", "tests/test_primitives.nim" @@ -79,6 +82,9 @@ task test, "Run all tests": test "-d:Constantine32", "tests/test_fp6.nim" test "-d:Constantine32", "tests/test_fp12.nim" + # Elliptic curve arithmetic + test "-d:Constantine32", "tests/test_ec_weierstrass_projective_g1.nim" + task test_no_gmp, "Run tests that don't require GMP": # -d:testingCurves is configured in a *.nim.cfg for convenience @@ -102,6 +108,9 @@ task test_no_gmp, "Run tests that don't require GMP": test "", "tests/test_fp6.nim" test "", "tests/test_fp12.nim" + # Elliptic curve arithmetic + test "", "tests/test_ec_weierstrass_projective_g1.nim" + if sizeof(int) == 8: # 32-bit tests # Primitives test "-d:Constantine32", "tests/test_primitives.nim" @@ -123,6 +132,9 @@ task test_no_gmp, "Run tests that don't require GMP": test "-d:Constantine32", "tests/test_fp6.nim" test "-d:Constantine32", "tests/test_fp12.nim" + # Elliptic curve arithmetic + test "-d:Constantine32", "tests/test_ec_weierstrass_projective_g1.nim" + proc runBench(benchName: string, compiler = "") = if not dirExists "build": mkDir "build" diff --git a/constantine/arithmetic/finite_fields.nim b/constantine/arithmetic/finite_fields.nim index a12aa2a..6a21722 100644 --- a/constantine/arithmetic/finite_fields.nim +++ b/constantine/arithmetic/finite_fields.nim @@ -334,3 +334,85 @@ func `*=`*(a: var Fp, b: Fp) = func square*(a: var Fp) = ## Squaring modulo p a.mres.montySquare(a.mres, Fp.C.Mod, Fp.C.getNegInvModWord(), Fp.C.canUseNoCarryMontySquare()) + +func `*=`*(a: var Fp, b: static int) {.inline.} = + ## Multiplication by a small integer known at compile-time + # Implementation: + # We don't want to go convert the integer to the Montgomery domain (O(n²)) + # and then multiply by ``b`` (another O(n²) + # + # So we hardcode addition chains for small integer + # + # In terms of cost a doubling/addition is 3 passes over the data: + # - addition + check if > prime + conditional substraction + # A full multiplication, assuming b is projected to Montgomery domain beforehand is: + # - n² passes over the data, each of 5~6 elementary addition/multiplication + # - a conditional substraction + # + const negate = b < 0 + const b = if negate: -b + else: b + when negate: + a.neg(a) + when b == 0: + a.setZero() + elif b == 1: + return + elif b == 2: + a.double() + elif b == 3: + let t1 = a + a.double() + a += t1 + elif b == 4: + a.double() + a.double() + elif b == 5: + let t1 = a + a.double() + a.double() + a += t1 + elif b == 6: + a.double() + let t2 = a + a.double() # 4 + a += t2 + elif b == 7: + let t1 = a + a.double() + let t2 = a + a.double() # 4 + a += t2 + a += t1 + elif b == 8: + a.double() + a.double() + a.double() + elif b == 9: + let t1 = a + a.double() + a.double() + a.double() # 8 + a += t1 + elif b == 10: + a.double() + let t2 = a + a.double() + a.double() # 8 + a += t2 + elif b == 11: + let t1 = a + a.double() + let t2 = a + a.double() + a.double() # 8 + a += t2 + a += t1 + elif b == 12: + a.double() + a.double() # 4 + let t4 = a + a.double() # 8 + a += t4 + else: + {.error: "Multiplication by this small int not implemented".} diff --git a/constantine/arithmetic/finite_fields_inversion.nim b/constantine/arithmetic/finite_fields_inversion.nim index 47bb180..8d10a9f 100644 --- a/constantine/arithmetic/finite_fields_inversion.nim +++ b/constantine/arithmetic/finite_fields_inversion.nim @@ -127,7 +127,7 @@ func invmod_addchain_bn[C](r: var Fp[C], a: Fp[C]) = ## Requires a `bn` curve with a positive parameter `u` # TODO: debug for input "0x0d2007d8aaface1b8501bfbe792974166e8f9ad6106e5b563604f0aea9ab06f6" # see test suite - static: doAssert C.canUseFast_BN_Inversion() + static: doAssert C.canUse_BN_AddchainInversion() var v0 {.noInit.}, v1 {.noInit.}: Fp[C] diff --git a/constantine/config/curves.nim b/constantine/config/curves.nim index 28261a8..81c54d4 100644 --- a/constantine/config/curves.nim +++ b/constantine/config/curves.nim @@ -10,167 +10,14 @@ import # Standard library macros, # Internal - ./curves_parser, ./common, - ../arithmetic/[precomputed, bigints] + ./curves_declaration, ./curves_derived, ./curves_parser, + ../arithmetic/bigints + +export CurveFamily, Curve, SexticTwist # ############################################################ # -# Configuration of finite fields -# -# ############################################################ - -# Curves & their corresponding finite fields are preconfigured in this file - -# Note, in the past the convention was to name a curve by its conjectured security level. -# as this might change with advances in research, the new convention is -# to name curves according to the length of the prime bit length. -# i.e. the BN254 was previously named BN128. - -# Curves security level were significantly impacted by -# advances in the Tower Number Field Sieve. -# in particular BN254 curve security dropped -# from estimated 128-bit to estimated 100-bit -# Barbulescu, R. and S. Duquesne, "Updating Key Size Estimations for Pairings", -# Journal of Cryptology, DOI 10.1007/s00145-018-9280-5, January 2018. - -# Generates public: -# - type Curve* = enum -# - proc Mod*(curve: static Curve): auto -# which returns the field modulus of the curve -# - proc Family*(curve: static Curve): CurveFamily -# which returns the curve family -# - proc get_BN_param_u_BE*(curve: static Curve): array[N, byte] -# which returns the "u" parameter of a BN curve -# as a big-endian canonical integer representation -# if it's a BN curve and u is positive -# - proc get_BN_param_6u_minus1_BE*(curve: static Curve): array[N, byte] -# which returns the "6u-1" parameter of a BN curve -# as a big-endian canonical integer representation -# if it's a BN curve and u is positive. -# This is used for optimized field inversion for BN curves - -type - CurveFamily* = enum - NoFamily - BarretoNaehrig # BN curve - -declareCurves: - # ----------------------------------------------------------------------------- - # Curves added when passed "-d:testingCurves" - curve Fake101: - testingCurve: true - bitsize: 7 - modulus: "0x65" # 101 in hex - curve Fake103: # 103 ≡ 3 (mod 4) - testingCurve: true - bitsize: 7 - modulus: "0x67" # 103 in hex - curve Fake10007: # 10007 ≡ 3 (mod 4) - testingCurve: true - bitsize: 14 - modulus: "0x2717" # 10007 in hex - curve Fake65519: # 65519 ≡ 3 (mod 4) - testingCurve: true - bitsize: 16 - modulus: "0xFFEF" # 65519 in hex - curve Mersenne61: - testingCurve: true - bitsize: 61 - modulus: "0x1fffffffffffffff" # 2^61 - 1 - curve Mersenne127: - testingCurve: true - bitsize: 127 - modulus: "0x7fffffffffffffffffffffffffffffff" # 2^127 - 1 - # ----------------------------------------------------------------------------- - curve P224: # NIST P-224 - bitsize: 224 - modulus: "0xffffffff_ffffffff_ffffffff_ffffffff_00000000_00000000_00000001" - curve BN254_Nogami: # Integer Variable χ–Based Ate Pairing, 2008, Nogami et al - bitsize: 254 - modulus: "0x2523648240000001ba344d80000000086121000000000013a700000000000013" - family: BarretoNaehrig - # Equation: Y^2 = X^3 + 2 - # u: -(2^62 + 2^55 + 1) - curve BN254_Snarks: # Zero-Knowledge proofs curve (SNARKS, STARKS, Ethereum) - bitsize: 254 - modulus: "0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47" - family: BarretoNaehrig - bn_u_bitwidth: 63 - bn_u: "0x44E992B44A6909F1" - # Equation: Y^2 = X^3 + 3 - # u: 4965661367192848881 - curve Curve25519: # Bernstein curve - bitsize: 255 - modulus: "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed" - curve P256: # secp256r1 / NIST P-256 - bitsize: 256 - modulus: "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff" - curve Secp256k1: # Bitcoin curve - bitsize: 256 - modulus: "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F" - curve BLS12_377: - # Zexe curve - # (p41) https://eprint.iacr.org/2018/962.pdf - # https://github.com/ethereum/EIPs/blob/41dea9615/EIPS/eip-2539.md - bitsize: 377 - modulus: "0x01ae3a4617c510eac63b05c06ca1493b1a22d9f300f5138f1ef3622fba094800170b5d44300000008508c00000000001" - # u: 3 * 2^46 * (7 * 13 * 499) + 1 - # u: 0x8508c00000000001 - curve BLS12_381: - bitsize: 381 - modulus: "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" - # Equation: y^2 = x^3 + 4 - # u: -(2^63 + 2^62 + 2^60 + 2^57 + 2^48 + 2^16) - curve BN446: - bitsize: 446 - modulus: "0x2400000000000000002400000002d00000000d800000021c0000001800000000870000000b0400000057c00000015c000000132000000067" - family: BarretoNaehrig - # u = 2^110 + 2^36 + 1 - curve FKM12_447: # Fotiadis-Konstantinou-Martindale - bitsize: 447 - modulus: "0x4ce300001338c00001c08180000f20cfffffe5a8bffffd08a000000f228000007e8ffffffaddfffffffdc00000009efffffffca000000007" - # TNFS Resistant Families of Pairing-Friendly Elliptic Curves - # Georgios Fotiadis and Elisavet Konstantinou, 2018 - # https://eprint.iacr.org/2018/1017 - # - # Family 17 choice b of - # Optimal TNFS-secure pairings on elliptic curves with composite embedding degree - # Georgios Fotiadis and Chloe Martindale, 2019 - # https://eprint.iacr.org/2019/555 - # - # A short-list of pairing-friendly curves resistant toSpecial TNFS at the 128-bit security level - # Aurore Guillevic - # https://hal.inria.fr/hal-02396352v2/document - # - # p(x) = 1728x^6 + 2160x^5 + 1548x^4 + 756x^3 + 240x^2 + 54x + 7 - # t(x) = −6x² + 1, r(x) = 36x^4 + 36x^3 + 18x^2 + 6x + 1. - # Choice (b):u=−2^72 − 2^71 − 2^36 - # - # Note the paper mentions 446-bit but it's 447 - curve BLS12_461: - # Updating Key Size Estimations for Pairings - # Barbulescu, R. and S. Duquesne, 2018 - # https://hal.archives-ouvertes.fr/hal-01534101/file/main.pdf - bitsize: 461 - modulus: "0x15555545554d5a555a55d69414935fbd6f1e32d8bacca47b14848b42a8dffa5c1cc00f26aa91557f00400020000555554aaaaaac0000aaaaaaab" - # u = −2^77 + 2^50 + 2^33 - # p = (u - 1)^2 (u^4 - u^2 + 1)/3 + u - curve BN462: - # Pairing-Friendly Curves - # IETF Draft - # https://tools.ietf.org/id/draft-irtf-cfrg-pairing-friendly-curves-02.html - - # Updating Key Size Estimations for Pairings - # Barbulescu, R. and S. Duquesne, 2018 - # https://hal.archives-ouvertes.fr/hal-01534101/file/main.pdf - bitsize: 462 - modulus: "0x240480360120023ffffffffff6ff0cf6b7d9bfca0000000000d812908f41c8020ffffffffff6ff66fc6ff687f640000000002401b00840138013" - family: BarretoNaehrig - # u = 2^114 + 2^101 - 2^14 - 1 - -# ############################################################ -# -# Curve characteristics +# Field properties # # ############################################################ @@ -182,143 +29,68 @@ macro Mod*(C: static Curve): untyped = func getCurveBitSize*(C: static Curve): static int = ## Returns the number of bits taken by the curve modulus - result = static(CurveBitSize[C]) + result = static(CurveBitWidth[C]) template matchingBigInt*(C: static Curve): untyped = - BigInt[CurveBitSize[C]] + BigInt[CurveBitWidth[C]] func family*(C: static Curve): CurveFamily = result = static(CurveFamilies[C]) # ############################################################ # -# Autogeneration of precomputed constants in ROM +# Curve properties # # ############################################################ -macro genConstants(): untyped = - ## Store - ## - the Montgomery magic constant "R^2 mod N" in ROM - ## For each curve under the private symbol "MyCurve_R2modP" - ## - the Montgomery magic constant -1/P mod 2^WordBitSize - ## For each curve under the private symbol "MyCurve_NegInvModWord - ## - ... - result = newStmtList() +macro getEquationForm*(C: static Curve): untyped = + ## Returns the equation form + result = bindSym($C & "_equation_form") - template used(name: string): NimNode = - nnkPragmaExpr.newTree( - ident(name), - nnkPragma.newTree(ident"used") - ) +macro getCoefA*(C: static Curve): untyped = + ## Returns the A coefficient of the curve + ## The return type is polymorphic, it can be an int + ## or a bigInt depending on the curve + result = bindSym($C & "_coef_A") - for curve in Curve.low .. Curve.high: - # const MyCurve_CanUseNoCarryMontyMul = useNoCarryMontyMul(MyCurve_Modulus) - result.add newConstStmt( - used($curve & "_CanUseNoCarryMontyMul"), newCall( - bindSym"useNoCarryMontyMul", - bindSym($curve & "_Modulus") - ) - ) +macro getCoefB*(C: static Curve): untyped = + ## Returns the B coefficient of the curve + ## The return type is polymorphic, it can be an int + ## or a bigInt depending on the curve + result = bindSym($C & "_coef_B") - # const MyCurve_CanUseNoCarryMontySquare = useNoCarryMontySquare(MyCurve_Modulus) - result.add newConstStmt( - used($curve & "_CanUseNoCarryMontySquare"), newCall( - bindSym"useNoCarryMontySquare", - bindSym($curve & "_Modulus") - ) - ) +macro get_QNR_Fp*(C: static Curve): untyped = + ## Returns the tower extension quadratic non-residue in 𝔽p + ## i.e. a number that is not a square in 𝔽p + result = bindSym($C & "_nonresidue_quad_fp") - # const MyCurve_R2modP = r2mod(MyCurve_Modulus) - result.add newConstStmt( - used($curve & "_R2modP"), newCall( - bindSym"r2mod", - bindSym($curve & "_Modulus") - ) - ) +macro get_CNR_Fp2*(C: static Curve): untyped = + ## Returns the tower extension cubic non-residue 𝔽p² + ## i.e. a number that is not a cube in 𝔽p² + ## + ## The return value is a tuple (a, b) + ## that corresponds to the number a + b𝑗 + ## with 𝑗 choosen for 𝑗² - QNR_Fp == 0 + ## i.e. if -1 is chosen as a quadratic non-residue 𝑗 = √-1 + ## if -2 is chosen as a quadratic non-residue 𝑗 = √-2 + result = bindSym($C & "_nonresidue_cube_fp2") - # const MyCurve_NegInvModWord = negInvModWord(MyCurve_Modulus) - result.add newConstStmt( - used($curve & "_NegInvModWord"), newCall( - bindSym"negInvModWord", - bindSym($curve & "_Modulus") - ) - ) - # const MyCurve_montyOne = montyOne(MyCurve_Modulus) - result.add newConstStmt( - used($curve & "_MontyOne"), newCall( - bindSym"montyOne", - bindSym($curve & "_Modulus") - ) - ) - # const MyCurve_MontyPrimeMinus1 = montyPrimeMinus1(MyCurve_Modulus) - result.add newConstStmt( - used($curve & "_MontyPrimeMinus1"), newCall( - bindSym"montyPrimeMinus1", - bindSym($curve & "_Modulus") - ) - ) - # const MyCurve_InvModExponent = primeMinus2_BE(MyCurve_Modulus) - result.add newConstStmt( - used($curve & "_InvModExponent"), newCall( - bindSym"primeMinus2_BE", - bindSym($curve & "_Modulus") - ) - ) - # const MyCurve_PrimePlus1div2 = primePlus1div2(MyCurve_Modulus) - result.add newConstStmt( - used($curve & "_PrimePlus1div2"), newCall( - bindSym"primePlus1div2", - bindSym($curve & "_Modulus") - ) - ) - # const MyCurve_PrimeMinus1div2_BE = primeMinus1div2_BE(MyCurve_Modulus) - result.add newConstStmt( - used($curve & "_PrimeMinus1div2_BE"), newCall( - bindSym"primeMinus1div2_BE", - bindSym($curve & "_Modulus") - ) - ) - # const MyCurve_PrimeMinus3div4_BE = primeMinus3div4_BE(MyCurve_Modulus) - result.add newConstStmt( - used($curve & "_PrimeMinus3div4_BE"), newCall( - bindSym"primeMinus3div4_BE", - bindSym($curve & "_Modulus") - ) - ) - # const MyCurve_PrimePlus1div4_BE = primePlus1div4_BE(MyCurve_Modulus) - result.add newConstStmt( - used($curve & "_PrimePlus1div4_BE"), newCall( - bindSym"primePlus1div4_BE", - bindSym($curve & "_Modulus") - ) - ) +macro getSexticTwist*(C: static Curve): untyped = + result = bindSym($C & "_sexticTwist") - if CurveFamilies[curve] == BarretoNaehrig: - # when declared(MyCurve_BN_param_u): - # const MyCurve_BN_u_BE = toCanonicalIntRepr(MyCurve_BN_param_u) - # const MyCurve_BN_6u_minus_1_BE = bn_6u_minus_1_BE(MyCurve_BN_param_u) - var bnStmts = newStmtList() - bnStmts.add newConstStmt( - used($curve & "_BN_u_BE"), newCall( - bindSym"toCanonicalIntRepr", - ident($curve & "_BN_param_u") - ) - ) - bnStmts.add newConstStmt( - used($curve & "_BN_6u_minus_1_BE"), newCall( - bindSym"bn_6u_minus_1_BE", - ident($curve & "_BN_param_u") - ) - ) +macro get_SNR_Fp2*(C: static Curve): untyped = + ## Returns the sextic non-residue in 𝔽p² + ## choosen to build the twisted curve E'(𝔽p²) + ## i.e. a number µ so that x⁶ - µ is irreducible + result = bindSym($C & "_sexticNonResidue_fp2") - result.add nnkWhenStmt.newTree( - nnkElifBranch.newTree( - newCall(ident"declared", ident($curve & "_BN_param_u")), - bnStmts - ) - ) +# ############################################################ +# +# Access precomputed derived constants in ROM +# +# ############################################################ -genConstants() +genDerivedConstants() macro canUseNoCarryMontyMul*(C: static Curve): untyped = ## Returns true if the Modulus is compatible with a fast @@ -335,7 +107,7 @@ macro getR2modP*(C: static Curve): untyped = result = bindSym($C & "_R2modP") macro getNegInvModWord*(C: static Curve): untyped = - ## Get the Montgomery "-1/P[0] mod 2^WordBitSize" constant associated to a curve field modulus + ## Get the Montgomery "-1/P[0] mod 2^Wordbitwidth" constant associated to a curve field modulus result = bindSym($C & "_NegInvModWord") macro getMontyOne*(C: static Curve): untyped = @@ -369,11 +141,11 @@ macro getPrimePlus1div4_BE*(C: static Curve): untyped = # Family specific # ------------------------------------------------------- -macro canUseFast_BN_Inversion*(C: static Curve): untyped = +macro canUse_BN_AddchainInversion*(C: static Curve): untyped = ## A BN curve can use the fast BN inversion if the parameter "u" is positive if CurveFamilies[C] != BarretoNaehrig: return newLit false - return bindSym($C & "_BN_can_use_fast_inversion") + return bindSym($C & "_BN_can_use_addchain_inversion") macro getBN_param_u_BE*(C: static Curve): untyped = ## Get the ``u`` parameter of a BN curve in canonical big-endian representation diff --git a/constantine/config/curves_declaration.nim b/constantine/config/curves_declaration.nim new file mode 100644 index 0000000..70aa6af --- /dev/null +++ b/constantine/config/curves_declaration.nim @@ -0,0 +1,201 @@ +# 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 + # Internal + ./curves_parser + +export CurveFamily + +# ############################################################ +# +# Configuration of finite fields +# +# ############################################################ + +# Curves & their corresponding finite fields are preconfigured in this file + +# Note, in the past the convention was to name a curve by its conjectured security level. +# as this might change with advances in research, the new convention is +# to name curves according to the length of the prime bit length. +# i.e. the BN254 was previously named BN128. + +# Curves security level were significantly impacted by +# advances in the Tower Number Field Sieve. +# in particular BN254 curve security dropped +# from estimated 128-bit to estimated 100-bit +# Barbulescu, R. and S. Duquesne, "Updating Key Size Estimations for Pairings", +# Journal of Cryptology, DOI 10.1007/s00145-018-9280-5, January 2018. + +# Generates public: +# - type Curve* = enum +# - proc Mod*(curve: static Curve): auto +# which returns the field modulus of the curve +# - proc Family*(curve: static Curve): CurveFamily +# which returns the curve family +# - proc get_BN_param_u_BE*(curve: static Curve): array[N, byte] +# which returns the "u" parameter of a BN curve +# as a big-endian canonical integer representation +# if it's a BN curve and u is positive +# - proc get_BN_param_6u_minus1_BE*(curve: static Curve): array[N, byte] +# which returns the "6u-1" parameter of a BN curve +# as a big-endian canonical integer representation +# if it's a BN curve and u is positive. +# This is used for optimized field inversion for BN curves + +declareCurves: + # ----------------------------------------------------------------------------- + # Curves added when passed "-d:testingCurves" + curve Fake101: + testingCurve: true + bitwidth: 7 + modulus: "0x65" # 101 in hex + curve Fake103: # 103 ≡ 3 (mod 4) + testingCurve: true + bitwidth: 7 + modulus: "0x67" # 103 in hex + curve Fake10007: # 10007 ≡ 3 (mod 4) + testingCurve: true + bitwidth: 14 + modulus: "0x2717" # 10007 in hex + curve Fake65519: # 65519 ≡ 3 (mod 4) + testingCurve: true + bitwidth: 16 + modulus: "0xFFEF" # 65519 in hex + curve Mersenne61: + testingCurve: true + bitwidth: 61 + modulus: "0x1fffffffffffffff" # 2^61 - 1 + curve Mersenne127: + testingCurve: true + bitwidth: 127 + modulus: "0x7fffffffffffffffffffffffffffffff" # 2^127 - 1 + # ----------------------------------------------------------------------------- + curve P224: # NIST P-224 + bitwidth: 224 + modulus: "0xffffffff_ffffffff_ffffffff_ffffffff_00000000_00000000_00000001" + curve BN254_Nogami: # Integer Variable χ–Based Ate Pairing, 2008, Nogami et al + bitwidth: 254 + modulus: "0x2523648240000001ba344d80000000086121000000000013a700000000000013" + family: BarretoNaehrig + # Equation: Y^2 = X^3 + 2 + # u: -(2^62 + 2^55 + 1) + curve BN254_Snarks: # Zero-Knowledge proofs curve (SNARKS, STARKS, Ethereum) + bitwidth: 254 + modulus: "0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47" + family: BarretoNaehrig + bn_u_bitwidth: 63 + bn_u: "0x44E992B44A6909F1" # u: 4965661367192848881 + + # G1 Equation: Y^2 = X^3 + 3 + # G2 Equation: Y^2 = X^3 + 3/(9+𝑖) + eq_form: ShortWeierstrass + coef_a: 0 + coef_b: 3 + nonresidue_quad_fp: -1 # -1 is not a square in 𝔽p + nonresidue_cube_fp2: (9, 1) # 9+𝑖 9+𝑖 is not a cube in 𝔽p² + + sexticTwist: D_Twist + sexticNonResidue_fp2: (9, 1) # 9+𝑖 + + curve Curve25519: # Bernstein curve + bitwidth: 255 + modulus: "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed" + curve P256: # secp256r1 / NIST P-256 + bitwidth: 256 + modulus: "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff" + curve Secp256k1: # Bitcoin curve + bitwidth: 256 + modulus: "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F" + curve BLS12_377: + # Zexe curve + # (p41) https://eprint.iacr.org/2018/962.pdf + # https://github.com/ethereum/EIPs/blob/41dea9615/EIPS/eip-2539.md + bitwidth: 377 + modulus: "0x01ae3a4617c510eac63b05c06ca1493b1a22d9f300f5138f1ef3622fba094800170b5d44300000008508c00000000001" + family: BarretoLynnScott + # u: 3 * 2^46 * (7 * 13 * 499) + 1 + # u: 0x8508c00000000001 + + # G1 Equation: y² = x³ + 1 + # G2 Equation: y² = x³ + 1/ with 𝑗 = √-5 + eq_form: ShortWeierstrass + coef_a: 0 + coef_b: 1 + nonresidue_quad_fp: -5 # -5 is not a square in 𝔽p + nonresidue_cube_fp2: (0, 1) # √-5 √-5 is not a cube in 𝔽p² + + sexticTwist: D_Twist + sexticNonResidue_fp2: (0, 1) # √-5 + + curve BLS12_381: + bitwidth: 381 + modulus: "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" + family: BarretoLynnScott + # u: -(2^63 + 2^62 + 2^60 + 2^57 + 2^48 + 2^16) + + # G1 Equation: y² = x³ + 4 + # G2 Equation: y² = x³ + 4 (1+i) + eq_form: ShortWeierstrass + coef_a: 0 + coef_b: 4 + nonresidue_quad_fp: -1 # -1 is not a square in 𝔽p + nonresidue_cube_fp2: (1, 1) # 1+𝑖 1+𝑖 is not a cube in 𝔽p² + + sexticTwist: M_Twist + sexticNonResidue_fp2: (1, 1) # 1+𝑖 + + curve BN446: + bitwidth: 446 + modulus: "0x2400000000000000002400000002d00000000d800000021c0000001800000000870000000b0400000057c00000015c000000132000000067" + family: BarretoNaehrig + # u = 2^110 + 2^36 + 1 + curve FKM12_447: # Fotiadis-Konstantinou-Martindale + bitwidth: 447 + modulus: "0x4ce300001338c00001c08180000f20cfffffe5a8bffffd08a000000f228000007e8ffffffaddfffffffdc00000009efffffffca000000007" + # TNFS Resistant Families of Pairing-Friendly Elliptic Curves + # Georgios Fotiadis and Elisavet Konstantinou, 2018 + # https://eprint.iacr.org/2018/1017 + # + # Family 17 choice b of + # Optimal TNFS-secure pairings on elliptic curves with composite embedding degree + # Georgios Fotiadis and Chloe Martindale, 2019 + # https://eprint.iacr.org/2019/555 + # + # A short-list of pairing-friendly curves resistant toSpecial TNFS at the 128-bit security level + # Aurore Guillevic + # https://hal.inria.fr/hal-02396352v2/document + # + # p(x) = 1728x^6 + 2160x^5 + 1548x^4 + 756x^3 + 240x^2 + 54x + 7 + # t(x) = −6x² + 1, r(x) = 36x^4 + 36x^3 + 18x^2 + 6x + 1. + # Choice (b):u=−2^72 − 2^71 − 2^36 + # + # Note the paper mentions 446-bit but it's 447 + curve BLS12_461: + # Updating Key Size Estimations for Pairings + # Barbulescu, R. and S. Duquesne, 2018 + # https://hal.archives-ouvertes.fr/hal-01534101/file/main.pdf + bitwidth: 461 + modulus: "0x15555545554d5a555a55d69414935fbd6f1e32d8bacca47b14848b42a8dffa5c1cc00f26aa91557f00400020000555554aaaaaac0000aaaaaaab" + # u = −2^77 + 2^50 + 2^33 + # p = (u - 1)^2 (u^4 - u^2 + 1)/3 + u + + # Note there is another BLS12-461 proposed here: + # https://tools.ietf.org/id/draft-yonezawa-pairing-friendly-curves-00.html#rfc.section.4.2 + curve BN462: + # Pairing-Friendly Curves + # IETF Draft + # https://tools.ietf.org/id/draft-irtf-cfrg-pairing-friendly-curves-02.html + + # Updating Key Size Estimations for Pairings + # Barbulescu, R. and S. Duquesne, 2018 + # https://hal.archives-ouvertes.fr/hal-01534101/file/main.pdf + bitwidth: 462 + modulus: "0x240480360120023ffffffffff6ff0cf6b7d9bfca0000000000d812908f41c8020ffffffffff6ff66fc6ff687f640000000002401b00840138013" + family: BarretoNaehrig + # u = 2^114 + 2^101 - 2^14 - 1 diff --git a/constantine/config/curves_derived.nim b/constantine/config/curves_derived.nim new file mode 100644 index 0000000..a055114 --- /dev/null +++ b/constantine/config/curves_derived.nim @@ -0,0 +1,150 @@ +# 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 + macros, + # Internal + ../arithmetic/precomputed, + ./curves_declaration + +{.experimental: "dynamicBindSym".} + +macro genDerivedConstants*(): untyped = + ## Generate constants derived from the main constants + ## + ## For example + ## - the Montgomery magic constant "R^2 mod N" in ROM + ## For each curve under the private symbol "MyCurve_R2modP" + ## - the Montgomery magic constant -1/P mod 2^Wordbitwidth + ## For each curve under the private symbol "MyCurve_NegInvModWord + ## - ... + + # Now typedesc are NimNode and there is no way to translate + # NimNode -> typedesc easily so we can't + # "for curve in low(Curve) .. high(Curve):" + # As an ugly workaround, we count + # The item at position 0 is a pragma + let curveList = Curve.getType[1].getType + + result = newStmtList() + + template used(name: string): NimNode = + nnkPragmaExpr.newTree( + ident(name), + nnkPragma.newTree(ident"used") + ) + + for curveSym in low(Curve) .. high(Curve): + let curve = $curveSym + + # const MyCurve_CanUseNoCarryMontyMul = useNoCarryMontyMul(MyCurve_Modulus) + result.add newConstStmt( + used(curve & "_CanUseNoCarryMontyMul"), newCall( + bindSym"useNoCarryMontyMul", + bindSym(curve & "_Modulus") + ) + ) + + # const MyCurve_CanUseNoCarryMontySquare = useNoCarryMontySquare(MyCurve_Modulus) + result.add newConstStmt( + used(curve & "_CanUseNoCarryMontySquare"), newCall( + bindSym"useNoCarryMontySquare", + bindSym(curve & "_Modulus") + ) + ) + + # const MyCurve_R2modP = r2mod(MyCurve_Modulus) + result.add newConstStmt( + used(curve & "_R2modP"), newCall( + bindSym"r2mod", + bindSym(curve & "_Modulus") + ) + ) + + # const MyCurve_NegInvModWord = negInvModWord(MyCurve_Modulus) + result.add newConstStmt( + used(curve & "_NegInvModWord"), newCall( + bindSym"negInvModWord", + bindSym(curve & "_Modulus") + ) + ) + # const MyCurve_montyOne = montyOne(MyCurve_Modulus) + result.add newConstStmt( + used(curve & "_MontyOne"), newCall( + bindSym"montyOne", + bindSym(curve & "_Modulus") + ) + ) + # const MyCurve_MontyPrimeMinus1 = montyPrimeMinus1(MyCurve_Modulus) + result.add newConstStmt( + used(curve & "_MontyPrimeMinus1"), newCall( + bindSym"montyPrimeMinus1", + bindSym(curve & "_Modulus") + ) + ) + # const MyCurve_InvModExponent = primeMinus2_BE(MyCurve_Modulus) + result.add newConstStmt( + used(curve & "_InvModExponent"), newCall( + bindSym"primeMinus2_BE", + bindSym(curve & "_Modulus") + ) + ) + # const MyCurve_PrimePlus1div2 = primePlus1div2(MyCurve_Modulus) + result.add newConstStmt( + used(curve & "_PrimePlus1div2"), newCall( + bindSym"primePlus1div2", + bindSym(curve & "_Modulus") + ) + ) + # const MyCurve_PrimeMinus1div2_BE = primeMinus1div2_BE(MyCurve_Modulus) + result.add newConstStmt( + used(curve & "_PrimeMinus1div2_BE"), newCall( + bindSym"primeMinus1div2_BE", + bindSym(curve & "_Modulus") + ) + ) + # const MyCurve_PrimeMinus3div4_BE = primeMinus3div4_BE(MyCurve_Modulus) + result.add newConstStmt( + used(curve & "_PrimeMinus3div4_BE"), newCall( + bindSym"primeMinus3div4_BE", + bindSym(curve & "_Modulus") + ) + ) + # const MyCurve_PrimePlus1div4_BE = primePlus1div4_BE(MyCurve_Modulus) + result.add newConstStmt( + used(curve & "_PrimePlus1div4_BE"), newCall( + bindSym"primePlus1div4_BE", + bindSym(curve & "_Modulus") + ) + ) + + if CurveFamilies[curveSym] == BarretoNaehrig: + # when declared(MyCurve_BN_param_u): + # const MyCurve_BN_u_BE = toCanonicalIntRepr(MyCurve_BN_param_u) + # const MyCurve_BN_6u_minus_1_BE = bn_6u_minus_1_BE(MyCurve_BN_param_u) + var bnStmts = newStmtList() + bnStmts.add newConstStmt( + used(curve & "_BN_u_BE"), newCall( + bindSym"toCanonicalIntRepr", + ident(curve & "_BN_param_u") + ) + ) + bnStmts.add newConstStmt( + used(curve & "_BN_6u_minus_1_BE"), newCall( + bindSym"bn_6u_minus_1_BE", + ident(curve & "_BN_param_u") + ) + ) + + result.add nnkWhenStmt.newTree( + nnkElifBranch.newTree( + newCall(ident"declared", ident(curve & "_BN_param_u")), + bnStmts + ) + ) diff --git a/constantine/config/curves_parser.nim b/constantine/config/curves_parser.nim index 75e020f..602027f 100644 --- a/constantine/config/curves_parser.nim +++ b/constantine/config/curves_parser.nim @@ -8,31 +8,114 @@ import # Standard library - macros, + std/[macros, strutils], # Internal - ../io/io_bigints, ../arithmetic/bigints + ../io/io_bigints, ../arithmetic/[bigints, precomputed] -# Macro to parse declarative curves configuration. +# Parsing is done in 2 steps: +# 1. All declared parameters are collected in a {.compileTime.} seq[CurveParams] +# 2. Those parameters are assigned to a constant +# If needed a macro is defined to retrieve those in a generic way. +# +# Using a const indirection rather than directly accessing the {.compileTime.} object ensures 2 things: +# - properly cross the compile-time -> runtime boundary +# - avoid inlining large const arrays at the call site +# for example when using the `r2modP` constant in multiple overloads in the same module +# TODO: check that those constants use extern const to avoid duplication across modules -macro declareCurves*(curves: untyped): untyped = - ## Parse curve configuration and generates - ## - ## type Curve = enum - ## BN254 - ## ... - ## - ## const CurveBitSize* = array[ - ## BN254: 254, - ## ... - ## ] - ## - ## TODO: Ensure that the modulus is not inlined at runtime - ## to avoid codesize explosion. - ## const BN254_Modulus = fromHex(BigInt[254], "0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47") - ## - ## func fieldModulus*(curve: static Curve): auto = - ## when curve == BN254_Modulus: BN254_Modulus - ## ... +type + CurveFamily* = enum + NoFamily + BarretoNaehrig # BN curve + BarretoLynnScott # BLS curve + + CurveCoefKind* = enum + ## Small coefficients fit in an int64 + ## Large ones require a bigint + ## Note that some seemingly large coefficients might be small + ## when represented as a negative modular integer + ## + ## NoCoef is used when the curve is not defined (i.e. we are only interest in field arithmetic) + ## use `Small` to set a coef to ``0`` + NoCoef + Small + Large + + CurveCoef* = object + case kind: CurveCoefKind + of NoCoef: discard + of Small: coef: int + of Large: coefHex: string + + CurveEquationForm* = enum + ShortWeierstrass + + SexticTwist* = enum + ## The sextic twist type of the current elliptic curve + ## + ## Assuming a standard curve `E` over the prime field `𝔽p` + ## denoted `E(𝔽p)` in Short Weierstrass form + ## y² = x³ + Ax + B + ## + ## If E(𝔽p^k), the elliptic curve defined over the extension field + ## of degree k, the embedding degree, admits an isomorphism + ## to a curve E'(Fp^(k/d)), we call E' a twisted curve. + ## + ## For pairing they have the following equation + ## y² = x³ + Ax/µ² + B/µ³ for a D-Twist (Divisor) + ## or + ## y² = x³ + µ²Ax + µ³B for a M-Twist (Multiplicand) + ## with the polynomial x^k - µ being irreducible. + ## + ## i.e. if d == 2, E' is a quadratic twist and µ is a quadratic non-residue + ## if d == 4, E' is a quartic twist + ## if d == 6, E' is a sextic twist + ## + ## References: + ## - Efficient Pairings on Twisted Elliptic Curve + ## Yasuyuki Nogami, Masataka Akane, Yumi Sakemi and Yoshitaka Morikawa, 2010 + ## https://www.researchgate.net/publication/221908359_Efficient_Pairings_on_Twisted_Elliptic_Curve + ## + ## - A note on twists for pairing friendly curves\ + ## Michael Scott, 2009\ + ## http://indigo.ie/~mscott/twists.pdf + NotTwisted + D_Twist + M_Twist + + CurveParams = object + ## All the curve parameters that may be defined + # Note: we don't use case object here, the transition is annoying + # and would force use to scan all "kind" field (eq_form, family, ...) + # before instantiating the object. + name: NimNode + + # Field parameters + bitWidth: NimNode # nnkIntLit + modulus: NimNode # nnkStrLit (hex) + + # Towering + nonresidue_quad_fp: NimNode # nnkIntLit + nonresidue_cube_fp2: NimNode # nnkPar(nnkIntLit, nnkIntLit) + + # Curve parameters + eq_form: CurveEquationForm + coef_A: CurveCoef + coef_B: CurveCoef + + sexticTwist: SexticTwist + sexticNonResidue_fp2: NimNode # nnkPar(nnkIntLit, nnkIntLit) + + family: CurveFamily + # BN family + # ------------------------ + bn_u_bitwidth: NimNode # nnkIntLit + bn_u: NimNode # nnkStrLit (hex) + +var curvesDefinitions {.compileTime.}: seq[CurveParams] + +proc parseCurveDecls(defs: var seq[CurveParams], curves: NimNode) = + ## Parse the curve declarations and store them in the curve definitions curves.expectKind(nnkStmtList) # curve BN254: @@ -55,26 +138,19 @@ macro declareCurves*(curves: untyped): untyped = # StmtList # StrLit "0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47" - var Curves: seq[NimNode] - var MapCurveBitWidth = nnkBracket.newTree() - var MapCurveFamily = nnkBracket.newTree() - var curveModStmts = newStmtList() - var curveExtraStmts = newStmtList() - for curveDesc in curves: # Checks # ----------------------------------------------- curveDesc.expectKind(nnkCommand) doAssert curveDesc[0].eqIdent"curve" - curveDesc[1].expectKind(nnkIdent) # Curve name - curveDesc[2].expectKind(nnkStmtList) - curveDesc[2][0].expectKind(nnkCall) - curveDesc[2][1].expectKind(nnkCall) - # Mandatory fields - # ----------------------------------------------- let curve = curveDesc[1] let curveParams = curveDesc[2] + curve.expectKind(nnkIdent) # Curve name + curveParams.expectKind(nnkStmtList) # Curve parameters + + # Skip test curves if not testing + # ----------------------------------------------- var offset = 0 var testCurve = false @@ -82,86 +158,158 @@ macro declareCurves*(curves: untyped): untyped = offset = 1 testCurve = curveParams[0][1].boolVal - let sizeSection = curveParams[offset] - doAssert sizeSection[0].eqIdent"bitsize" - sizeSection[1].expectKind(nnkStmtList) - let bitSize = sizeSection[1][0] + if testCurve and defined(testingCurves): + continue - let modSection = curveParams[offset+1] - doAssert modSection[0].eqIdent"modulus" - modSection[1].expectKind(nnkStmtList) - let modulus = modSection[1][0] - - # Construct the constants + # Parameters # ----------------------------------------------- - if not testCurve or defined(testingCurves): - Curves.add curve - # "BN254: 254" for array construction - MapCurveBitWidth.add nnkExprColonExpr.newTree( - curve, bitSize - ) + var params = CurveParams(name: curve) + for i in offset ..< curveParams.len: + let sectionId = curveParams[i][0] + curveParams[i][1].expectKind(nnkStmtList) + let sectionVal = curveParams[i][1][0] - # const BN254_Snarks_Modulus = fromHex(BigInt[254], "0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47") - let modulusID = ident($curve & "_Modulus") - curveModStmts.add newConstStmt( - modulusID, - newCall( - bindSym"fromHex", - nnkBracketExpr.newTree(bindSym"BigInt", bitSize), - modulus - ) - ) - - # Family specific - # ----------------------------------------------- - if offset + 2 < curveParams.len: - let familySection = curveParams[offset+2] - doAssert familySection[0].eqIdent"family" - familySection[1].expectKind(nnkStmtList) - let family = familySection[1][0] - - MapCurveFamily.add nnkExprColonExpr.newTree( - curve, family - ) - - # BN curves - # ----------------------------------------------- - if family.eqIdent"BarretoNaehrig": - if offset + 5 == curveParams.len: - if curveParams[offset+3][0].eqIdent"bn_u_bitwidth" and - curveParams[offset+4][0].eqIdent"bn_u": - - let bn_u_bitwidth = curveParams[offset+3][1][0] - let bn_u = curveParams[offset+4][1][0] - - # const BN254_Snarks_BN_can_use_fast_inversion = ... - curveExtraStmts.add newConstStmt( - ident($curve & "_BN_can_use_fast_inversion"), - if ($bn_u)[0] == '-': newLit false # negative ``u`` can use the specialized fast inversion - else: newLit true - ) - - # const BN254_Snarks_BN_param_u = fromHex(BigInt[63], "0x44E992B44A6909F1") - curveExtraStmts.add newConstStmt( - ident($curve & "_BN_param_u"), - newCall( - bindSym"fromHex", - nnkBracketExpr.newTree(bindSym"BigInt", bn_u_bitwidth), - bn_u - ) - ) + if sectionId.eqIdent"bitwidth": + params.bitWidth = sectionVal + elif sectionId.eqident"modulus": + params.modulus = sectionVal + elif sectionId.eqIdent"family": + params.family = parseEnum[CurveFamily]($sectionVal) + elif sectionId.eqIdent"bn_u_bitwidth": + params.bn_u_bitwidth = sectionVal + elif sectionId.eqIdent"bn_u": + params.bn_u = sectionVal + elif sectionId.eqIdent"eq_form": + params.eq_form = parseEnum[CurveEquationForm]($sectionVal) + elif sectionId.eqIdent"coef_a": + if sectionVal.kind == nnkIntLit: + params.coef_A = CurveCoef(kind: Small, coef: sectionVal.intVal.int) else: - # const BN254_Snarks_BN_can_use_fast_inversion = ... - curveExtraStmts.add newConstStmt( - ident($curve & "_BN_can_use_fast_inversion"), - newLit false - ) + params.coef_A = CurveCoef(kind: Large, coefHex: sectionVal.strVal) + elif sectionId.eqIdent"coef_b": + if sectionVal.kind == nnkIntLit: + params.coef_B = CurveCoef(kind: Small, coef: sectionVal.intVal.int) + else: + params.coef_B = CurveCoef(kind: Large, coefHex: sectionVal.strVal) + elif sectionId.eqIdent"nonresidue_quad_fp": + params.nonresidue_quad_fp = sectionVal + elif sectionId.eqIdent"nonresidue_cube_fp2": + params.nonresidue_cube_fp2 = sectionVal + elif sectionId.eqIdent"sexticTwist": + params.sexticTwist = parseEnum[SexticTwist]($sectionVal) + elif sectionId.eqIdent"sexticNonResidue_fp2": + params.sexticNonResidue_fp2 = sectionVal + else: + error "Invalid section: \n", curveParams[i].toStrLit() - else: - MapCurveFamily.add nnkExprColonExpr.newTree( - curve, ident"NoFamily" + defs.add params + +proc exported(id: string): NimNode = + nnkPostfix.newTree( + ident"*", + ident(id) + ) + +template getCoef(c: CurveCoef, width: NimNode): untyped {.dirty.}= + case c.kind + of NoCoef: + error "Unreachable" + nnkDiscardStmt.newTree(newLit "Dummy") + of Small: + newLit c.coef + of Large: + newCall( + bindSym"fromHex", + nnkBracketExpr.newTree(bindSym"BigInt", width), + newLit c.coefHex + ) + +proc genMainConstants(defs: var seq[CurveParams]): NimNode = + ## Generate curves and fields main constants + + var Curves: seq[NimNode] + var MapCurveBitWidth = nnkBracket.newTree() + var MapCurveFamily = nnkBracket.newTree() + var curveModStmts = newStmtList() + var curveEllipticStmts = newStmtList() + var curveExtraStmts = newStmtList() + + for curveDef in defs: + curveDef.name.expectKind(nnkIdent) + curveDef.bitWidth.expectKind(nnkIntLit) + curveDef.modulus.expectKind(nnkStrLit) + + let curve = curveDef.name + let bitWidth = curveDef.bitWidth + let modulus = curveDef.modulus + let family = curveDef.family + + Curves.add curve + # "BN254_Snarks: 254" array construction expression + MapCurveBitWidth.add nnkExprColonExpr.newTree( + curve, bitWidth + ) + + # const BN254_Snarks_Modulus = fromHex(BigInt[254], "0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47") + curveModStmts.add newConstStmt( + exported($curve & "_Modulus"), + newCall( + bindSym"fromHex", + nnkBracketExpr.newTree(bindSym"BigInt", bitWidth), + modulus + ) + ) + + MapCurveFamily.add nnkExprColonExpr.newTree( + curve, newLit(family) + ) + # Curve equation + # ----------------------------------------------- + curveEllipticStmts.add newConstStmt( + exported($curve & "_equation_form"), + newLit curveDef.eq_form + ) + if curveDef.coef_A.kind != NoCoef and curveDef.coef_B.kind != NoCoef: + curveEllipticStmts.add newConstStmt( + exported($curve & "_coef_A"), + curveDef.coef_A.getCoef(bitWidth) + ) + curveEllipticStmts.add newConstStmt( + exported($curve & "_coef_B"), + curveDef.coef_B.getCoef(bitWidth) + ) + curveEllipticStmts.add newConstStmt( + exported($curve & "_nonresidue_quad_fp"), + curveDef.nonresidue_quad_fp + ) + curveEllipticStmts.add newConstStmt( + exported($curve & "_nonresidue_cube_fp2"), + curveDef.nonresidue_cube_fp2 + ) + curveEllipticStmts.add newConstStmt( + exported($curve & "_sexticTwist"), + newLit curveDef.sexticTwist + ) + curveEllipticStmts.add newConstStmt( + exported($curve & "_sexticNonResidue_fp2"), + curveDef.sexticNonResidue_fp2 ) + # BN curves + # ----------------------------------------------- + if family == BarretoNaehrig: + if not curveDef.bn_u_bitwidth.isNil and + not curveDef.bn_u.isNil and + ($curveDef.bn_u)[0] != '-': # The parameter must be positive + curveExtraStmts.add newConstStmt( + exported($curve & "_BN_can_use_addchain_inversion"), + newLit true + ) + else: + curveExtraStmts.add newConstStmt( + exported($curve & "_BN_can_use_addchain_inversion"), + newLit false + ) # end for --------------------------------------------------- result = newStmtList() @@ -177,14 +325,35 @@ macro declareCurves*(curves: untyped): untyped = # const CurveBitSize: array[Curve, int] = ... result.add newConstStmt( - ident("CurveBitSize"), MapCurveBitWidth + exported("CurveBitWidth"), MapCurveBitWidth ) # const CurveFamily: array[Curve, CurveFamily] = ... result.add newConstStmt( - ident("CurveFamilies"), MapCurveFamily + exported("CurveFamilies"), MapCurveFamily ) result.add curveModStmts + result.add curveEllipticStmts result.add curveExtraStmts # echo result.toStrLit() + +macro declareCurves*(curves: untyped): untyped = + ## Parse curve configuration and generates + ## + ## type Curve = enum + ## BN254 + ## ... + ## + ## const CurveBitSize* = array[ + ## BN254: 254, + ## ... + ## ] + ## + ## TODO: Ensure that + ## 1. the modulus is not inlined at runtime to avoid codesize explosion. + ## 2. is not duplicated across compilation modules. + + curves.expectKind(nnkStmtList) + curvesDefinitions.parseCurveDecls(curves) + result = curvesDefinitions.genMainConstants() diff --git a/constantine/elliptic/README.md b/constantine/elliptic/README.md index cdb900e..b404220 100644 --- a/constantine/elliptic/README.md +++ b/constantine/elliptic/README.md @@ -2,8 +2,137 @@ This folder will hold the implementation of elliptic curves arithmetic +## Terminology + +### Coordinates system + +The point P of the curve `y² = x³ + ax + b)` have the following coordinate: + +- `(x, y)` in the affine coordinate system +- `(X, Y, Z)` with `X = xZ` and `Y = yZ` in the homogeneous projective coordinate system. + The homogeneous projective coordinates will be called projective coordinates from now on. +- `(X, Y, Z)` with `X = xZ²` and `Y = yZ³` in the jacobian projective coordinate system. + The jacobian projective coordinates will be called jacobian coordinates from now on. + +## Operations on a Twist + +Pairings require operation on a twisted curve. Formulas are available +in Costello2009 and Ionica2017 including an overview of which coordinate system (affine, homogeneous projective or jacobian) is the most efficient for the Miller loop. + +In particular for sextic twist (applicable to BN and BLS12 families), the projective coordinates are more efficient while for quadratic and quartic twists, jacobian coordinates ar emore efficient. + +When the addition law requires the `a` or `b` parameter from the curve Scott2009 and Nogami2010 give the parameter relevant to the twisted curve for the M-Twist (multiplication by non-residue) or D-Twist (Division by non-residue) cases. + +## Side-Channel resistance + +### Scalar multiplication + +Scalar multiplication of a point `P` by a scalar `k` and denoted `R = [k]P` (or `R = kP`) +is a critical operation to make side-channel resistant. + +Elliptic Curve-based signature scheme indeed rely on the fact that computing the inverse of elliptic scalar multiplication is intractable to produce a public key `[k]P` from +the secret (integer) key `k`. The problem is called ECDLP, Elliptic Curve Discrete Logarithm Problem in the litterature. + +Scalar multiplication for elliptic curve presents the same constant-time challenge as square-and-multiply, a naive implementation will leak every bit of the secret key: +``` + N ← P + R ← 0 + for i from 0 to log2(k) do + if k.bit(i) == 1 then + Q ← point_add(Q, N) + N ← point_double(N) + return Q +``` + +### Point Addition and Doubling + +#### Exceptions in elliptic curve group laws. + +For an elliptic curve in short Weierstrass form: `y² = x³ + ax + b)` + +The equation for elliptic curve addition is in affine (x, y) coordinates: + +``` +P + Q = R +(Px, Py) + (Qx, Qy) = (Rx, Ry) + +with +Rx = λ² - Px - Qx +Ry = λ(Px - Rx) - Py +``` +but in the case of addition +``` +λ = (Qy - Py) / (Px - Qx) +``` +which is undefined for P == Q or P == -Q (as `-(x, y) = (x, -y)`) + +the doubling formula uses the slope of the tangent at the limit + +``` +λ = (3 Px² + a) / (2 Px) +``` + +So we have to take into account 2 special-cases. + +Furthermore when using (homogeneous) projective or jacobian coordinates, most formulæ +needs to special-case the point at infinity. + +#### Dealing with exceptions + +An addition formula that works for both addition and doubling (adding the same point) is called **unified**. +An addition formula that works for all inputs including adding infinity point or the same point is called **complete** or **exception-free**. + +Abarúa2019 highlight several attacks, their defenses, counterattacks and counterdefenses +on elliptic curve implementations. + +We use the complete addition law from Renes2015 for projective coordinates, note that the prime order requirement can be relaxed to odd order according to the author. + +We use the complete addition law from Bos2014 for Jacobian coordinates, note that there is a prime order requirement. + ## References - Pairing-Friendly Curves\ (Draft, expires May 4, 2020)\ https://tools.ietf.org/html/draft-irtf-cfrg-pairing-friendly-curves-00#section-2.1 + +- Survey for Performance & Security Problems of Passive Side-channel Attacks Countermeasures in ECC\ + Rodrigo Abarúa, Claudio Valencia, and Julio López, 2019\ + https://eprint.iacr.org/2019/010 + +- Completing the Complete ECC Formulae with Countermeasures + Łukasz Chmielewski, Pedro Maat Costa Massolino, Jo Vliegen, Lejla Batina and Nele Mentens, 2017\ + https://www.mdpi.com/2079-9268/7/1/3/pdf + +- Pairings\ + Chapter 3 of Guide to Pairing-Based Cryptography\ + Sorina Ionica, Damien Robert, 2017\ + https://www.math.u-bordeaux.fr/~damienrobert/csi2018/pairings.pdf + +- Complete addition formulas for prime order elliptic curves\ + Joost Renes and Craig Costello and Lejla Batina, 2015\ + https://eprint.iacr.org/2015/1060 + +- Selecting Elliptic Curves for Cryptography: An Efficiency and Security Analysis\ + Joppe W. Bos and Craig Costello and Patrick Longa and Michael Naehrig, 2014\ + https://eprint.iacr.org/2014/130 + https://www.imsc.res.in/~ecc14/slides/costello.pdf + +- State-of-the-art of secure ECC implementations:a survey on known side-channel attacks and countermeasures\ + Junfeng Fan,XuGuo, Elke De Mulder, Patrick Schaumont, Bart Preneel and Ingrid Verbauwhede, 2010 + https://www.esat.kuleuven.be/cosic/publications/article-1461.pdf + +- Efficient Pairings on Twisted Elliptic Curve\ + Yasuyuki Nogami, Masataka Akane, Yumi Sakemi and Yoshitaka Morikawa, 2010\ + https://www.researchgate.net/publication/221908359_Efficient_Pairings_on_Twisted_Elliptic_Curve + +- A note on twists for pairing friendly curves\ + Michael Scott, 2009\ + http://indigo.ie/~mscott/twists.pdf + +- Faster Pairing Computations on Curves withHigh-Degree Twists\ + Craig Costello and Tanja Lange and Michael Naehrig, 2009, + https://eprint.iacr.org/2009/615 + +- Complete systems of Two Addition Laws for Elliptic Curve\ + Bosma and Lenstra, 1995 + http://www.mat.uniroma3.it/users/pappa/CORSI/CR510_13_14/BosmaLenstra.pdf diff --git a/constantine/elliptic/ec_weierstrass_affine.nim b/constantine/elliptic/ec_weierstrass_affine.nim new file mode 100644 index 0000000..88cd1f6 --- /dev/null +++ b/constantine/elliptic/ec_weierstrass_affine.nim @@ -0,0 +1,53 @@ +# 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 + ../primitives, + ../config/[common, curves], + ../arithmetic, + ../towers, + ../io/io_bigints + +func curve_eq_rhs*[F](y2: var F, x: F) = + ## Compute the curve equation right-hand-side from field element `x` + ## i.e. `y²` in `y² = x³ + a x + b` + ## or on sextic twists for pairing curves `y² = x³ + b/µ` or `y² = x³ + µ b` + ## with µ the chosen sextic non-residue + + var t{.noInit.}: F + t.square(x) + t *= x + + # No need to precompute `b` in 𝔽p or 𝔽p² or `b/µ` `µ b` + # This procedure is not use in perf critcal situation like signing/verification + # but for testing to quickly create points on a curve. + y2 = F.fromBig F.C.matchingBigInt().fromUint F.C.getCoefB() + when F is Fp2: + when F.C.getSexticTwist() == D_Twist: + y2 /= F.C.get_SNR_Fp2() + elif F.C.getSexticTwist() == M_Twist: + y2 *= F.C.get_SNR_Fp2() + else: + {.error: "Only twisted curves are supported on extension field 𝔽p²".} + + y2 += t + + when F.C.getCoefA() != 0: + t = x + t *= F.C.getCoefA() + y2 += t + +func isOnCurve*[F](x, y: F): CTBool[Word] = + ## Returns true if the (x, y) coordinates + ## represents a point of the elliptic curve + + var y2, rhs {.noInit.}: F + y2.square(y) + rhs.curve_eq_rhs(x) + + return y2 == rhs diff --git a/constantine/elliptic/ec_weierstrass_projective.nim b/constantine/elliptic/ec_weierstrass_projective.nim new file mode 100644 index 0000000..271c7e9 --- /dev/null +++ b/constantine/elliptic/ec_weierstrass_projective.nim @@ -0,0 +1,193 @@ +# 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 + ../primitives, + ../config/[common, curves], + ../arithmetic, + ../towers, + ./ec_weierstrass_affine + +# ############################################################ +# +# Elliptic Curve in Weierstrass form +# with Projective Coordinates +# +# ############################################################ + +type ECP_SWei_Proj*[F] = object + ## Elliptic curve point for a curve in Short Weierstrass form + ## y² = x³ + a x + b + ## + ## over a field F + ## + ## in projective coordinates (X, Y, Z) + ## corresponding to (x, y) with X = xZ and Y = yZ + ## + ## Note that projective coordinates are not unique + x, y, z: F + +func `==`*[F](P, Q: ECP_SWei_Proj[F]): CTBool[Word] = + ## Constant-time equality check + # Reminder: the representation is not unique + + var a{.noInit.}, b{.noInit.}: F + + a.prod(P.x, Q.z) + b.prod(Q.x, P.z) + result = a == b + + a.prod(P.y, Q.z) + b.prod(Q.y, P.z) + result = result and a == b + +func isInf*(P: ECP_SWei_Proj): CTBool[Word] = + ## Returns true if P is an infinity point + ## and false otherwise + ## + ## Note: the projective coordinates equation is + ## Y²Z = X³ + aXZ² + bZ³ + ## A "zero" point is any point with coordinates X and Z = 0 + ## Y can be anything + result = P.x.isZero() and P.z.isZero() + +func setInf*(P: var ECP_SWei_Proj) = + ## Set ``P`` to infinity + P.x.setZero() + P.y.setOne() + P.z.setZero() + +func trySetFromCoordsXandZ*[F](P: var ECP_SWei_Proj[F], x, z: F): CTBool[Word] = + ## Try to create a point the elliptic curve + ## Y²Z = X³ + aXZ² + bZ³ (projective coordinates) + ## y² = x³ + a x + b (affine coordinate) + ## return true and update `P` if `x` leads to a valid point + ## return false otherwise, in that case `P` is undefined. + ## + ## Note: Dedicated robust procedures for hashing-to-curve + ## will be provided, this is intended for testing purposes. + P.y.curve_eq_rhs(x) + # TODO: supports non p ≡ 3 (mod 4) modulus like BLS12-377 + result = sqrt_if_square_p3mod4(P.y) + + P.x.prod(x, z) + P.y *= z + P.z = z + +func trySetFromCoordX*[F](P: var ECP_SWei_Proj[F], x: F): CTBool[Word] = + ## Try to create a point the elliptic curve + ## y² = x³ + a x + b (affine coordinate) + ## + ## The `Z` coordinates is set to 1 + ## + ## return true and update `P` if `x` leads to a valid point + ## return false otherwise, in that case `P` is undefined. + ## + ## Note: Dedicated robust procedures for hashing-to-curve + ## will be provided, this is intended for testing purposes. + P.y.curve_eq_rhs(x) + # TODO: supports non p ≡ 3 (mod 4) modulus like BLS12-377 + result = sqrt_if_square_p3mod4(P.y) + P.x = x + P.z.setOne() + +func neg*(P: var ECP_SWei_Proj) = + ## Negate ``P`` + P.y.neg(P.y) + +func sum*[F]( + r: var ECP_SWei_Proj[F], + P, Q: ECP_SWei_Proj[F] + ) = + ## Elliptic curve point addition for Short Weierstrass curves in projective coordinate + ## Short Weierstrass curves have the following equation in projective coordinates + ## Y²Z = X³ + aXZ² + bZ³ + ## from the affine equation + ## y² = x³ + a x + b + ## + ## ``r`` is initialized/overwritten with the sum + ## + ## Implementation is constant-time, in particular it will not expose + ## that P == Q or P == -Q or P or Q are the infinity points + ## to simple side-channel attacks (SCA) + ## This is done by using a "complete" or "exception-free" addition law. + ## + ## This requires the order of the curve to be odd + # + # Implementation: + # Algorithms 1 (generic case), 4 (a == -3), 7 (a == 0) of + # Complete addition formulas for prime order elliptic curves\ + # Joost Renes and Craig Costello and Lejla Batina, 2015\ + # https://eprint.iacr.org/2015/1060 + # + # with the indices 1 corresponding to ``P``, 2 to ``Q`` and 3 to the result ``r`` + # + # X3 = (X1 Y2 + X2 Y1)(Y1 Y2 - a(X1 Z2 + X2 Z1) - 3b Z1 Z2) + # - (Y1 Z2 + Y2 Z1)(a X1 X2 + 3b(X1 Z2 + X2 Z1) - a² Z1 Z2) + # Y3 = (3 X1 X2 + a Z1 Z2)(a X1 X2 + 3b (X1 Z2 + X2 Z1) - a² Z1 Z2) + # + (Y1 Y2 + a (X1 Z2 + X2 Z1) + 3b Z1 Z2)(Y1 Y2 - a(X1 Z2 + X2 Z1) - 3b Z1 Z2) + # Z3 = (Y1 Z2 + Y2 Z1)(Y1 Y2 + a(X1 Z2 + X2 Z1) + 3b Z1 Z2) + (X1 Y2 + X2 Y1)(3 X1 X2 + a Z1 Z2) + + # TODO: static doAssert odd order + var t0 {.noInit.}, t1 {.noInit.}, t2 {.noInit.}, t3 {.noInit.}, t4 {.noInit.}: F + const b3 = 3 * F.C.getCoefB() + + when F.C.getCoefA() == 0: + # Algorithm 7 for curves: y² = x³ + b + # 12M + 2 mul(3b) + 19A + # X3 = (X1 Y2 + X2 Y1)(Y1 Y2 − 3b Z1 Z2) + # − 3b(Y1 Z2 + Y2 Z1)(X1 Z2 + X2 Z1) + # Y3 = (Y1 Y2 + 3b Z1 Z2)(Y1 Y2 − 3b Z1 Z2) + # + 9b X1 X2 (X1 Z2 + X2 Z1) + # Z3= (Y1 Z2 + Y2 Z1)(Y1 Y2 + 3b Z1 Z2) + 3 X1 X2 (X1 Y2 + X2 Y1) + t0.prod(P.x, Q.x) # 1. t0 <- X1 X2 + t1.prod(P.y, Q.y) # 2. t1 <- Y1 Y2 + t2.prod(P.z, Q.z) # 3. t2 <- Z1 Z2 + t3.sum(P.x, P.y) # 4. t3 <- X1 + Y1 + t4.sum(Q.x, Q.y) # 5. t4 <- X2 + Y2 + t3 *= t4 # 6. t3 <- t3 * t4 + t4.sum(t0, t1) # 7. t4 <- t0 + t1 + t3 -= t4 # 8. t3 <- t3 - t4 t3 = (X1 + Y1)(X2 + Y2) - (X1 X2 + Y1 Y2) = X1.Y2 + X2.Y1 + when F is Fp2 and F.C.getSexticTwist() == D_Twist: + t3 *= F.sexticNonResidue() + t4.sum(P.y, P.z) # 9. t4 <- Y1 + Z1 + r.x.sum(Q.y, Q.z) # 10. X3 <- Y2 + Z2 + t4 *= r.x # 11. t4 <- t4 X3 + r.x.sum(t1, t2) # 12. X3 <- t1 + t2 X3 = Y1 Y2 + Z1 Z2 + t4 -= r.x # 13. t4 <- t4 - X3 t4 = (Y1 + Z1)(Y2 + Z2) - (Y1 Y2 + Z1 Z2) = Y1 Z2 + Y2 Z1 + when F is Fp2 and F.C.getSexticTwist() == D_Twist: + t4 *= F.sexticNonResidue() + r.x.sum(P.x, P.z) # 14. X3 <- X1 + Z1 + r.y.sum(Q.x, Q.z) # 15. Y3 <- X2 + Z2 + r.x *= r.y # 16. X3 <- X3 Y3 X3 = (X1 Z1)(X2 Z2) + r.y.sum(t0, t2) # 17. Y3 <- t0 + t2 Y3 = X1 X2 + Z1 Z2 + r.y.diff(r.x, r.y) # 18. Y3 <- X3 - Y3 Y3 = (X1 + Z1)(X2 + Z2) - (X1 X2 + Z1 Z2) = X1 Z2 + X2 Z1 + when F is Fp2 and F.C.getSexticTwist() == D_Twist: + t0 *= F.sexticNonResidue() + t1 *= F.sexticNonResidue() + r.x.double(t0) # 19. X3 <- t0 + t0 X3 = 2 X1 X2 + t0 += r.x # 20. t0 <- X3 + t0 t0 = 3 X1 X2 + t2 *= b3 # 21. t2 <- b3 t2 t2 = 3b Z1 Z2 + when F is Fp2 and F.C.getSexticTwist() == M_Twist: + t2 *= F.sexticNonResidue() + r.z.sum(t1, t2) # 22. Z3 <- t1 + t2 Z3 = Y1 Y2 + 3b Z1 Z2 + t1 -= t2 # 23. t1 <- t1 - t2 t1 = Y1 Y2 - 3b Z1 Z2 + r.y *= b3 # 24. Y3 <- b3 Y3 Y3 = 3b(X1 Z2 + X2 Z1) + when F is Fp2 and F.C.getSexticTwist() == M_Twist: + r.y *= F.sexticNonResidue() + r.x.prod(t4, r.y) # 25. X3 <- t4 Y3 X3 = 3b(Y1 Z2 + Y2 Z1)(X1 Z2 + X2 Z1) + t2.prod(t3, t1) # 26. t2 <- t3 t1 t2 = (X1 Y2 + X2 Y1) (Y1 Y2 - 3b Z1 Z2) + r.x.diff(t2, r.x) # 27. X3 <- t2 - X3 X3 = (X1 Y2 + X2 Y1) (Y1 Y2 - 3b Z1 Z2) - 3b(Y1 Z2 + Y2 Z1)(X1 Z2 + X2 Z1) + r.y *= t0 # 28. Y3 <- Y3 t0 Y3 = 9b X1 X2 (X1 Z2 + X2 Z1) + t1 *= r.z # 29. t1 <- t1 Z3 t1 = (Y1 Y2 - 3b Z1 Z2)(Y1 Y2 + 3b Z1 Z2) + r.y += t1 # 30. Y3 <- t1 + Y3 Y3 = (Y1 Y2 + 3b Z1 Z2)(Y1 Y2 - 3b Z1 Z2) + 9b X1 X2 (X1 Z2 + X2 Z1) + t0 *= t3 # 31. t0 <- t0 t3 t0 = 3 X1 X2 (X1.Y2 + X2.Y1) + r.z *= t4 # 32. Z3 <- Z3 t4 Z3 = (Y1 Y2 + 3b Z1 Z2)(Y1 Z2 + Y2 Z1) + r.z += t0 # 33. Z3 <- Z3 + t0 Z3 = (Y1 Z2 + Y2 Z1)(Y1 Y2 + 3b Z1 Z2) + 3 X1 X2 (X1.Y2 + X2.Y1) + else: + {.error: "Not implemented.".} diff --git a/constantine/towers.nim b/constantine/towers.nim new file mode 100644 index 0000000..24712df --- /dev/null +++ b/constantine/towers.nim @@ -0,0 +1,21 @@ +# 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 + tower_field_extensions/[ + abelian_groups, + fp2_complex, + fp6_1_plus_i, + fp12_quad_fp6 + ] + +export + abelian_groups, + fp2_complex, + fp6_1_plus_i, + fp12_quad_fp6 diff --git a/helpers/prng.nim b/helpers/prng.nim index d5bea9a..ac4ab69 100644 --- a/helpers/prng.nim +++ b/helpers/prng.nim @@ -8,7 +8,8 @@ import ../constantine/arithmetic/bigints, - ../constantine/config/[common, curves] + ../constantine/config/[common, curves], + ../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective] # ############################################################ # @@ -75,11 +76,8 @@ func next(rng: var RngState): uint64 = rng.s[7] = rotl(rng.s[7], 21); -# ############################################################ -# -# Create a random BigInt or Field element -# -# ############################################################ +# BigInts and Fields +# ------------------------------------------------------------ func random[T](rng: var RngState, a: var T, C: static Curve) {.noInit.}= ## Recursively initialize a BigInt or Field element @@ -97,6 +95,44 @@ func random[T](rng: var RngState, a: var T, C: static Curve) {.noInit.}= for field in fields(a): rng.random(field, C) +# Elliptic curves +# ------------------------------------------------------------ + +func random[F](rng: var RngState, a: var ECP_SWei_Proj[F]) = + ## Initialize a random curve point with Z coordinate == 1 + + var fieldElem {.noInit.}: F + var success = CtFalse + + while not bool(success): + # Euler's criterion: there are (p-1)/2 squares in a field with modulus `p` + # so we have a probability of ~0.5 to get a good point + rng.random(fieldElem, F.C) + success = trySetFromCoordX(a, fieldElem) + +func random_with_randZ[F](rng: var RngState, a: var ECP_SWei_Proj[F]) = + ## Initialize a random curve point with Z coordinate being random + + var Z{.noInit.}: F + rng.random(Z, F.C) # If Z is zero, X will be zero and that will be an infinity point + + var fieldElem {.noInit.}: F + var success = CtFalse + + while not bool(success): + rng.random(fieldElem, F.C) + success = trySetFromCoordsXandZ(a, fieldElem, Z) + +# Generic over any supported type +# ------------------------------------------------------------ + func random*(rng: var RngState, T: typedesc): T = - ## Create a random Field or Extension Field Element - rng.random(result, T.C) + ## Create a random Field or Extension Field or Curve Element + when T is ECP_SWei_Proj: + rng.random(result) + else: + rng.random(result, T.C) + +func random_with_randZ*(rng: var RngState, T: typedesc[ECP_SWei_Proj]): T = + ## Create a random curve element with a random Z coordinate + rng.random_with_randZ(result) diff --git a/tests/test_ec_weierstrass_projective_g1.nim b/tests/test_ec_weierstrass_projective_g1.nim new file mode 100644 index 0000000..94421fd --- /dev/null +++ b/tests/test_ec_weierstrass_projective_g1.nim @@ -0,0 +1,142 @@ +# 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 + unittest, times, random, + # Internals + ../constantine/config/[common, curves], + ../constantine/arithmetic, + ../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective], + # Test utilities + ../helpers/prng + +const Iters = 128 + +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 xoshiro512** seed: ", seed + +# Import: wrap in elliptic curve tests in small procedures +# otherwise they will become globals, +# and will create binary size issues. +# Also due to Nim stack scanning, +# having too many elements on the stack (a couple kB) +# will significantly slow down testing (100x is possible) + +suite "Elliptic curve in Short Weierstrass form y² = x³ + a x + b with projective coordinates (X, Y, Z): Y²Z = X³ + aXZ² + bZ³ i.e. X = xZ, Y = yZ": + test "The infinity point is the neutral element w.r.t. to EC addition": + proc test(F: typedesc, randZ: static bool) = + var inf {.noInit.}: ECP_SWei_Proj[F] + inf.setInf() + check: bool inf.isInf() + + for _ in 0 ..< Iters: + var r{.noInit.}: ECP_SWei_Proj[F] + when randZ: + let P = rng.random_with_randZ(ECP_SWei_Proj[F]) + else: + let P = rng.random(ECP_SWei_Proj[F]) + + r.sum(P, inf) + check: bool(r == P) + + r.sum(inf, P) + check: bool(r == P) + + test(Fp[BLS12_381], randZ = false) + test(Fp[BLS12_381], randZ = true) + + test "Adding opposites gives an infinity point": + proc test(F: typedesc, randZ: static bool) = + for _ in 0 ..< Iters: + var r{.noInit.}: ECP_SWei_Proj[F] + when randZ: + let P = rng.random_with_randZ(ECP_SWei_Proj[F]) + else: + let P = rng.random(ECP_SWei_Proj[F]) + var Q = P + Q.neg() + + r.sum(P, Q) + check: bool r.isInf() + + r.sum(Q, P) + check: bool r.isInf() + + test(Fp[BLS12_381], randZ = false) + test(Fp[BLS12_381], randZ = true) + + test "EC add is commutative": + proc test(F: typedesc, randZ: static bool) = + for _ in 0 ..< Iters: + var r0{.noInit.}, r1{.noInit.}: ECP_SWei_Proj[F] + when randZ: + let P = rng.random_with_randZ(ECP_SWei_Proj[F]) + let Q = rng.random_with_randZ(ECP_SWei_Proj[F]) + else: + let P = rng.random(ECP_SWei_Proj[F]) + let Q = rng.random(ECP_SWei_Proj[F]) + + r0.sum(P, Q) + r1.sum(Q, P) + check: bool(r0 == r1) + + test(Fp[BLS12_381], randZ = false) + test(Fp[BLS12_381], randZ = true) + + test "EC add is associative": + proc test(F: typedesc, randZ: static bool) = + for _ in 0 ..< Iters: + when randZ: + let a = rng.random_with_randZ(ECP_SWei_Proj[F]) + let b = rng.random_with_randZ(ECP_SWei_Proj[F]) + let c = rng.random_with_randZ(ECP_SWei_Proj[F]) + else: + let a = rng.random(ECP_SWei_Proj[F]) + let b = rng.random(ECP_SWei_Proj[F]) + let c = rng.random(ECP_SWei_Proj[F]) + + var tmp1{.noInit.}, tmp2{.noInit.}: ECP_SWei_Proj[F] + + # r0 = (a + b) + c + tmp1.sum(a, b) + tmp2.sum(tmp1, c) + let r0 = tmp2 + + # r1 = a + (b + c) + tmp1.sum(b, c) + tmp2.sum(a, tmp1) + let r1 = tmp2 + + # r2 = (a + c) + b + tmp1.sum(a, c) + tmp2.sum(tmp1, b) + let r2 = tmp2 + + # r3 = a + (c + b) + tmp1.sum(c, b) + tmp2.sum(a, tmp1) + let r3 = tmp2 + + # r4 = (c + a) + b + tmp1.sum(c, a) + tmp2.sum(tmp1, b) + let r4 = tmp2 + + # ... + + check: + bool(r0 == r1) + bool(r0 == r2) + bool(r0 == r3) + bool(r0 == r4) + + test(Fp[BLS12_381], randZ = false) + test(Fp[BLS12_381], randZ = true)