From 99a4d8cb19abb0489f21b4a703176ca4815cc13b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mamy=20Andr=C3=A9-Ratsimbazafy?= Date: Sat, 6 Jun 2020 17:48:13 +0200 Subject: [PATCH 01/19] Add MultiScalar recoding from "Efficient and Secure Algorithms for GLV-Based Scalar Multiplication" by Faz et al --- constantine/arithmetic/bigints.nim | 23 ++ .../elliptic/ec_endomorphism_accel.nim | 262 ++++++++++++++++++ constantine/primitives/constant_time.nim | 2 +- 3 files changed, 286 insertions(+), 1 deletion(-) create mode 100644 constantine/elliptic/ec_endomorphism_accel.nim diff --git a/constantine/arithmetic/bigints.nim b/constantine/arithmetic/bigints.nim index 26280d05f..7c705837a 100644 --- a/constantine/arithmetic/bigints.nim +++ b/constantine/arithmetic/bigints.nim @@ -182,6 +182,11 @@ func add*(a: var BigInt, b: SecretWord): SecretBool = ## Returns the carry (SecretBool) add(a.limbs, b) +func `+=`*(a: var BigInt, b: SecretWord) = + ## Constant-time in-pace addition + ## Discards the carry + discard add(a.limbs, b) + func sub*(a: var BigInt, b: BigInt): SecretBool = ## Constant-time in-place substraction ## Returns the borrow @@ -231,6 +236,24 @@ func shiftRight*(a: var BigInt, k: int) = ## k MUST be less than the base word size (2^31 or 2^63) a.limbs.shiftRight(k) +func bit*[bits: static int](a: BigInt[bits], index: int): Ct[uint8] = + ## Access an individual bit of `a` + ## Bits are accessed as-if the bit representation is bigEndian + ## for a 8-bit "big-integer" we have + ## (b7, b6, b5, b4, b3, b2, b1, b0) + ## for a 256-bit big-integer + ## (b255, b254, ..., b1, b0) + const SlotShift = log2(WordBitWidth.uint32) + const SelectMask = WordBitWidth - 1 + const BitMask = SecretWord 1 + + let slot = a.limbs[index shr SlotShift] # LimbEndianness is littleEndian + result = ct[uint8](slot shr (index and SelectMask) and BitMask) + +func bit0*(a: BigInt): Ct[uint8] = + ## Access the least significant bit + ct[uint8](a.limbs[0] and SecretWord(1)) + # ############################################################ # # Modular BigInt diff --git a/constantine/elliptic/ec_endomorphism_accel.nim b/constantine/elliptic/ec_endomorphism_accel.nim new file mode 100644 index 000000000..9f32d50e6 --- /dev/null +++ b/constantine/elliptic/ec_endomorphism_accel.nim @@ -0,0 +1,262 @@ +# 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 + typetraits, + # Internal + ../primitives, + ../config/[common, curves], + ../arithmetic, + ../towers, + ./ec_weierstrass_affine, + ./ec_weierstrass_projective + +# ############################################################ +# +# Endomorphism acceleration for +# Scalar Multiplication +# +# ############################################################ +# +# This files implements endomorphism-acceleration of scalar multiplication +# using: +# - GLV endomorphism on G1 (Gallant-Lambert-Vanstone) +# - GLV and GLS endomorphisms on G2 (Galbraith-Lin-Scott) +# - NAF recoding (windowed Non-Adjacent-Form) + + +# Secret scalar + dynamic point +# ---------------------------------------------------------------- +# +# This section targets the case where the scalar multiplication [k]P +# involves: +# - a secret scalar `k`, hence requiring constant-time operations +# - a dynamic `P` +# +# For example signing a message +# +# When P is known ahead of time (for example it's the generator) +# We can precompute the point decomposition with plain scalar multiplication +# and not require a fast endomorphism. +# (For example generating a public-key) + +type + Recoded[LengthInDigits: static int] = distinct array[LengthInDigits, byte] + GLV_SAC[M, LengthInDigits: static int] = array[M, Recoded[LengthInDigits]] + ## GLV-Based Sign-Aligned-Column representation + ## see Faz-Hernandez, 2013 + ## + ## (i) Length of every sub-scalar is fixed and given by + ## l = ⌈log2 r/m⌉ + 1 where r is the prime subgroup order + ## and m the number of dimensions of the GLV endomorphism + ## (ii) Exactly one subscalar which should be odd + ## is expressed by a signed nonzero representation + ## with all digits ∈ {1, −1} + ## (iii) Other subscalars have digits ∈ {0, 1, −1} + ## + ## We pack the representation, using 2 bits per digit: + ## 0 = 0b00 + ## 1 = 0b01 + ## -1 = 0b11 + ## + ## This means that GLV_SAC uses twice the size of a canonical integer + ## + ## Digit-Endianness is bigEndian + + MultiScalar[M, LengthInBits: static int] = array[M, BigInt[LengthInBits]] + ## Decomposition of a secret scalar in multiple scalars + +const + BitSize = 2 + Shift = 2 # log2(4) - we can store 4 digit per byte + ByteMask = 3 # we need (mod 4) to access a packed bytearray + DigitMask = 0b11 # Digits take 2-bit + +# template signExtend_2bit(recoded: byte): int8 = +# ## We need to extend: +# ## - 0b00 to 0b0000_0000 ( 0) +# ## - 0b01 to 0b0000_0001 ( 1) +# ## - 0b11 to 0b1111_1111 (-1) +# ## +# ## This can be done by shifting left to have +# ## - 0b00 to 0b0000_0000 +# ## - 0b01 to 0b0100_0000 +# ## - 0b11 to 0b1100_0000 +# ## +# ## And then an arithmetic right shift (SAR) +# ## +# ## However there is no builtin SAR +# ## we can get it in C by right-shifting +# ## with the main compilers/platforms +# ## (GCC, Clang, MSVC, ...) +# ## but this is implementation defined behavior +# ## Nim `ashr` uses C signed right shifting +# ## +# ## We could check the compiler to ensure we only use +# ## well documented behaviors: https://gcc.gnu.org/onlinedocs/gcc/Integers-implementation.html#Integers-implementation +# ## but if we can avoid that altogether in a crypto library +# ## +# ## Instead we use signed bitfield which are automatically sign-extended +# ## in a portable way as sign extension is automatic for builtin types + +type + SignExtender = object + ## Uses C builtin types sign extension to sign extend 2-bit to 8-bit + ## in a portable way as sign extension is automatic for builtin types + ## http://graphics.stanford.edu/~seander/bithacks.html#FixedSignExtend + digit {.bitsize:2.}: int8 + + +proc `[]`(recoding: Recoded, + digitIdx: int): int8 {.inline.}= + ## 0 <= digitIdx < LengthInDigits + ## returns digit ∈ {0, 1, −1} + const len = Recoded.LengthInDigits + assert digitIdx < len + + let slot = distinctBase(recoding)[ + len-1 - (digitIdx shr Shift) + ] + let recoded = slot shr (BitSize*(digitIdx and ByteMask)) and DigitMask + var signExtender: SignExtender + # Hack with C assignment that return values + {.emit: [result, " = ", signExtender, ".digit = ", recoded, ";"].} + # " # Fix highlighting bug in VScode + + +proc `[]=`(recoding: var Recoded, + digitIdx: int, value: int8) {.inline.}= + ## 0 <= digitIdx < LengthInDigits + ## returns digit ∈ {0, 1, −1} + ## This is write-once + const len = Recoded.LengthInDigits + assert digitIdx < Recoded.LengthInDigits + + let slot = distinctBase(recoding)[ + len-1 - (digitIdx shr Shift) + ].addr + + let shifted = byte((value and DigitMask) shl (BitSize*(digitIdx and ByteMask))) + slot[] = slot[] or shifted + + +func nDimMultiScalarRecoding[M, LengthInBits, LengthInDigits: static int]( + dst: var GLV_SAC[M, LengthInDigits], + src: MultiScalar[M, LengthInBits] + ) = + ## This recodes N scalar for GLV multi-scalar multiplication + ## with side-channel resistance. + ## + ## Precondition src[0] is odd + # + # - Efficient and Secure Algorithms for GLV-Based Scalar + # Multiplication and their Implementation on GLV-GLS + # Curves (Extended Version) + # Armando Faz-Hernández, Patrick Longa, Ana H. Sánchez, 2013 + # https://eprint.iacr.org/2013/158.pdf + # + # Algorithm 1 Protected Recoding Algorithm for the GLV-SAC Representation. + # ------------------------------------------------------------------------ + # + # Input: m l-bit positive integers kj = (kj_l−1, ..., kj_0)_2 for + # 0 ≤ j < m, an odd “sign-aligner” kJ ∈ {kj}^m, where + # l = ⌈log2 r/m⌉ + 1, m is the GLV dimension and r is + # the prime subgroup order. + # Output: (bj_l−1 , ..., bj_0)GLV-SAC for 0 ≤ j < m, where + # bJ_i ∈ {1, −1}, and bj_i ∈ {0, bJ_i} for 0 ≤ j < m with + # j != J. + # ------------------------------------------------------------------------ + # + # 1: bJ_l-1 = 1 + # 2: for i = 0 to (l − 2) do + # 3: bJ_i = 2kJ_i+1 - 1 + # 4: for j = 0 to (m − 1), j != J do + # 5: for i = 0 to (l − 1) do + # 6: bj_i = bJ_i kj_0 + # 7: kj = ⌊kj/2⌋ − ⌊bj_i/2⌋ + # 8: return (bj_l−1 , . . . , bj_0)_GLV-SAC for 0 ≤ j < m. + # + # - Guide to Pairing-based Cryptography + # Chapter 6: Scalar Multiplication and Exponentiation in Pairing Groups + # Joppe Bos, Craig Costello, Michael Naehrig + # + # We choose kJ = k0 + # + # Implementation strategy and points of attention + # - The subscalars kj must support extracting the least significant bit + # - The subscalars kj must support floor division by 2 + # For that floored division, kj is 0 or positive + # - The subscalars kj must support individual bit accesses + # - The subscalars kj must support addition by a small value (0 or 1) + # Hence we choose to use our own BigInt representation. + # + # - The digit bji must support floor division by 2 + # For that floored division, bji may be negative!!! + # In particular floored division of -1 is -1 not 0. + # This means that arithmetic right shift must be used instead of logical right shift + static: doAssert LengthInDigits == LengthInBits + 1 + # assert src[0].isOdd - Only happen on implementation error, we don't want to leak a single bit + + var k = src # Keep the source multiscalar in registers + template b: untyped {.dirty.} = dst + + b[0][LengthInDigits-1] = 1 + for i in 0 .. LengthInDigits-2: + b[0][i] = 2 * k[0].bit(i+1).int8 - 1 + for j in 1 .. M-1: + for i in 0 .. LengthInDigits-1: + let bji = b[0][i] * k[j].bit0.int8 + b[j][i] = bji + # In the following equation + # kj = ⌊kj/2⌋ − ⌊bj_i/2⌋ + # We have ⌊bj_i/2⌋ (floor division) + # = -1 if bj_i == -1 + # = 0 if bj_i ∈ {0, 1} + # So we turn that statement in an addition + # by the opposite + k[j].div2() + k[j] += SecretWord -bji.ashr(1) + + +# Sanity checks +# ---------------------------------------------------------------- + +when isMainModule: + import ../io/io_bigints + + + proc toString(glvSac: GLV_SAC): string = + for j in 0 ..< glvSac.M: + result.add "k" & $j & ": [" + for i in countdown(glvSac.LengthInDigits-1, 0): + result.add " " & (block: + case glvSac[j][i] + of -1: "1\u{0305}" + of 0: "0" + of 1: "1" + else: + raise newException(ValueError, "Unexpected encoded value: " & $glvSac[j][i]) + ) # " # Unbreak VSCode highlighting bug + result.add " ]\n" + + + proc main() = + var k: MultiScalar[4, 4] + var kRecoded: GLV_SAC[4, 5] + + k[0].fromUint(11) + k[1].fromUint(6) + k[2].fromuint(14) + k[3].fromUint(3) + + kRecoded.nDimMultiScalarRecoding(k) + + echo kRecoded.toString() + + main() diff --git a/constantine/primitives/constant_time.nim b/constantine/primitives/constant_time.nim index 1507ef98c..005ee478a 100644 --- a/constantine/primitives/constant_time.nim +++ b/constantine/primitives/constant_time.nim @@ -37,7 +37,7 @@ template cfalse*(T: typedesc[Ct or BaseUint]): auto = else: (CTBool[Ct[T]])(false) -template ct*[T: BaseUint](x: T): Ct[T] = +template ct*[T: BaseUint](x: auto): Ct[T] = (Ct[T])(x) template `$`*[T](x: Ct[T]): string = From eff88238de2a604324451b347b74fdf4138aaaf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mamy=20Andr=C3=A9-Ratsimbazafy?= Date: Sun, 7 Jun 2020 02:18:59 +0200 Subject: [PATCH 02/19] precompute cube root of unity - Add VM precomputation of Fp - workaround upstream bug https://github.com/nim-lang/Nim/issues/14585 --- constantine.nimble | 12 ++ constantine/arithmetic.nim | 1 - constantine/arithmetic/bigints.nim | 43 +----- constantine/arithmetic/finite_fields.nim | 17 +-- constantine/arithmetic/limbs.nim | 15 -- constantine/config/curves.nim | 14 +- constantine/config/curves_declaration.nim | 2 + constantine/config/curves_derived.nim | 26 +++- constantine/config/curves_parser.nim | 16 ++- .../precomputed.nim => config/precompute.nim} | 130 +++++++++++++++++- constantine/config/type_bigint.nim | 53 +++++++ constantine/config/type_fp.nim | 26 ++++ constantine/io/io_bigints.nim | 3 +- constantine/io/io_ec.nim | 1 - constantine/io/io_fields.nim | 2 +- constantine/primitives/constant_time.nim | 5 +- sage/curve_family_bls12.sage | 3 + sage/curve_family_bn.sage | 4 + tests/test_precomputed.nim | 27 ++++ 19 files changed, 315 insertions(+), 85 deletions(-) rename constantine/{arithmetic/precomputed.nim => config/precompute.nim} (78%) create mode 100644 constantine/config/type_bigint.nim create mode 100644 constantine/config/type_fp.nim create mode 100644 tests/test_precomputed.nim diff --git a/constantine.nimble b/constantine.nimble index e71811770..e20b0d584 100644 --- a/constantine.nimble +++ b/constantine.nimble @@ -70,6 +70,9 @@ task test, "Run all tests": test "", "tests/test_finite_fields_vs_gmp.nim" + # Precompute + test "", "tests/test_precomputed" + # Towers of extension fields test "", "tests/test_fp2.nim" test "", "tests/test_fp6.nim" @@ -100,6 +103,9 @@ task test, "Run all tests": test "-d:Constantine32", "tests/test_finite_fields_vs_gmp.nim" + # Precompute + test "-d:Constantine32", "tests/test_precomputed" + # Towers of extension fields test "-d:Constantine32", "tests/test_fp2.nim" test "-d:Constantine32", "tests/test_fp6.nim" @@ -138,6 +144,9 @@ task test_no_gmp, "Run tests that don't require GMP": test "", "tests/test_finite_fields_sqrt.nim" test "", "tests/test_finite_fields_powinv.nim" + # Precompute + test "", "tests/test_precomputed" + # Towers of extension fields test "", "tests/test_fp2.nim" test "", "tests/test_fp6.nim" @@ -164,6 +173,9 @@ task test_no_gmp, "Run tests that don't require GMP": test "-d:Constantine32", "tests/test_finite_fields_sqrt.nim" test "-d:Constantine32", "tests/test_finite_fields_powinv.nim" + # Precompute + test "-d:Constantine32", "tests/test_precomputed" + # Towers of extension fields test "-d:Constantine32", "tests/test_fp2.nim" test "-d:Constantine32", "tests/test_fp6.nim" diff --git a/constantine/arithmetic.nim b/constantine/arithmetic.nim index 7d2c6ca1b..fd1d78c4e 100644 --- a/constantine/arithmetic.nim +++ b/constantine/arithmetic.nim @@ -12,6 +12,5 @@ import arithmetic/[finite_fields, finite_fields_inversion] export - limbs, limbs_modular, limbs_montgomery, bigints, finite_fields, finite_fields_inversion diff --git a/constantine/arithmetic/bigints.nim b/constantine/arithmetic/bigints.nim index 7c705837a..9501cbbba 100644 --- a/constantine/arithmetic/bigints.nim +++ b/constantine/arithmetic/bigints.nim @@ -7,10 +7,12 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - ../config/common, + ../config/[common, type_bigint], ../primitives, ./limbs, ./limbs_montgomery, ./limbs_modular +export BigInt + # ############################################################ # # BigInts @@ -55,41 +57,6 @@ import # having redundant representations that forces costly reductions before multiplications. # https://github.com/mratsim/constantine/issues/15 -func wordsRequired(bits: int): int {.compileTime.} = - ## Compute the number of limbs required - # from the **announced** bit length - (bits + WordBitWidth - 1) div WordBitWidth - -type - BigInt*[bits: static int] = object - ## Fixed-precision big integer - ## - ## - "bits" is the announced bit-length of the BigInt - ## This is public data, usually equal to the curve prime bitlength. - ## - ## - "limbs" is an internal field that holds the internal representation - ## of the big integer. Least-significant limb first. Within limbs words are native-endian. - ## - ## This internal representation can be changed - ## without notice and should not be used by external applications or libraries. - limbs*: array[bits.wordsRequired, SecretWord] - -# For unknown reason, `bits` doesn't semcheck if -# `limbs: Limbs[bits.wordsRequired]` -# with -# `Limbs[N: static int] = distinct array[N, SecretWord]` -# so we don't set Limbs as a distinct type - -debug: - import strutils - - func `$`*(a: BigInt): string = - result = "BigInt[" - result.add $BigInt.bits - result.add "](limbs: " - result.add a.limbs.toString() - result.add ")" - # No exceptions allowed {.push raises: [].} {.push inline.} @@ -248,11 +215,11 @@ func bit*[bits: static int](a: BigInt[bits], index: int): Ct[uint8] = const BitMask = SecretWord 1 let slot = a.limbs[index shr SlotShift] # LimbEndianness is littleEndian - result = ct[uint8](slot shr (index and SelectMask) and BitMask) + result = ct(slot shr (index and SelectMask) and BitMask, uint8) func bit0*(a: BigInt): Ct[uint8] = ## Access the least significant bit - ct[uint8](a.limbs[0] and SecretWord(1)) + ct(a.limbs[0] and SecretWord(1), uint8) # ############################################################ # diff --git a/constantine/arithmetic/finite_fields.nim b/constantine/arithmetic/finite_fields.nim index e46bcc22f..ecab6509f 100644 --- a/constantine/arithmetic/finite_fields.nim +++ b/constantine/arithmetic/finite_fields.nim @@ -26,23 +26,10 @@ import ../primitives, - ../config/[common, curves], + ../config/[common, type_fp, curves], ./bigints, ./limbs_montgomery -type - Fp*[C: static Curve] = object - ## All operations on a field are modulo P - ## P being the prime modulus of the Curve C - ## Internally, data is stored in Montgomery n-residue form - ## with the magic constant chosen for convenient division (a power of 2 depending on P bitsize) - mres*: matchingBigInt(C) - -debug: - func `$`*[C: static Curve](a: Fp[C]): string = - result = "Fp[" & $C - result.add "](" - result.add $a.mres - result.add ')' +export Fp # No exceptions allowed {.push raises: [].} diff --git a/constantine/arithmetic/limbs.nim b/constantine/arithmetic/limbs.nim index 53a36b14a..627595a18 100644 --- a/constantine/arithmetic/limbs.nim +++ b/constantine/arithmetic/limbs.nim @@ -43,21 +43,6 @@ type Limbs*[N: static int] = array[N, SecretWord] ## ## but for unknown reason, it prevents semchecking `bits` -debug: - import strutils - - func toString*(a: Limbs): string = - result = "[" - result.add " 0x" & toHex(BaseType(a[0])) - for i in 1 ..< a.len: - result.add ", 0x" & toHex(BaseType(a[i])) - result.add "])" - - func toHex*(a: Limbs): string = - result = "0x" - for i in countdown(a.len-1, 0): - result.add toHex(BaseType(a[i])) - # No exceptions allowed {.push raises: [].} diff --git a/constantine/config/curves.nim b/constantine/config/curves.nim index f5896c1e7..d17d73880 100644 --- a/constantine/config/curves.nim +++ b/constantine/config/curves.nim @@ -10,8 +10,8 @@ import # Standard library macros, # Internal - ./curves_declaration, ./curves_derived, ./curves_parser, - ../arithmetic/bigints + ./type_bigint, ./common, + ./curves_declaration, ./curves_derived, ./curves_parser export CurveFamily, Curve, SexticTwist @@ -170,6 +170,12 @@ macro getBN_param_6u_minus_1_BE*(C: static Curve): untyped = ## of a BN curve in canonical big-endian representation result = bindSym($C & "_BN_6u_minus_1_BE") +# Endomorphism +# ------------------------------------------------------- +macro getCubicRootOfUnity*(C: static Curve): untyped = + ## Get a non-trivial cubic root of unity + result = bindSym($C & "_cubicRootOfUnity") + # ############################################################ # # Debug info printed at compile-time @@ -187,12 +193,16 @@ macro debugConsts(): untyped {.used.} = let modulus = bindSym(curveName & "_Modulus") let r2modp = bindSym(curveName & "_R2modP") let negInvModWord = bindSym(curveName & "_NegInvModWord") + let cubeRootOfUnity = ident(curveName & "_cubicRootOfUnity") result.add quote do: echo "Curve ", `curveName`,':' echo " Field Modulus: ", `modulus` echo " Montgomery R² (mod P): ", `r2modp` echo " Montgomery -1/P[0] (mod 2^", WordBitWidth, "): ", `negInvModWord` + when declared(`cubeRootOfUnity`): + echo " Cube root of unity: ", `cubeRootOfUnity` + result.add quote do: echo "----------------------------------------------------------------------------" diff --git a/constantine/config/curves_declaration.nim b/constantine/config/curves_declaration.nim index 25ccd75df..f631c855b 100644 --- a/constantine/config/curves_declaration.nim +++ b/constantine/config/curves_declaration.nim @@ -91,6 +91,7 @@ declareCurves: family: BarretoNaehrig bn_u_bitwidth: 63 bn_u: "0x44E992B44A6909F1" # u: 4965661367192848881 + cubicRootOfUnity: "0x59e26bcea0d48bacd4f263f1acdb5c4f5763473177fffffe" # G1 Equation: Y^2 = X^3 + 3 # G2 Equation: Y^2 = X^3 + 3/(9+𝑖) @@ -141,6 +142,7 @@ declareCurves: modulus: "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" family: BarretoLynnScott # u: -(2^63 + 2^62 + 2^60 + 2^57 + 2^48 + 2^16) + cubicRootOfUnity: "0x5f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffe" # G1 Equation: y² = x³ + 4 # G2 Equation: y² = x³ + 4 (1+i) diff --git a/constantine/config/curves_derived.nim b/constantine/config/curves_derived.nim index a05511497..f19c928a3 100644 --- a/constantine/config/curves_derived.nim +++ b/constantine/config/curves_derived.nim @@ -10,8 +10,10 @@ import # Standard library macros, # Internal - ../arithmetic/precomputed, - ./curves_declaration + ./precompute, + ./curves_declaration, + ./type_fp, + ../io/io_bigints {.experimental: "dynamicBindSym".} @@ -124,6 +126,24 @@ macro genDerivedConstants*(): untyped = ) ) + # const MyCurve_cubicRootOfUnity + block: + let cubicHex = ident(curve & "_cubicRootOfUnityHex") + let cubic = used(curve & "_cubicRootOfUnity") + let M = bindSym(curve & "_Modulus") + let r2modM = ident(curve & "_R2modP") + let m0ninv = ident(curve & "_NegInvModWord") + result.add quote do: + when declared(`cubichex`): + const `cubic` = block: + var cubic: Fp[Curve(`curveSym`)] + montyResidue_precompute( + cubic.mres, + fromHex(cubic.mres.typeof, `cubicHex`), + `M`, `r2modM`, `m0ninv` + ) + cubic + if CurveFamilies[curveSym] == BarretoNaehrig: # when declared(MyCurve_BN_param_u): # const MyCurve_BN_u_BE = toCanonicalIntRepr(MyCurve_BN_param_u) @@ -148,3 +168,5 @@ macro genDerivedConstants*(): untyped = bnStmts ) ) + + # echo result.toStrLit() diff --git a/constantine/config/curves_parser.nim b/constantine/config/curves_parser.nim index c85a70fb0..064380b5e 100644 --- a/constantine/config/curves_parser.nim +++ b/constantine/config/curves_parser.nim @@ -10,7 +10,8 @@ import # Standard library std/[macros, strutils], # Internal - ../io/io_bigints, ../arithmetic/[bigints, precomputed] + ../io/io_bigints, + ./type_bigint, ./precompute # Parsing is done in 2 steps: # 1. All declared parameters are collected in a {.compileTime.} seq[CurveParams] @@ -98,6 +99,9 @@ type nonresidue_quad_fp: NimNode # nnkIntLit nonresidue_cube_fp2: NimNode # nnkPar(nnkIntLit, nnkIntLit) + # Endomorphisms + cubicRootOfUnity: NimNode # nnkStrLit + # Curve parameters eq_form: CurveEquationForm coef_A: CurveCoef @@ -181,6 +185,8 @@ proc parseCurveDecls(defs: var seq[CurveParams], curves: NimNode) = params.bn_u_bitwidth = sectionVal elif sectionId.eqIdent"bn_u": params.bn_u = sectionVal + elif sectionId.eqident"cubicRootOfUnity": + params.cubicRootOfUnity = sectionVal elif sectionId.eqIdent"eq_form": params.eq_form = parseEnum[CurveEquationForm]($sectionVal) elif sectionId.eqIdent"coef_a": @@ -271,6 +277,14 @@ proc genMainConstants(defs: var seq[CurveParams]): NimNode = MapCurveFamily.add nnkExprColonExpr.newTree( curve, newLit(family) ) + # Endomorphisms + # ----------------------------------------------- + if not curveDef.cubicRootOfUnity.isNil: + curveExtraStmts.add newConstStmt( + exported($curve & "_cubicRootOfUnityHex"), + curveDef.cubicRootOfUnity + ) + # Curve equation # ----------------------------------------------- curveEllipticStmts.add newConstStmt( diff --git a/constantine/arithmetic/precomputed.nim b/constantine/config/precompute.nim similarity index 78% rename from constantine/arithmetic/precomputed.nim rename to constantine/config/precompute.nim index a2c1f41d0..d20797521 100644 --- a/constantine/arithmetic/precomputed.nim +++ b/constantine/config/precompute.nim @@ -7,13 +7,14 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - ./bigints, + ./type_bigint, ./common, ../primitives/constant_time, - ../config/common, ../io/io_bigints # Precomputed constants -# ############################################################ +# We need alternate code paths for the VM +# for various reasons +# ------------------------------------------------------------ # ############################################################ # @@ -25,7 +26,7 @@ import # Those are NOT tagged compile-time, using CTBool seems to confuse the VM # We don't use distinct types here, they confuse the VM -# Similarly, using addC / subB confuses the VM +# Similarly, using addC / subB from primitives confuses the VM # As we choose to use the full 32/64 bits of the integers and there is no carry flag # in the compile-time VM we need a portable (and slow) "adc" and "sbb". @@ -36,9 +37,15 @@ const HalfBase = (BaseType(1) shl HalfWidth) HalfMask = HalfBase - 1 +func hi(n: BaseType): BaseType = + result = n shr HalfWidth + +func lo(n: BaseType): BaseType = + result = n and HalfMask + func split(n: BaseType): tuple[hi, lo: BaseType] = - result.hi = n shr HalfWidth - result.lo = n and HalfMask + result.hi = n.hi + result.lo = n.lo func merge(hi, lo: BaseType): BaseType = (hi shl HalfWidth) or lo @@ -104,6 +111,56 @@ func sub(a: var BigInt, w: BaseType): bool = result = bool(borrow) +func mul(hi, lo: var BaseType, u, v: BaseType) = + ## Extended precision multiplication + ## (hi, lo) <- u * v + var x0, x1, x2, x3: BaseType + + let + (uh, ul) = u.split() + (vh, vl) = v.split() + + x0 = ul * vl + x1 = ul * vh + x2 = uh * vl + x3 = uh * vh + + x1 += hi(x0) # This can't carry + x1 += x2 # but this can + if x1 < x2: # if carry, add it to x3 + x3 += HalfBase + + hi = x3 + hi(x1) + lo = merge(x1, lo(x0)) + +func muladd1(hi, lo: var BaseType, a, b, c: BaseType) {.inline.} = + ## Extended precision multiplication + addition + ## (hi, lo) <- a*b + c + ## + ## Note: 0xFFFFFFFF_FFFFFFFF² -> (hi: 0xFFFFFFFFFFFFFFFE, lo: 0x0000000000000001) + ## so adding any c cannot overflow + var carry: BaseType + mul(hi, lo, a, b) + addC(carry, lo, lo, c, 0) + addC(carry, hi, hi, 0, carry) + +func muladd2(hi, lo: var BaseType, a, b, c1, c2: BaseType) {.inline.}= + ## Extended precision multiplication + addition + addition + ## (hi, lo) <- a*b + c1 + c2 + ## + ## Note: 0xFFFFFFFF_FFFFFFFF² -> (hi: 0xFFFFFFFFFFFFFFFE, lo: 0x0000000000000001) + ## so adding 0xFFFFFFFFFFFFFFFF leads to (hi: 0xFFFFFFFFFFFFFFFF, lo: 0x0000000000000000) + ## and we have enough space to add again 0xFFFFFFFFFFFFFFFF without overflowing + var carry1, carry2: BaseType + + mul(hi, lo, a, b) + # Carry chain 1 + addC(carry1, lo, lo, c1, 0) + addC(carry1, hi, hi, 0, carry1) + # Carry chain 2 + addC(carry2, lo, lo, c2, 0) + addC(carry2, hi, hi, 0, carry2) + func cadd(a: var BigInt, b: BigInt, ctl: bool): bool = ## In-place optional addition ## @@ -147,6 +204,22 @@ func doubleMod(a: var BigInt, M: BigInt) = ctl = ctl or not a.csub(M, false) discard csub(a, M, ctl) +func `<`(a, b: BigInt): bool = + var diff, borrow: BaseType + for i in 0 ..< a.limbs.len: + subB(borrow, diff, BaseType(a.limbs[i]), BaseType(b.limbs[i]), borrow) + + result = bool borrow + +func shiftRight*(a: var BigInt, k: int) = + ## Shift right by k. + ## + ## k MUST be less than the base word size (2^32 or 2^64) + + for i in 0 ..< a.limbs.len-1: + a.limbs[i] = (a.limbs[i] shr k) or (a.limbs[i+1] shl (WordBitWidth - k)) + a.limbs[a.limbs.len-1] = a.limbs[a.limbs.len-1] shr k + # ############################################################ # # Montgomery Magic Constants precomputation @@ -413,3 +486,48 @@ func bn_6u_minus_1_BE*[bits: static int]( # Export result.exportRawUint(u_ext, bigEndian) + +# ############################################################ +# +# Compile-time Conversion to Montgomery domain +# +# ############################################################ +# This is needed to avoid recursive dependencies + +func montyMul_precompute(r: var BigInt, a, b, M: BigInt, m0ninv: BaseType) = + ## Montgomery Multiplication using Coarse Grained Operand Scanning (CIOS) + var t: typeof(M) # zero-init + const N = t.limbs.len + var tN: BaseType + var tNp1: BaseType + + var tmp: BaseType # Distinct types bug in the VM are a huge pain ... + + for i in 0 ..< N: + var A: BaseType + for j in 0 ..< N: + muladd2(A, tmp, BaseType(a.limbs[j]), BaseType(b.limbs[i]), BaseType(t.limbs[j]), A) + t.limbs[j] = SecretWord(tmp) + addC(tNp1, tN, tN, A, 0) + + var C, lo: BaseType + let m = BaseType(t.limbs[0]) * m0ninv + muladd1(C, lo, m, BaseType(M.limbs[0]), BaseType(t.limbs[0])) + for j in 1 ..< N: + muladd2(C, tmp, m, BaseType(M.limbs[j]), BaseType(t.limbs[j]), C) + t.limbs[j-1] = SecretWord(tmp) + + var carry: BaseType + addC(carry, tmp, tN, C, 0) + t.limbs[N-1] = SecretWord(tmp) + addC(carry, tN, tNp1, 0, carry) + + discard t.csub(M, (tN != 0) or not(t < M)) + r = t + +func montyResidue_precompute*(r: var BigInt, a, M, r2modM: BigInt, + m0ninv: BaseType) = + ## Transform a bigint ``a`` from it's natural representation (mod N) + ## to a the Montgomery n-residue representation + ## This is intended for compile-time precomputations-only + montyMul_precompute(r, a, r2ModM, M, m0ninv) diff --git a/constantine/config/type_bigint.nim b/constantine/config/type_bigint.nim new file mode 100644 index 000000000..38dde062d --- /dev/null +++ b/constantine/config/type_bigint.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 ./common + +func wordsRequired*(bits: int): int {.compileTime.} = + ## Compute the number of limbs required + # from the **announced** bit length + (bits + WordBitWidth - 1) div WordBitWidth + +type + BigInt*[bits: static int] = object + ## Fixed-precision big integer + ## + ## - "bits" is the announced bit-length of the BigInt + ## This is public data, usually equal to the curve prime bitlength. + ## + ## - "limbs" is an internal field that holds the internal representation + ## of the big integer. Least-significant limb first. Within limbs words are native-endian. + ## + ## This internal representation can be changed + ## without notice and should not be used by external applications or libraries. + limbs*: array[bits.wordsRequired, SecretWord] + + +debug: + import strutils + + type Limbs[N: static int] = array[N, SecretWord] + + func toString*(a: Limbs): string = + result = "[" + result.add " 0x" & toHex(BaseType(a[0])) + for i in 1 ..< a.len: + result.add ", 0x" & toHex(BaseType(a[i])) + result.add "])" + + func toHex*(a: Limbs): string = + result = "0x" + for i in countdown(a.len-1, 0): + result.add toHex(BaseType(a[i])) + + func `$`*(a: BigInt): string = + result = "BigInt[" + result.add $BigInt.bits + result.add "](limbs: " + result.add a.limbs.toString() + result.add ")" diff --git a/constantine/config/type_fp.nim b/constantine/config/type_fp.nim new file mode 100644 index 000000000..d05249753 --- /dev/null +++ b/constantine/config/type_fp.nim @@ -0,0 +1,26 @@ +# 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 + ./common, + ./curves_declaration + +type + Fp*[C: static Curve] = object + ## All operations on a field are modulo P + ## P being the prime modulus of the Curve C + ## Internally, data is stored in Montgomery n-residue form + ## with the magic constant chosen for convenient division (a power of 2 depending on P bitsize) + mres*: matchingBigInt(C) + +debug: + func `$`*[C: static Curve](a: Fp[C]): string = + result = "Fp[" & $C + result.add "](" + result.add $a.mres + result.add ')' diff --git a/constantine/io/io_bigints.nim b/constantine/io/io_bigints.nim index 8e1230623..47b207588 100644 --- a/constantine/io/io_bigints.nim +++ b/constantine/io/io_bigints.nim @@ -12,8 +12,7 @@ import ../primitives/constant_time, - ../arithmetic/bigints, - ../config/common + ../config/[common, type_bigint] # ############################################################ # diff --git a/constantine/io/io_ec.nim b/constantine/io/io_ec.nim index ca2c57d0c..eab593055 100644 --- a/constantine/io/io_ec.nim +++ b/constantine/io/io_ec.nim @@ -9,7 +9,6 @@ import ./io_bigints, ./io_fields, ../config/curves, - ../arithmetic/[bigints, finite_fields], ../elliptic/[ ec_weierstrass_affine, ec_weierstrass_projective diff --git a/constantine/io/io_fields.nim b/constantine/io/io_fields.nim index dbc1c435a..cf132a215 100644 --- a/constantine/io/io_fields.nim +++ b/constantine/io/io_fields.nim @@ -9,7 +9,7 @@ import ./io_bigints, ../config/curves, - ../arithmetic/[bigints, finite_fields] + ../arithmetic/finite_fields # No exceptions allowed {.push raises: [].} diff --git a/constantine/primitives/constant_time.nim b/constantine/primitives/constant_time.nim index 005ee478a..e2c02ddc3 100644 --- a/constantine/primitives/constant_time.nim +++ b/constantine/primitives/constant_time.nim @@ -37,7 +37,10 @@ template cfalse*(T: typedesc[Ct or BaseUint]): auto = else: (CTBool[Ct[T]])(false) -template ct*[T: BaseUint](x: auto): Ct[T] = +template ct*[T: BaseUint](x: T): Ct[T] = + (Ct[T])(x) + +template ct*(x: auto, T: typedesc[BaseUint]): Ct[T] = (Ct[T])(x) template `$`*[T](x: Ct[T]): string = diff --git a/sage/curve_family_bls12.sage b/sage/curve_family_bls12.sage index a8324a08a..984c30115 100644 --- a/sage/curve_family_bls12.sage +++ b/sage/curve_family_bls12.sage @@ -30,11 +30,14 @@ def compute_curve_characteristic(u_str): else: print(' Parameter u (hex): 0x' + u.hex()) + print(f' p mod 3: ' + str(p % 3)) print(f' p mod 4: ' + str(p % 4)) print(f' p mod 8: ' + str(p % 8)) print(f' p mod 12: ' + str(p % 12)) print(f' p mod 16: ' + str(p % 16)) + print(f' cube roots of unity: ' + str(['0x' + Integer(root).hex() for root in GF(p)(1).nth_root(3, all=True)])) + if __name__ == "__main__": # Usage # sage sage/curve_family_bls12.sage '-(2^63 + 2^62 + 2^60 + 2^57 + 2^48 + 2^16)' diff --git a/sage/curve_family_bn.sage b/sage/curve_family_bn.sage index 2b81286f2..02801bf57 100644 --- a/sage/curve_family_bn.sage +++ b/sage/curve_family_bn.sage @@ -30,14 +30,18 @@ def compute_curve_characteristic(u_str): else: print(' Parameter u (hex): 0x' + u.hex()) + print(f' p mod 3: ' + str(p % 3)) print(f' p mod 4: ' + str(p % 4)) print(f' p mod 8: ' + str(p % 8)) print(f' p mod 12: ' + str(p % 12)) print(f' p mod 16: ' + str(p % 16)) + print(f' cube roots of unity: ' + str(['0x' + Integer(root).hex() for root in GF(p)(1).nth_root(3, all=True)])) + if __name__ == "__main__": # Usage # sage sage/curve_family_bn.sage '-(2^62 + 2^55 + 1)' + # sage sage/curve_family_bn.sage 4965661367192848881 from argparse import ArgumentParser diff --git a/tests/test_precomputed.nim b/tests/test_precomputed.nim new file mode 100644 index 000000000..a9c9b7638 --- /dev/null +++ b/tests/test_precomputed.nim @@ -0,0 +1,27 @@ +# 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 unittest, + ../constantine/arithmetic, + ../constantine/config/curves, + ../constantine/io/io_fields + +proc checkCubeRootOfUnity(curve: static Curve) = + test $curve & " cube root of unity": + var cru = curve.getCubicRootOfUnity() + cru.square() + cru *= curve.getCubicRootOfUnity() + + check: bool cru.isOne() + +proc main() = + suite "Sanity checks on precomputed values": + checkCubeRootOfUnity(BN254_Snarks) + checkCubeRootOfUnity(BLS12_381) + +main() From 5925e98bb032f171dd35f49e0f9f300d9477bf66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mamy=20Andr=C3=A9-Ratsimbazafy?= Date: Sun, 7 Jun 2020 15:08:13 +0200 Subject: [PATCH 03/19] =?UTF-8?q?Add=20the=20=CF=86-accelerated=20lookup?= =?UTF-8?q?=20table=20builder?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../elliptic/ec_endomorphism_accel.nim | 79 ++++++++++++++++++- .../elliptic/ec_weierstrass_projective.nim | 5 ++ 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/constantine/elliptic/ec_endomorphism_accel.nim b/constantine/elliptic/ec_endomorphism_accel.nim index 9f32d50e6..353e1b2a0 100644 --- a/constantine/elliptic/ec_endomorphism_accel.nim +++ b/constantine/elliptic/ec_endomorphism_accel.nim @@ -223,14 +223,55 @@ func nDimMultiScalarRecoding[M, LengthInBits, LengthInDigits: static int]( k[j].div2() k[j] += SecretWord -bji.ashr(1) +iterator bits(u: SomeInteger): tuple[bitIndex: int32, bitValue: uint8] = + ## bit iterator, starts from the least significant bit + var u = u + var idx = 0'i32 + while u != 0: + yield (idx, uint8(u and 1)) + u = u shr 1 + inc idx + +func buildLookupTable[M: static int, F]( + P: ECP_SWei_Proj[F], + endomorphisms: array[M-1, ECP_SWei_Proj[F]], + lut: var array[1 shl (M-1), ECP_SWei_Proj[F]], + ) = + ## Build the lookup table from the base point P + ## and the curve endomorphism + # Note: This is for variable/unknown point P + # when P is fixed at compile-time (for example is the generator point) + # alternative algorithm are more efficient. + # + # TODO: + # 1. Window method for M == 2 + # 2. Have P in affine coordinate and build the table with mixed addition + # assuming endomorphism φi(P) do not affect the Z coordinates + # (if table is big enough/inversion cost is amortized) + # 3. Use Montgomery simultaneous inversion to have the table in + # affine coordinate so that we can use mixed addition in teh main loop + for u in 0 ..< 1 shl (M-1): + # The recoding allows usage of 2^(n-1) table instead of the usual 2^n with NAF + lut[u] = P + for u in 0 ..< 1 shl (M-1): + for idx, bit in bits(u): + # TODO: we can reuse older table entries + if bit == 1: + lut[u] += endomorphisms[idx] # Sanity checks # ---------------------------------------------------------------- +# See page 7 of +# +# - Efficient and Secure Algorithms for GLV-Based Scalar +# Multiplication and their Implementation on GLV-GLS +# Curves (Extended Version) +# Armando Faz-Hernández, Patrick Longa, Ana H. Sánchez, 2013 +# https://eprint.iacr.org/2013/158.pdf when isMainModule: import ../io/io_bigints - proc toString(glvSac: GLV_SAC): string = for j in 0 ..< glvSac.M: result.add "k" & $j & ": [" @@ -245,10 +286,26 @@ when isMainModule: ) # " # Unbreak VSCode highlighting bug result.add " ]\n" + func buildLookupTable[M: static int]( + P: string, + endomorphisms: array[M-1, string], + lut: var array[1 shl (M-1), string], + ) = + # Checking the LUT by building strings of endomorphisms additions + for u in 0 ..< 1 shl (M-1): + # The recoding allows usage of 2^(n-1) table instead of the usual 2^n with NAF + lut[u] = P + for u in 0 ..< 1 shl (M-1): + for idx, bit in bits(u): + if bit == 1: + lut[u] &= " + " & endomorphisms[idx] proc main() = - var k: MultiScalar[4, 4] - var kRecoded: GLV_SAC[4, 5] + const M = 4 # GLS-4 decomposition + const miniBitwidth = 4 # Bitwidth of the miniscalars resulting from scalar decomposition + + var k: MultiScalar[M, miniBitwidth] + var kRecoded: GLV_SAC[M, miniBitwidth+1] k[0].fromUint(11) k[1].fromUint(6) @@ -259,4 +316,20 @@ when isMainModule: echo kRecoded.toString() + var lut: array[1 shl (M-1), string] + let + P = "P0" + endomorphisms = ["P1", "P2", "P3"] + + buildLookupTable(P, endomorphisms, lut) + echo lut + doAssert lut[0] == "P0" + doAssert lut[1] == "P0 + P1" + doAssert lut[2] == "P0 + P2" + doAssert lut[3] == "P0 + P1 + P2" + doAssert lut[4] == "P0 + P3" + doAssert lut[5] == "P0 + P1 + P3" + doAssert lut[6] == "P0 + P2 + P3" + doAssert lut[7] == "P0 + P1 + P2 + P3" + main() diff --git a/constantine/elliptic/ec_weierstrass_projective.nim b/constantine/elliptic/ec_weierstrass_projective.nim index bf61f47f8..04b14164a 100644 --- a/constantine/elliptic/ec_weierstrass_projective.nim +++ b/constantine/elliptic/ec_weierstrass_projective.nim @@ -274,6 +274,11 @@ func double*[F]( else: {.error: "Not implemented.".} +func `+=`*[F](P: var ECP_SWei_Proj[F], Q: ECP_SWei_Proj[F]) = + var tmp {.noInit.}: ECP_SWei_Proj[F] + tmp.sum(P, Q) + P = tmp + # ############################################################ # # # Scalar Multiplication # From 2213d1bf0498fe408f348404a6008b1c9cecb76f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mamy=20Andr=C3=A9-Ratsimbazafy?= Date: Sun, 7 Jun 2020 16:21:31 +0200 Subject: [PATCH 04/19] Add a dedicated bithacks file --- constantine/primitives.nim | 4 +- constantine/primitives/bithacks.nim | 72 +++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 constantine/primitives/bithacks.nim diff --git a/constantine/primitives.nim b/constantine/primitives.nim index d311f8424..b648ef391 100644 --- a/constantine/primitives.nim +++ b/constantine/primitives.nim @@ -11,7 +11,8 @@ import primitives/constant_time, primitives/multiplexers, primitives/addcarry_subborrow, - primitives/extended_precision + primitives/extended_precision, + primitives/bithacks export constant_time_types, @@ -19,3 +20,4 @@ export multiplexers, addcarry_subborrow, extended_precision + bithacks diff --git a/constantine/primitives/bithacks.nim b/constantine/primitives/bithacks.nim new file mode 100644 index 000000000..72c30a58e --- /dev/null +++ b/constantine/primitives/bithacks.nim @@ -0,0 +1,72 @@ +# 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. + +# ############################################################ +# +# Bit hacks +# +# ############################################################ + +# Bithacks +# ------------------------------------------------------------ +# TODO: Nim std/bitops is unsatisfactory +# in particular the "noUndefined" flag +# for countLeadingZeroBits/countTrailingZeroBits +# is returning zero instead of the integer bitwidth +# +# Furthermore it is not guaranteed constant-time +# And lastly, even compiler builtin may be slightly inefficient +# for example when doing fastLog2 +# which is "31 - builtin_clz" we get +# `bsr + xor (from clz) + sub` +# instead of plain `bsr` +# +# At the moment we don't need them to operate on secret data + +from std/bitops import countLeadingZeroBits, clearBit +export countLeadingZeroBits, clearBit + +# Compile-time VM +# ------------------------------------------------------------ + +func log2*(x: uint32): uint32 = + ## Find the log base 2 of a 32-bit or less integer. + ## using De Bruijn multiplication + ## Works at compile-time, guaranteed constant-time. + ## Note: at runtime BitScanReverse or CountLeadingZero are more efficient + ## but log2 is never needed at runtime. + # https://graphics.stanford.edu/%7Eseander/bithacks.html#IntegerLogDeBruijn + const lookup: array[32, uint8] = [0'u8, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, + 22, 25, 3, 30, 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31] + var v = x + v = v or v shr 1 # first round down to one less than a power of 2 + v = v or v shr 2 + v = v or v shr 4 + v = v or v shr 8 + v = v or v shr 16 + lookup[(v * 0x07C4ACDD'u32) shr 27] + +func log2*(x: uint64): uint64 {.inline, noSideEffect.} = + ## Find the log base 2 of a 32-bit or less integer. + ## using De Bruijn multiplication + ## Works at compile-time, guaranteed constant-time. + ## Note: at runtime BitScanReverse or CountLeadingZero are more efficient + ## but log2 is never needed at runtime. + # https://graphics.stanford.edu/%7Eseander/bithacks.html#IntegerLogDeBruijn + const lookup: array[64, uint8] = [0'u8, 58, 1, 59, 47, 53, 2, 60, 39, 48, 27, 54, + 33, 42, 3, 61, 51, 37, 40, 49, 18, 28, 20, 55, 30, 34, 11, 43, 14, 22, 4, 62, + 57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21, 56, 45, 25, 31, + 35, 16, 9, 12, 44, 24, 15, 8, 23, 7, 6, 5, 63] + var v = x + v = v or v shr 1 # first round down to one less than a power of 2 + v = v or v shr 2 + v = v or v shr 4 + v = v or v shr 8 + v = v or v shr 16 + v = v or v shr 32 + lookup[(v * 0x03F6EAF2CD271461'u64) shr 58] From f4c46826f9268474222b0326efdffcf9ada2ab2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mamy=20Andr=C3=A9-Ratsimbazafy?= Date: Sun, 7 Jun 2020 16:22:11 +0200 Subject: [PATCH 05/19] cosmetic import consistency --- constantine/arithmetic/limbs_montgomery.nim | 6 ++- constantine/config/curves.nim | 2 +- constantine/config/curves_derived.nim | 2 +- constantine/config/type_bigint.nim | 2 +- constantine/config/type_fp.nim | 2 + constantine/primitives/constant_time.nim | 47 +------------------ .../research/addcarry_subborrow_compiler.nim | 4 +- formal_verification/bls12_381_q_64.nim | 2 +- helpers/static_for.nim | 2 +- tests/test_bigints.nim | 2 +- tests/test_bigints_multimod.nim | 2 +- tests/test_bigints_vs_gmp.nim | 2 +- tests/test_ec_bls12_381.nim | 2 +- tests/test_ec_bn254.nim | 2 +- tests/test_ec_weierstrass_projective_g1.nim | 2 +- tests/test_finite_fields.nim | 4 +- tests/test_finite_fields_mulsquare.nim | 15 +++--- tests/test_finite_fields_powinv.nim | 17 ++++--- tests/test_finite_fields_sqrt.nim | 18 +++---- tests/test_finite_fields_vs_gmp.nim | 2 +- tests/test_precomputed.nim | 2 +- 21 files changed, 53 insertions(+), 86 deletions(-) diff --git a/constantine/arithmetic/limbs_montgomery.nim b/constantine/arithmetic/limbs_montgomery.nim index 683d065cf..42b1d3f68 100644 --- a/constantine/arithmetic/limbs_montgomery.nim +++ b/constantine/arithmetic/limbs_montgomery.nim @@ -7,10 +7,12 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import + # Stadard library + std/macros, + # Internal ../config/common, ../primitives, - ./limbs, - macros + ./limbs # ############################################################ # diff --git a/constantine/config/curves.nim b/constantine/config/curves.nim index d17d73880..7a3809fa2 100644 --- a/constantine/config/curves.nim +++ b/constantine/config/curves.nim @@ -8,7 +8,7 @@ import # Standard library - macros, + std/macros, # Internal ./type_bigint, ./common, ./curves_declaration, ./curves_derived, ./curves_parser diff --git a/constantine/config/curves_derived.nim b/constantine/config/curves_derived.nim index f19c928a3..4cc8ace18 100644 --- a/constantine/config/curves_derived.nim +++ b/constantine/config/curves_derived.nim @@ -8,7 +8,7 @@ import # Standard library - macros, + std/macros, # Internal ./precompute, ./curves_declaration, diff --git a/constantine/config/type_bigint.nim b/constantine/config/type_bigint.nim index 38dde062d..340cfffb3 100644 --- a/constantine/config/type_bigint.nim +++ b/constantine/config/type_bigint.nim @@ -29,7 +29,7 @@ type debug: - import strutils + import std/strutils type Limbs[N: static int] = array[N, SecretWord] diff --git a/constantine/config/type_fp.nim b/constantine/config/type_fp.nim index d05249753..a206021c7 100644 --- a/constantine/config/type_fp.nim +++ b/constantine/config/type_fp.nim @@ -19,6 +19,8 @@ type mres*: matchingBigInt(C) debug: + import ./type_bigint + func `$`*[C: static Curve](a: Fp[C]): string = result = "Fp[" & $C result.add "](" diff --git a/constantine/primitives/constant_time.nim b/constantine/primitives/constant_time.nim index e2c02ddc3..715f8c600 100644 --- a/constantine/primitives/constant_time.nim +++ b/constantine/primitives/constant_time.nim @@ -124,58 +124,15 @@ template `-`*[T: Ct](x: T): T = # ############################################################ # -# Bit hacks +# Hardened Boolean primitives # # ############################################################ -template isMsbSet*[T: Ct](x: T): CTBool[T] = +template isMsbSet[T: Ct](x: T): CTBool[T] = ## Returns the most significant bit of an integer const msb_pos = T.sizeof * 8 - 1 (CTBool[T])(x shr msb_pos) -func log2*(x: uint32): uint32 = - ## Find the log base 2 of a 32-bit or less integer. - ## using De Bruijn multiplication - ## Works at compile-time, guaranteed constant-time. - ## Note: at runtime BitScanReverse or CountLeadingZero are more efficient - ## but log2 is never needed at runtime. - # https://graphics.stanford.edu/%7Eseander/bithacks.html#IntegerLogDeBruijn - const lookup: array[32, uint8] = [0'u8, 9, 1, 10, 13, 21, 2, 29, 11, 14, 16, 18, - 22, 25, 3, 30, 8, 12, 20, 28, 15, 17, 24, 7, 19, 27, 23, 6, 26, 5, 4, 31] - var v = x - v = v or v shr 1 # first round down to one less than a power of 2 - v = v or v shr 2 - v = v or v shr 4 - v = v or v shr 8 - v = v or v shr 16 - lookup[(v * 0x07C4ACDD'u32) shr 27] - -func log2*(x: uint64): uint64 {.inline, noSideEffect.} = - ## Find the log base 2 of a 32-bit or less integer. - ## using De Bruijn multiplication - ## Works at compile-time, guaranteed constant-time. - ## Note: at runtime BitScanReverse or CountLeadingZero are more efficient - ## but log2 is never needed at runtime. - # https://graphics.stanford.edu/%7Eseander/bithacks.html#IntegerLogDeBruijn - const lookup: array[64, uint8] = [0'u8, 58, 1, 59, 47, 53, 2, 60, 39, 48, 27, 54, - 33, 42, 3, 61, 51, 37, 40, 49, 18, 28, 20, 55, 30, 34, 11, 43, 14, 22, 4, 62, - 57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21, 56, 45, 25, 31, - 35, 16, 9, 12, 44, 24, 15, 8, 23, 7, 6, 5, 63] - var v = x - v = v or v shr 1 # first round down to one less than a power of 2 - v = v or v shr 2 - v = v or v shr 4 - v = v or v shr 8 - v = v or v shr 16 - v = v or v shr 32 - lookup[(v * 0x03F6EAF2CD271461'u64) shr 58] - -# ############################################################ -# -# Hardened Boolean primitives -# -# ############################################################ - template fmap[T: Ct](x: CTBool[T], op: untyped, y: CTBool[T]): CTBool[T] = CTBool[T](op(T(x), T(y))) diff --git a/constantine/primitives/research/addcarry_subborrow_compiler.nim b/constantine/primitives/research/addcarry_subborrow_compiler.nim index 63d2523eb..33915779d 100644 --- a/constantine/primitives/research/addcarry_subborrow_compiler.nim +++ b/constantine/primitives/research/addcarry_subborrow_compiler.nim @@ -17,7 +17,7 @@ # # This overcome the bad GCC codegen aven with addcary_u64 intrinsic. -import macros +import std/macros func wordsRequired(bits: int): int {.compileTime.} = ## Compute the number of limbs required @@ -86,7 +86,7 @@ func `+=`(a: var BigInt, b: BigInt) {.noinline.}= # ############################################# when isMainModule: - import random + import std/random proc rand(T: typedesc[BigInt]): T = for i in 0 ..< result.limbs.len: result.limbs[i] = uint64(rand(high(int))) diff --git a/formal_verification/bls12_381_q_64.nim b/formal_verification/bls12_381_q_64.nim index b5da75e33..07cb51c52 100644 --- a/formal_verification/bls12_381_q_64.nim +++ b/formal_verification/bls12_381_q_64.nim @@ -128,7 +128,7 @@ func fromHex(output: var openArray[byte], hexStr: string, order: static[Endianne # ------------------------------------------------------------------------- when isMainModule: - import random, std/monotimes, times, strformat, ../benchmarks/platforms + import std/[random, monotimes, times, strformat], ../benchmarks/platforms const Iters = 1_000_000 const InvIters = 1000 diff --git a/helpers/static_for.nim b/helpers/static_for.nim index 0232664b9..ffedcadd7 100644 --- a/helpers/static_for.nim +++ b/helpers/static_for.nim @@ -1,4 +1,4 @@ -import macros +import std/macros proc replaceNodes(ast: NimNode, what: NimNode, by: NimNode): NimNode = # Replace "what" ident node by "by" diff --git a/tests/test_bigints.nim b/tests/test_bigints.nim index ed4f50f21..2a0ca5259 100644 --- a/tests/test_bigints.nim +++ b/tests/test_bigints.nim @@ -6,7 +6,7 @@ # * 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 unittest, +import std/unittest, ../constantine/io/io_bigints, ../constantine/arithmetic, ../constantine/config/common, diff --git a/tests/test_bigints_multimod.nim b/tests/test_bigints_multimod.nim index e020b4c99..3c5e5e8b5 100644 --- a/tests/test_bigints_multimod.nim +++ b/tests/test_bigints_multimod.nim @@ -8,7 +8,7 @@ import # Standard library - unittest, + std/unittest, # Third-party ../constantine/io/io_bigints, ../constantine/arithmetic, diff --git a/tests/test_bigints_vs_gmp.nim b/tests/test_bigints_vs_gmp.nim index fe711456e..e55df0d83 100644 --- a/tests/test_bigints_vs_gmp.nim +++ b/tests/test_bigints_vs_gmp.nim @@ -8,7 +8,7 @@ import # Standard library - random, macros, times, strutils, + std/[random, macros, times, strutils], # Third-party gmp, stew/byteutils, # Internal diff --git a/tests/test_ec_bls12_381.nim b/tests/test_ec_bls12_381.nim index b487965a6..2927b027b 100644 --- a/tests/test_ec_bls12_381.nim +++ b/tests/test_ec_bls12_381.nim @@ -8,7 +8,7 @@ import # Standard library - unittest, times, + std/[unittest, times], # Internals ../constantine/config/[common, curves], ../constantine/arithmetic, diff --git a/tests/test_ec_bn254.nim b/tests/test_ec_bn254.nim index d553effd3..18b5cda10 100644 --- a/tests/test_ec_bn254.nim +++ b/tests/test_ec_bn254.nim @@ -8,7 +8,7 @@ import # Standard library - unittest, times, + std/[unittest, times], # Internals ../constantine/config/[common, curves], ../constantine/arithmetic, diff --git a/tests/test_ec_weierstrass_projective_g1.nim b/tests/test_ec_weierstrass_projective_g1.nim index ef0ac0375..e6cccb4fd 100644 --- a/tests/test_ec_weierstrass_projective_g1.nim +++ b/tests/test_ec_weierstrass_projective_g1.nim @@ -8,7 +8,7 @@ import # Standard library - unittest, times, + std/[unittest, times], # Internals ../constantine/config/[common, curves], ../constantine/arithmetic, diff --git a/tests/test_finite_fields.nim b/tests/test_finite_fields.nim index f85f70bc9..fd169a40f 100644 --- a/tests/test_finite_fields.nim +++ b/tests/test_finite_fields.nim @@ -6,13 +6,11 @@ # * 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 unittest, +import std/unittest, ../constantine/arithmetic, ../constantine/io/io_fields, ../constantine/config/curves -import ../constantine/io/io_bigints - static: doAssert defined(testingCurves), "This modules requires the -d:testingCurves compile option" proc main() = diff --git a/tests/test_finite_fields_mulsquare.nim b/tests/test_finite_fields_mulsquare.nim index d44bf49bd..d04923c7e 100644 --- a/tests/test_finite_fields_mulsquare.nim +++ b/tests/test_finite_fields_mulsquare.nim @@ -6,12 +6,15 @@ # * 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/unittest, std/times, - ../constantine/arithmetic, - ../constantine/io/[io_bigints, io_fields], - ../constantine/config/[curves, common], - # Test utilities - ../helpers/prng_unsafe +import + # Standard library + std/[unittest, times], + # Internal + ../constantine/arithmetic, + ../constantine/io/[io_bigints, io_fields], + ../constantine/config/[curves, common], + # Test utilities + ../helpers/prng_unsafe const Iters = 128 diff --git a/tests/test_finite_fields_powinv.nim b/tests/test_finite_fields_powinv.nim index bb59f24d7..ee6cb4543 100644 --- a/tests/test_finite_fields_powinv.nim +++ b/tests/test_finite_fields_powinv.nim @@ -6,13 +6,16 @@ # * 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/arithmetic, - ../constantine/io/[io_bigints, io_fields], - ../constantine/config/curves, - # Test utilities - ../helpers/prng_unsafe, - # Standard library - std/unittest, std/times +import + # Standard library + std/[unittest, times], + # Internal + ../constantine/arithmetic, + ../constantine/io/[io_bigints, io_fields], + ../constantine/config/curves, + # Test utilities + ../helpers/prng_unsafe, + static: doAssert defined(testingCurves), "This modules requires the -d:testingCurves compile option" diff --git a/tests/test_finite_fields_sqrt.nim b/tests/test_finite_fields_sqrt.nim index c6b81502c..ec8f07486 100644 --- a/tests/test_finite_fields_sqrt.nim +++ b/tests/test_finite_fields_sqrt.nim @@ -6,14 +6,16 @@ # * 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/[arithmetic, primitives], - ../constantine/io/[io_fields], - ../constantine/config/[curves, common], - # Test utilities - ../helpers/prng_unsafe, - # Standard library - std/tables, - std/unittest, std/times +import + # Standard library + std/[tables, unittest, times], + # Internal + ../constantine/[arithmetic, primitives], + ../constantine/io/[io_fields], + ../constantine/config/[curves, common], + # Test utilities + ../helpers/prng_unsafe, + const Iters = 128 diff --git a/tests/test_finite_fields_vs_gmp.nim b/tests/test_finite_fields_vs_gmp.nim index 518730f81..63fa2ffd6 100644 --- a/tests/test_finite_fields_vs_gmp.nim +++ b/tests/test_finite_fields_vs_gmp.nim @@ -8,7 +8,7 @@ import # Standard library - random, macros, times, strutils, + std/[random, macros, times, strutils], # Third-party gmp, stew/byteutils, # Internal diff --git a/tests/test_precomputed.nim b/tests/test_precomputed.nim index a9c9b7638..40a123033 100644 --- a/tests/test_precomputed.nim +++ b/tests/test_precomputed.nim @@ -6,7 +6,7 @@ # * 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 unittest, +import std/unittest, ../constantine/arithmetic, ../constantine/config/curves, ../constantine/io/io_fields From cb7ab3ebc9a3d341f411e9dd4a3bec304a2f7e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mamy=20Andr=C3=A9-Ratsimbazafy?= Date: Sun, 7 Jun 2020 16:59:19 +0200 Subject: [PATCH 06/19] =?UTF-8?q?Build=20the=20=CF=86=20precompute=20table?= =?UTF-8?q?=20with=20n-1=20EC=20additions=20instead=20of=202^(n-1)=20addit?= =?UTF-8?q?ions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- constantine/config/precompute.nim | 2 +- constantine/elliptic/ec_endomorphism_accel | Bin 0 -> 142328 bytes .../elliptic/ec_endomorphism_accel.nim | 87 +++++++++++++----- constantine/primitives.nim | 2 +- constantine/primitives/bithacks.nim | 21 +++-- tests/test_finite_fields_powinv.nim | 2 +- tests/test_finite_fields_sqrt.nim | 2 +- 7 files changed, 82 insertions(+), 34 deletions(-) create mode 100755 constantine/elliptic/ec_endomorphism_accel diff --git a/constantine/config/precompute.nim b/constantine/config/precompute.nim index d20797521..dc2e9b3d2 100644 --- a/constantine/config/precompute.nim +++ b/constantine/config/precompute.nim @@ -8,7 +8,7 @@ import ./type_bigint, ./common, - ../primitives/constant_time, + ../primitives, ../io/io_bigints # Precomputed constants diff --git a/constantine/elliptic/ec_endomorphism_accel b/constantine/elliptic/ec_endomorphism_accel new file mode 100755 index 0000000000000000000000000000000000000000..a5326c3206b5710d6f103035b7d9031bbfedf8a7 GIT binary patch literal 142328 zcmeFad3;n=5;ojH8lu7^E@*UIB5oiqaX~>v0@~UxI2i?Z1s8BbMM)HwKx30JZQIh~ zhU1oTT;d)#jE+D+OrtZSjgA@}m!N<*1~sm%FwXT#`t%;(6*sIXzh)^ zl?Iysd7dG&pT!1m1ALOM(ZKURw=sD3(@Mxs()ILb?uH7npP^FyO}a3@n&0`K6QkOL z{j_kmzX=Kt<$X4sp#1HppN_U^ny++C{|yi+6-Nrp|hVMV=wz@ z?Ma*P?-B#g|2zyZ{;{9L<)5mR)#vcLlTbPGKff~_*iTFMXV6j3_5EYbew^vAN4psL ztIVgdPH3;m=byFzetS(mXOGF}UtBY7k7>j9-(&y%_MCdjp8JU2n?n$q^oV1}g$9iK z`xD=8)oY78!y|8e^S5no-R6N|5z0*&$d5SuLwvtE2jDm1q8GmumG)xW7Wg}O+Z(nS z{!P-lBU*xg+VOMKV3YjX82^?RXrGI=LipGA(0+3d?f3T3KDmeXpL=M3u!r{NdT778 zhxT33UXH){f0m%3kiHXpXn(VZ_Q!i@zr2U`_j+jGAMNG%oBwAu8V2S!;`e;~-6XVC zD71`@5enIP_fOMap4T3Nzg|T5+&?t_$gyL_pEL2YiRYd_wR+-ZW5*mi`I3t#jy?0N z$rD54$De!AB^QsMT7Bka)#Jx2YC%M>J7==GJHDQ+oF`41Tr>5&(4@(ioPFV>^TBT7 zwDYS&Q_ny5;xi|MHMpLA;rO%9yKwxZGtZwKy6B=ar-Y_fUp9H-#i5HPUNm)LH9yY2 zXi5n3oO=O2$D@s(Q>)Lpq(-4ruH>(Ymt7XRsODlam~_Qu=T}b*O{%%r?REV4v!|Uo ze$x5q_57=-6D2e{U3BL87l)2G=E%{9j^Ag`z4vp!df)chb0}=cdDsgxvRB|g#x$>m z82pcaihz46K5bmN<)+&(ZF`6MV|duM`T6Jf=L~0fP7tFJ$Af9mi7R zoXsZQ+V&Q+4z}dAS4>w%OY_>ROnWx3eTr#s%qpGbZ-2MFq3w)4sv*AMPrI+((y-m} zwfAF#`P1cV-^@1RGv{k(taSevzvbG#|^nLc@IR5{FHDrLUcR+dolXyIJ}LROM^m$RgwOBwssYv;8x{*AB;V z|D5M*|AX5a3Qh5~m-yPJ`P!{-5*+ij8K(zpwwZ!2c}pKMVZN0{^qX z|1}GITe96x(fI1(XfpiX!lF>LuBE!y+H5qwr1)j&W9`sS(7bl%75KMg`$~M{@AC-j zXkUvzJHOB0Oa(f!^7~ExW(w2MBEMhbZ>9ttP4as&e=`;6XprA8@;6g}jH?X&k6M<$k?5lws&jj!mc8hcDCyu}ePRjU8Jhx21Z zTLcywFlRwg=({w2J`4uYRG4x`hv%vXz_b?wM$>M%w!LKg77(@(%;=@(5#a+5NZ14E(Ct}i2 z!J~;tZtoV@dGr``l!z?F*OB_QEt-jBsobPYq#c;4NJ})4adv}NfHV_AjOS>mRCpsF z(F@wN<``kw2EiMn$#D(Q!CCas?3_pr$w+fFIVN{bbVxrDq&n>M{dMhHqry5xU8Q^} z>I&OMK~ZP?hQW#~6fs|-xT)|nK7s|>)L@&E;~-|FAv%0<$()w@$U^4{(3i}MEX)`5 zj9+|$R>7vt-R*gkC*;lnYQd%?KRWz%mW(N{-+mAuH+}{n@-v?y)v$H3<>89`UpMGE83zQ)}WNXX2 zpuVSLBq2x>O{(QiC zNcx)gL`kQ3ofepO&tRBovXv^~p&qi%MA{p|t$^t~;qV(KbXI}3n;q}?@%Qa`h$r1> zufqb;4N&RCjteS8#1Rf^ug>PT(P*>{{-y@pf;J6h{I!?xtSW_GRtX?hmKg;^p&xGt zgXFlTXkv6(G+x^rs%GrO)KE8}N%mHgG#u$VYtyJ^ZS0?9V`@N+d<du2qJfje z@N~0EJm<4Y4^z8dJK~t#pkI3km1|*Q3do&5kjGnOBYzBIW!8rtdYbC>G|&RP%ltMs=*%jz{G2rR1?m=@o%I^n~-W%Y2ufn~!7 z#>Bcu;LUVI=riSZa0-s(K9$E`_2#djmFAoqEy{9?861Ipr6N}tWW@f5H1=;r=v4~c)IcG1sPj*0LatFr zj%guPj@QL2%}8ZH zELd-TtGe!~SpTwEutc93JBT=YDd;H|6lp5z_zH734PT_Ze^G>}i%=U=d1D3TJtQb^ z%t*2XtB7;6EUZkIf~nE!Z+&5D0^F^!yNiv}{^?@#kxGl{ARUCu%itF3qUSou11f*L zkLW>ds*2HSpglaa$AS0}BE^8|+47XXMZqpx{O;w3W6@mj(gm=L=6BR~{Js!KiSTr$z8Ygt( zIqtgrW9CTK{eajI(FhI#sXcj1BWyK?lPK}tix4+-Okx$yHyOrB zL}sxYmhGY5Ri>L44e{J4wqBQpg(#X>1iPoBiQ;G?8*fLtiadMA=cN<0M&@>W#_3OP zZ>i9$N2@I|k$UA9{=A7Ti-aHdlIU~9(dg4z-bGRBN&Fure$pkr!4N+##2KeXaBm>) zJBk}OxY1;|-XJx7g}^ZdI?IgkZw=-Fg3V{_)KRColcNHY!&#s8pR|}+-ilr>)9U}B z^*2-Fv^x z{eg%YTws!4yiEW5)0DY{W)pnP`!~&I` zeF*zhVOc*c!F&+QBnCjj%k9()W6`;dEXfEq?CEYxV#d;0qU$S0DyaSqtNrSCSKVy4 zxV9eY@hq^m9?iwwOW$189TljbB?h18C1i;9NaiT+mtKxQSQuryNOSc_ zj-Xj^&IrIOOF=H`n-ynd04!6h^Vzghs>(LyE8F(@?<>2PvgEieUuW8V^Rl3_yod{; z$2_7YOmhEj?j{TJ*2*IzPNjrVH$u3Z5 zc%#E-h$@VFfpXNo%9Dn0Y@P~%&|zM3cs<%E>NOhj{$Y_NX2co?0gCnX4w*;Dq!UsA zs#WA03nQN_F^9;bi=EMhcHRBEX@Ef4)O{Mx*3AO7SZw<$sKt^uu)%*9n_{ksfpXb> zj?Y*V3Qv*!ylzfiXE&##8yL&Yq<_@`+{F?Z*Ko#gdtP9H7_c6~%u{C6`E{F)7t}fX zdMIS8oxiCdcN+pU7dcB&+R$odFleMd%eP43Rq7y5kS{bh>>+R*0YUs$c|@0_W*^ZL z+GNt-HcV~AvPc8>k6ci$tpz&qjbE)ZUapyb^ec~U~`aVpG7mLQ2O$D-VZ zQORx4kt8-}>Dn*rJ8!;dD+oJpX3WvIM8kC z3H8!Q*HH=#<{1#GHS)X+=4urv=@HmT*}w(!doP7+jHc3! zk|UfvpKr=9mgh^}-38Crv8O5fctRAAx^ZdHlz&snmmT8zMq_m65O$to%sJl_Hs&eo zA=1Y>IuvQDnU+oI4y=Qr%;3$0|W?Z0oP6Fr$e9$ zotg1unx-;6VF^Sg#Gnx^NQz7D;l;wVb6yWpRdkc8u-m<+qeg3`Y)1vH^;v6h0N+vR zPVy1GuT2r#Bm$3V&|=4$uQ9o0aG&ESOF*q*lC+<_a#j0rz99>XytOCtZg#v1io730 zwgdc(E?+-d*+B4|Hm$NvzIFCCxf0tCf)-k5|MQVg&~?P;8kLT=eed9Pje7DGv8}8M z46FlHvimKO6toaL0oqYP2@hnG{gPRn`^4EvaV8p^*2oh&?UUI2B*&#qjK3Mdp+52i z6nsJ}o!jW3RF&b=BK5(F4kWFJ8a!|PJ6gi?&f47%2dRp((8%xxx_b$!0781nym5%x z(BqZk8P2S3`P-E1NWsMw+Jw?61OXkUkSh1vTDp_C@ zW-6<%h8q!*b;1K-`C(8-6LnQ&3B+p^vDF~9)*Y&SDgyBPVGC9-t+Dw6E2-;cI*BXo z^)BsUd9>#UEn6WaKa7+)&Q)|iOBo#LNhx9_>P`YK5$Y6SkcU7uc3z7Zw4hQ(Bje0Q z9R~_cFeKM0$@jyIL)1k8fDx5vvZ?}K1y_@e`>>SP3l2misu2;pXc6&xW%rA1 zbhEF?V8z^~y7Sm8B{Ut!TPh{>Fn!A!cr{n;grNHP9^^5ZlE)-jRUDN54joM5!I!4F z=(kEsQdz5_=b_$%&WHmZ@{x$d*zXOh&&vl`iyclrm<=&pnZ{PUbs=mKa;8Dr`Z~g3 zDSqh09ZVJ}Wf3zhgf=0*-SGiv(;e@~znnlaYtxEf<>DK6lOl2UCWY?fLeU&=DM$Pk z;yDW5%$2v&$eWHHByL`U2wKNR2+9u?bX!Q34rufs7J5aL5aYuYrEb=K9^GH3$`^iF3N~0J9B4dk4Tt&{az&*VMto*j!^r6 z!taIObkR11!8ZI*+w8(#88)?cU%VH{gf<>0K$vY%8jn^D_eKyg{*&3q3L4(Dc zmGAh3n!w^s;k@J#?SO)ruRUy5@0u@yX{rtZH-_jSs__JDz=;J21>?yPgofyvw&)I7 zj90UB1qvsCVAT(98b=1COw>NsePKM&6Y%7H%>Wg&+|Q^=z;dpDb5y{c4AIQm#~4|! zkUIIz3M=-(&~nAbpqG3m-FXnFW>Ya*J1bRn(KZx{GNM5~ z9Oj{U9yI7szA3Rd8F?(R>@6p;7?5WN;mYc~-o2tF3$ldTg3d9hhp4H7iRR{Sdjq;8 zX7|@TM$z47gLN`+PNIAa6ee|&ih%%Wj5~S_ zkelF7kS8w)!490Je__efPSdzkL>Hh<%%$=OlUbWfEr@*J~BE&{w+C{=C$|_D}G53A2LtVGA&X_Dbqfpc%N0cApwS( zic{yGxHm{uwWGD(NdBmjHF;#j2hvYMnkaQHLYV|@!O1%8EpKdBPbfGI&QVw8kRE5b z7aY>>UXUSmr@OF1V&`NBCjJspHiG&d&Exet<}roxyVF-`@0zHO0mdxZK?C zF87-H8RdI=FV~B*=sYjLjm?vfV+EpGOC&1-E!`NMxwtnjdMXh8AWXG7H!NrOm6nvDz~8F?1gn9j0;ENS8Zrk=TK zPuBskP*E(%fn*BOE&3!JEqVT%Peuav`A90=Hjju;Jd@+l|gGR{H3gr#!elEY^gKD*qr z%u0zfca|t#+a9Xk1UT)CEO&AA6Rn+L4i5~;lXZkFizdEVfMEy`=@E1Q{+Y-x{%*%g z>o49#`pWK3yroVOIZ zSdC076!jGkb<;FZQ$xR(mW=Zg9T13m95Z~ELDVF=^$qs`Ql?wKlC4z5{!W<2SM#*G*f&2Owks-mhtqa-i-0`}Mpu}{CfK>QmLoGEv zR;1-n((|#}=R-C9c^GxAHb|-&d0vyRt*LH@GT6f~aL#}k=xB$1j7|y!rGDt#b(bD& zu|S#87H{vdu3(gTHfW30DoleV`h8oZ{_WY~OQk-`Qd4ad-i@uP)FmGkR_Xw7ag~x3 z64_dTt9sunYbtikZtGI)OO%di-Op8|?JcqxG#x7kRB{fkrOnZVv^dK?EU-4+^)H`j z6`a_KniW_Bm?oNeJ+3V|W(62IdM5RHP;i2KQ>_$anCmzXn;ggap3~`zxq3e)^g8g& z#^$*I7FVLV;8x(`Li_Gw6xwI^!RxZm>rab)G|PdVepEZHK~e_M+jzthav~narqNdB zkljPE+J|ssw;zINJUo&vxfiv!ht#?C6r1SMI_Mm;=+UbQ7jzMhJ4^`pg7z4aI8|9kb`;%k$NlAkGmR^ z`83Z|7MB@C!ix1)0W3LnUd0o~WYxMcL6fH^6F>| zVL7mrJr!$Dm4!&6T&CR-WC;U6=6=L?%L?vrzYZpA?f8bP2DFGfdsN9$g@5*1`;;HA%0sq(;+Z z+QETrAY!!BA`3LeDo5O7^Kt1CiVq|#<3t+lA?eW29kiauVud90ES|7B#>5`?T_xle zi!8PWWZ>*%R3$hQLSv}oZ}>;dfrOkmOFSGgtf^$6mO$u(F4Pq8SSq$6G^0zwH&%Yoq4UFRv@ z%NB2@!~qr=X6!~j&nx`z7H+C=nOGJbhAQDp3?Jf4FDU9Xi<*&R#(={8wHUi7*UOFV zyYQkzLQf*w(w;Dl7PUl&YkSGD`pJN+@+Z>lmxA%6Eyi~v+gpG~x4pBU+64HQl)okr?{e!+b?(^bm#-l2j>&3*Pi2h{>!SvP`#_{%W+DCmE*~&m82{pk-`3 zKD8EXQ9k>-d?GPpK7Y^1I2H>pP8j_4u4gP#`X9=TO7ZzskT_Ix8iib}!1pZBi*2GX zxK6<30GPsrHLXg6@MR@zu!L&qR&lwa&cQPFuPDwnK^$u<&!!p08|&i9LgS9ET;6$+ z%Awz-iaW@~H7=Y^my1h@*sQtO!IgFpzN&;@ZEr``bb`tr$Iqb4tw%rRw6C|60|2J~QH;NK!(e719{ILnOv}SaYVT&zepiu>?*T~+FoCPKSytXt{2g6*RDGI!HZ;Zk@6cD zeXNXzc#L%LZq-Fl>@)|Z9T-FMuH}mV^R}*}NQ5*q{X{|S2Gm-2tk}bbeQqIOu1B=| zkL657cI|}qN-kKf`Y-K7TG)vncK=5PNhE>8MFDOR1*{2)hqyoOvqEJV>x!v#Z%on&NAT zQV%fHdchmp32}MxiT)p8lz6c_@}!Q>9_+PL=|23UH3@4!>c`CB=)@RirvA=NC{jR_ zU~TiX1)fP(Pk~9`Ts4?Lg^Gw{bw1K07uo&_nxq5;YY3CqNdx%CA#dV&HQMzVJ2czoA zy#{kw4D+Ov9UQBOvkM`P0aCv#vHql3=X+R~CYq_z&Vyfv1TuAkTa&L}T7Of2tMh@T z?q$0{&vdAu_uc<@HO(n1)7x9QelXWPf;R?ga`zC40Ug}d&r02N)!IP@MxHdJQbKG*{KFDCdFH~i7AS=6tYoJC% zgoAng#CSXkiVV_Eco+`(Z}978_zHPN(5tq`oX z_w{RAg`kQMT&P~SyH0Ityp8Rn;4EKoFT@7qE*1Z)0j`2gQhUMt+kSzaf2;7wm<<{D zo3PWo3-yq0v+dkw-)Ky+>tOda@MfL(lbdn$8y|n308hLiJ$dU~N)~N4n#tQ7675kS>ttR3=R^MS%p&50GZayAI zReF7D_W#t0NAc&{J_?F|pb;OgPG`E%B4eHvo`Y+$axI**aEn3`E`Qc1;RG8cka|J6 zF$ErMw3{dCmzx-C6qGc5ryBZQm2{CsmYf6?G|sx@81(XbbkQoz9oW9;>*&ly;)Ds~ zfNe@V%OeI6MY9tQM8V6em@}g}zV9jJQR`1>=6MHdR$6drRTh0c7LDD!zuEJCgaPsX zKQP8Xg8wS{(o)Y#I0Ma5d(JkA4WN#>h>Z)ecR7+KAAV=1_y>xA zs)yf%72ge9A1e6ZAh;B*A!qH&1swOar#-h=5`$aRW9}7r5XU|m8tAwmmw1YjaWF-k zd55@z<~mUD7@bj%SR$#ku!8E|SKmM;w8gQCI>Vx-5OGsukN|M6F01}VAF9dud~QfG zE+13s$bxzN$FdAdu>Z4S?`pB*wT(#g*O*?9Q|N{k%2i~<+O>T{GsaS=;}!fl+!c#Q z8{D}b7p#i$BjfxD_l?>WWvt@y0D$80DgxZz6X}mgl{#lo^Xv(Vd}{!i*_}964tHo; zwkIj(g)Sz7uif7bUp!rW=w!t?!o}&5BRf7qy!#n?n}k%;OdLMi-fjbBLJ* zS)HZODHiJGjF=a))e7W{d*fzEv?Ff7kb#^Lb`_ua>KkBy@!86FOUsx9$(UBdklYSl zsykVbNr}v9Z2=uuy}V-%H$Lgjou1OnJ{nXwZyxHmu6c7L4v9gyCMYl3U!_7`W{J?h zTk{=bF9SlETE#oc!()=uk}#WCQt4i!AE$`fpRsIBHX*@TWaEhQcg|y208& zG149ed{@lDYi{!0ME1_J(w*aF()LlB(>xk*tt`VJpDfblQFDN8pin$mOT-%DcZ6Jh3o}?0u@rb-VHsbTUC}K$`f zcg3zHK|7> zyd-9w5{Ynuv$G}^QS**I>(V@#hAiImE3!z}DciG_h#r=VwNQ1;!74f;HY7y3sQ`-E zq$1@a#w7(X%o&J8nk+kqOKd>GPV`EP?m5of1;B#QGfu0{2m}CzWiGdoEVkXGE4Fm9 z(qUGZsEw)t!5v|DDqta$%1#-nQAQ#7H4i+O9X;}^aZ8|}f<}~wJQQ-U!-(1=p^0wL zDw!;}a6#||esHD|<#J@Sd!X_GO~5TU#NR*U4RKEk^V1ptIJCc6-jJ1%B^Yy>ZiE1U zhzV2SWfocbMFNEXtIAahe61|!b5QG&v^R(b^D`B`E04&yKTb3dIz106gNq!elpKaA z&MEmgunrdqMcQ4FhUO#D$T}N(p$Nl37kH?{zaRX4JI&TOL_59HdtFnYKBX#~V~`J9 zWNW8N2+Y6Tjm3CDRV!4BOY$grj4e>U-JT|RJVF{?1FhE z!Q?}pk|%*$ZUL$Q1FL&;Mb6UJ54C4bQ|}p|L%_a%+**E5OjNblAS(V>p-2d z9Qf<7^Fd7JS;_(fKN#CLK=1X{UI!Y0y?eiuw)+t@Ct4MLOD##6<}8rHd(kKZOsm za4#um`c}sazW+ZTGNAia_FxdI)3#TAXx><2x@ZHR27|v%cz$q@=F0^ zh@(&L%WkURyYs;kwrKKl_PCj1T@l0@RA6wnRLn8?n8PCt(+?r5trTH*58?Oi{|2t< zn((lll7I1w)!p3snwHaF)&k~6SZVBt*$BFW(mv(UPT^FEx6m3gFLzXo*%kxy62m%j zI&Nb}<;zY9DG4>}te9)gG3=z2<1A%2{2{AlAmx0^u8KU!MMf^FQpou3rr-@-u+dTP z>x3U|?~0Of-r|M1o38l*`LM8%t%(Jq1!*fi_y%dL3&ev`3PU1kxy& zYLcg8CC8xESP}=bazMoR?XGMmTeb!xX$Ju72yzNJyreP-z}Xy?88|5V2quOf-0gh-GbaOV0Xi-mlZpLR-D^l$fz18mI<=eRON6I<87gSAMuOOUJ#K~l;gBr7vyr699M*WXaG zuX3Q)(l6YNzHBc_;O5rgMG2n%uVW5HVd*aDwaFt#M^XU-8x&jJ3nF0C?SjZMC7$XL z|6bp?EuroSQlV>eMPsN(y;i{k&Qkb=77m7IYtu=#iSr}>p;QIW$z-EzvV2Cbk6?$uX&G*3-a~l= zej3~;-aps1rhA{mNnFhm&+&j=ceAE6X3?xo-y2I6H0yCQ)DD9*kBVDldvecxu*GhQ zaF&N40wnDzR=MrMT?|w>g`wXeO1!g2tmwG)p&jqMK^9}4a#2cO+(WU~d}jo=w;*6L z=s9ZO?5Q|!S)BNMuI_la!2$w+u$Lk{Z4m-znYDL)z=pY)2(yFzG#b?|gOL~+?X8T? zH;h{A&SFOhL@^+$p0coM`%y~j<~Wu zNuVkV@ddA>ZWg^8vh1g_TxMkHuJ2h~NP#$15s!2cA*K`sbV#jBP=9mcVYR ztPdb<$VAeN0Fb$!rhA8H?6@Z}26xZ2B4w4l`4+QZJV05`|3(d{rgvR$eb{>(134nTBPY zO%R^zr3w8I5^!Ns#2(jRq4qm?|(-;pqWHRDA8js5%W5K)LID#4)|MCDXwxU3V3l7h@6VQ z7g(r%zR6vv%;Q%IpN0ELCqLJxQri>6g|>Oi{?pUcuOz9%^^Y&D_E>b2Qhmqxz@V~l z?5ZpCf&daPP70zk{k{ScAL*BPicM4sABjHRllrK2O8v_fD)mv&2P$rBkvS(>ur8I^ zDOJLfS)+QJTEfwa{go#^u!#zHN;hM##}Pe7kzO-M@^m`O)+D#OqNT8+1zuex4_g_d zM2O6XeS4RDk5}Y<)`cv4B61$9 z$YBpzBF6Wh!Zr+1iEFK0$0_E2SNmmvl_V}9z>D9Xph)vPBt$Ir7;zq17|un}wBtR@ z5E;}Hl_YLR0`+CGFV}&<*UEC7f-T}zmAhc71+m%%p_+rK#7WAc((AQBoVyZ3-ZQqVKj+efM+)AMOWZGRIqr8TWe|tTPm=JP(V#8O--rD8KQF`NQWv?ZN0% z!)Stn-}QsN?&brlQ=f})Ai+Om9McFzYbpj5G6PIG@x4k$bwi39K%;$>=V1&C8YPg_fx#P)t_E4iEmSSXU@k zB#4D>1?vQae5dHn0;Bk^glDQHF_@~p@ae}s8e=$$%W+m(v@bY{@hNJmgS2$_tVK3^ zuuE}a8pM*WAFL=h8I;z@?etq{mT@j^Q!D845G6Xz;|qdF1083i zf<}2Dc#JnXl?oo{f$MQ1VQx9(9YVjN%R_rDtb$!c@xNJV)D;b66thPv=v@zl!L2No z!3E_}3U2nm5JUnp#=VrH9<3O+1u&Q=0Sn+iEBJ~4xN;Dd%f{(f5aD=5IKe|`%)ee0 zxX1#<IIO6eo~abyed=mTKa-UHECs#if#68C9*+nNmm8e#OcRyn0Y44=yzajc zU(S-a`Nky0yxPNT){7O&66ZRZ@QJHRp7=8FwT1=GRk}m{bQDE4*3{RP*1wzM7bzbPuGbX&k-+cjiM`IF0$Ah03YeHU5mf$&) z=wv_vd07Rz(nD?{GIVZmukN-(B8*298Zm2hTrx^G%F?OEnAtMOWvEa%zusCvk)^YI zij*P-=D;<_nUWfDSQRb^lwpJGJ(g!e&uiTU8e6Dprwua&RXzPeRke(IrNRp=auR_^ zGfRAO=`NX#s+U-0D@DCI9~DpC!*dS!{tH~E{(L0AEram%7O0La6#{Inv?p3xZ(%|= z@R3FB8!+m&uj+2pKPu@qmQ*B!%=Tq$6j>H@bM9#4}CRS&{Xj9SKd8OIR~RfV<*RSfIA=9tzIXRa{0OIon zTv8`j-7|Hi`MpRB3LFH`gA;X}JWQEwv9Ec(mDghI4n zDZc~v2Gycm82zY>ZsjlVBX^@r>0il`aIoZuVL8)d8Rf>+SVuy{&yq~l4FVUjfHGXr z+w{Dt_DnG7^dpbM3*=zPm<~|j|H7=W17+(t3jd_J#fd+_-?`R0KIe|6P%~Av7;2-% zQDPY4Kns2tE#yF<(Kla3o4#VC66{_eJJ?ybe2W=7^>_S^e)&yllEJrcB_Pkv~aO;r`3%A`B7Xt3Q;lZ4B2? zDsO4rVmFA7sf-gsR!50;0+N=YkyzTi`rdD zXQDa-emzJUKSw0Z-pk;W3XiqOvTk6h(S1<1LBT^kFvLQ7h&uGFB$b1b4=T8Ff zoSuSDqYe@NJHGT1DCvWb z=?!Bs<>?M5lPa)g%(=#AwJMA+m=?O}c=X328OQi`s|h-Wj(KBzp*!TjrM@| zK6`b(cHO=Al8;=?=3Dy})a;V8RI?>2Pue1jEX=a_{x|0-43%?QAz^m)3)90?TCG!o zX0EXO3kvhenJUbyD$E8JSrZ#~g3o{;m?+2@B(Zo+vEP1ARK^n#7b0jg_wgP=eh)H> zK)H7Zj=?{b@Ge6rMDWY|pe&6)8Fh?q#y{XHpYcWC!kP8j58MIxMZfkiDZiW`I_u1U zP)Dm!8A~L3W|0Q=V*1DlwqvA$)5iH1N-5$qd539kxpMtPZ1hBY_#L$!t%CmZy#MGt z5$9_%Uu}tk<)4H8Yo;}LEl|v(f|!_%;^j?`!K?c8pvCLcd|H%a4m%JoN8z9|^E>$F z=)>Tfn_;nrJ#uKy1&##4KWbp9LXU5GR;L|3@(eoz3#wi9fmQ*-$w zzV48fU?^6}~oq)4T1>4%`MrH*V@y_RDQ+T=( zJm3-Jv8POPF*%$^>I-;q%@v+um&9P$0=JeJ-n7plwwr?XsX!fma7Qn5`@pQG#~yK#;1c3=-g^3pYJ> zbmfdEk^^Gr+VG*5NxEZ~;>1;xXs2i1c_%*t5ap;+^1-&Vcw}x-#~5a+giccEodM>R zWn5feH6_Y(6=hlgMXFh4PVz4W+~b6reD}aKDsa#Bhsu#I@<+=YBB8sy=+XZ)sGf;5 z5JI6Z;3u9}^KQT*#n3;db4aerU#M6u77K(ZL6Goq+a&HFhUa4`t>H})bCDv)EV8^- z?`*N!#R{5eL7IN><%2Oi0a}*mNJGbty-c;~5=GzNL+1!mUU;0uu`hp=`PtZUf*lyt zq$z4IL(>|$H|V{oe&%;+)U6Q=UnJL%?v2&n8>-n^+<`Ml_s8fGlPSyFf8)6gGr-6U|Mk73r`5l4$H7p&G^7 zE{Mf>;2#b|Ilh)A-(>&DsmVH*%8*P`UT?k)=`?+9MoVa61^#hzYF_4vh=>shC|4@M z-zxYIZ~aHU_K+yNauT4B{CXAbL5nPMAg=@{si33>YKks$ z@B&3$rPNz@;2zDyvKy4(6d!@SdJ0s$t3sHj6lZ@Q4#0hsDe9km&7_>@Ys!1_71l!@ z=c5RMEf2p^0zUkzKMFf(zyrB$+m!XQ+65{Nt#-E>um`KnK2faJ1SQ0Je^zbIvqbb( zoy`rt72I6h9v#H$9^*@9#SP!%m2c^~DLA2r;~l4b7aaS|=LnN`{#j`(DSf;|v>h#wpDWmd0#K)6RV{@lmWX!4u~fT;j9#6r=*k8VX&E>#JJP+IxA- z3&i>zPT(s6pA<{aRcU@hp$M+j2949!rS4GBryj`Xr;tIS>Kc>v(6Rrj1W#K6d2au# zpV$iMoeI6hLg91Wy!bA~nc~B-e#x-ny)2bp9C&^d-9nCF;faIn?QZ3?i{TWjeKJ%t zg@Ah$(BF1}rhB#NCz#6Ez?}_lum1;gr*l-g^AK^9zi9_|xE=6#Ig>vz;dE9rxGvo}D{U*V@zo-FqN_f#0##fGXJ2Q+2s934$T<5W^}&ebV1X4_ zP^$;wpBZPzbG@od@z0Etv25)Y&lOc4*m*2_B1swVys6Y*+s>M{X1~owAYfY&-Woz< z4ewaY2C)4^*@l8_k+X2=;q!?~=HVq$EwFiG$81CMsnY!Gb+E7zf)UcAc|KI~ywG%> z!A@2v!JR!4;0;iUE(cpyD*EIe(WMF=X*9XfXNo?;LvMswO=;(B)Ht9;Q-1gya)EEE zOu$qDexa*uyC_EvXZyd6uFwWn$V-(!sS;z_;mm%n5wCo70dFzwE!bSPm&`tzRC3`;mP2qBW43v_7ie=ZB>WO|MzTTp^dWGn zmdxYd7G_&`IQu4iTBG-7Z+U!!N}+D z=z(xq?=IEU7G<*9Y^ZuGQOM?|81P{NptO#zHT8fI$=txH5~h50E;J2O=?ju&>bcT%#1`25JpC!lkpMR9CwV z6?zY^Jdh`uVkBgR^NzEE;>0lnT~1YSo9B4V!|X1`RgquENe&RbV+%D^vlKJBttzV?vV zv>ho9)NCBczMu=5t zQ$@$-{0TP}_b07SmiEt9>5jqSWZO-_;IOeO(`c1>O=a835~W6L0wtVn4g^0i__u0rC+AhnHCYQ4In6pL*}OL@s@%c{oqUpE4Fm~ zUxi-pha&ZT^AqVu3UePQ%DFCz>^K_Dbo&q|Oq_jD`jFAd+iw0m!DrYCOc@S-JGw?n zS=)vHhM*GyA%w=vscX4%Z}|}n0hmr1=L#kb=8b&(Js3wNJ6Oy}D>JiF=x+Omn7@;^AN;q<3x+lgxrlqB)iKH5!0D#Zsn=LZCYbIHM#Hk{&DhzyMc zl?D_s*cp$PW3vKP#ruXwzEp;dO3Gz zkn|9bs0p%jeCss?VvKQ0rxb0@qdSW_O&&1xf+C}0hXwM|@C)DuOW}RA+}&alERDAp z&1$I*JL5?U4>tsu2}RieenSv{FTszX6()j3C6`NuZ<{Y7U}(qDda#D16oa_MHD`#; zqCVNhgw3U-SV}&NRNrH}L92?L9Z+ZiQ=kW*j@@y(HwaoJ?0zXXrh$?!a^E5&Bf6t` zf&*~>VSxH_kVG&1#`_5@k+=wB$BuOPUh448PTj>K8dSoPO0ZG;?8C z{3Ue*$6=^aU2dtg?j*4b`*2$1R15MVE4_i#B<}66;yc3?x{-ml zMsB1l(tp5E{O1PbAN7$NQD?YO3-x6{fFE#r3c`hofOtWi`p9hOyU9|SnVr8by>zP2 zS*BpdVj>i(9!zL~mKswQS+NT`%+%391mq`-1~hdbj;(Jp9jm^0(ld~CCOirHHSXFdW0%+V$)$%76{otrK!5D`y1#kH^h zYI7XbBUwdqy&-nz0qN+>T8F35X7FN>!|Rt=H#)aRgynKk_{b;ou1L<<3yS1*i|7}S&Z*ZP72iv2!-(p&iipE zyJncVx{Hhx^W{KY;<<7rr3pQ@m5~?D0F1?3;Gr5^@RZliOJmZW?ouN0^&90%j(Ih` zek6oVM+mls0qX=gSy?9Y*}OK#*GSRz22Fd&4=pc3D*hSCO6_nttygj*&IZF*^=(6x zu_t_3XCsM(f(kS_lcsO^iI$AfR{JJ3xxwCOhw=o>r?TlxZjx(SdK11r@Cw2XBU<40 z`jgmV7rv*rqkZ+x`b;FIyzQ%erU|B4ZXYb+7_}Juy51lK_*@`-uxi=JYV{pgZ{kcB zmms}OTt*Xft>j7b1|rnx#=!buuZi@>xb4_l3>iEAbnx-l93V?;?3~(_i;QVD+IpiH z+=t3DC2b7PL>kB!q+%gRqh*r@w3Lb7b(Q=`>c>I6IFE~QB#?H!Peqdrd(W;(CeGNF zHjOZRn}B5cK-|@lTEQic0pFb`w^m7KZsmOWSAxMH-=cC#R7W6_;rP2UXaRVsg0%~H z)7IGkg7;FG6Mtp1+43Tc1d${;+_wwxxF}K7u`PO)GpA;jL%YDR-n@;dl0`pl{(h3< zvPP1vR1#*5?TTSkF;*0DH&boef#fI>wx8Y}`E8W^A+iH7=evJU%q&)|`_e6ZJ54-m z$(XInNb|^-g27 zvHqAnjjU)mZuw`R@ksnA43cZ$X#Aq3MIGbe!p;uIyK6>#HG1N08sXJ=cB{_`c0ri+ ziUd_a!kS&fIoJZpxdnuhd`OR(QFy5hVz{Vu!nZV!nNtZ^mew_a=};hXOu)3NOKN4A zn%{(^e`0Vz;+udJ$*lp6o^@IwYbRQ8%kCzd!R*;qrN+2~(7F5M3cPLr<0h1$QG`3|;%`$dCjEKRjuA`w`$4TPsFBbWf%;rCf@yNte8 zMvn&=@yJK1GZXemgCwv+J_fRq=qP90x7h~)!Zm&`DKUlL7-m8c13gm}+-S10zz+cKQ{R*Qph2+<8@B4?vaPN`17C&-$-yIhIO(W|2uKyBA? zq_Q>Jal;#Ow%AkbsjSRgx(5K70d8XZ2u>5uB~WlK&6uj+g9usL{DtrQ(q^1T_i(#% zbi#CT!pItdp_Mv{V`5I1mbhf8JPENyJK;(qj|#T^!|zMxjagBDOfT{gk(5tY>tUs( zVhD<9+O)Sk(;)b8KI&YG*S3eM$4Indl}9dDqPlQMu%%={)<~^$P9E2gpiy8ki4kWo zM#LkzqRwaJ&V%4+xZMds#xbWCby#IMsf~AvRUIc$!-&+5u?aQvA6clPB}YnioPN|6 zs=+;k{F!l52*((`_Kr`0nMP}y1R`HNM2?K=I`=MZg2=i_I^dmo0aW;|)Zhn8qo3ih zc*z+2;}*>D@NOOr(B%eQQjc~uJAT=LC{!$)iZX+W$u&k!xQRuT%&bF2OO1VNV}ji- zS6T3M8XnzI8EXZz1P+bA^0EM1qp=d(5Ca*u)ChiDcJa>}*LAfouh8G#} zEVo|KML&*=CSHpUhGQTWgLb&mZsyXW`NHfEus>d9&(ky02lBg&E8Z@bSc=o^MN5lX z@HIPlX}qN(v8WTOM;a506IsHtfN@%-eHcE|Bx@Ql+PH7}oBCr)ODVyDD#1-Qw#Os& zMQlGv+po0kTs+M4dkYJ3m6 z?lp!Yc{AxqMgH~)m{=~?K{~U#RVt{>g7gwUsG+{=Jv!>2Nh#88*Tyft5LbZZ!P{^{Lvw}IGj2Pr<}%JCrFT~N@aTQQKPDyr``pVH0{)) z?vaMi1#*CB)RzNHz=~R+hJU6i$f&W2(Ut0>r@WMCgT2tJZ;XyoVLmX%U09Ca&Qe@h zgau__j#h+!xYoxGHpRR~)|wsR4^HMJpi9aZJlZ$=2%q3nAo$$;aA%GSEzI^sug7Vx zoXsuhlwxWc98hUHFV*;iSjFP!?Ks>~(5jUg7I9(yb2N|$0xkrBC@R_qyD)WsF~8y8V3YHT++ zYj>iG>==JjL*M+XYFvi{9PxOpooF*qyl#sylmmX+k8^8pN#X=Kn7{!SL8Z#OxHJ4B zY)1b=tGFe`l_5o>FbS#b&<7mHS1`$zf@0;OiEt%atX|3RJ_3ia!h?M6R-R;dBf<@1 zQ`;8C!s?3+{j9#iFJz*Fbd$6kzns}PP-**GzrzZXK2ihe-q_tf|H*4ES1XI!!@TA} zzUCd!?A0MA6prb4JR}3hn0~i@?I9D~YcLQ^{98~#yQfAR+6UTw#oqOz5>}xBUxjHZ znwh_@7oJs2T{NX2!I6Jv0m?9&M1Wu;a%a+`6>#p?Dr3lvyAa5yh9+1E# z4<=}>D}lG7P!@iGZyn*mJ?1vil&Q4i9mYg9KauBCnr~1N#<8V@sZQ$$#K${0r3?xN zkV~FkK=ga0M5_hT#<6J+fIPhxs&rXkTJv3rv8oC zSM0n9{gB(?_H`VIRUHkhcy_|;TG?z#5>DlK^L5kog#^D3{ z)<}1eHQHByxa{x;Xlh)P>d$U?)B$GF`R|TdCB|%N-au$BaJW{B=}t@Gl;w=++%D=5 z|I#Q$j8M%VqA7;>j1$5t+j$22R9rM&rHMs5+SF&=DWm+XWqWih`#3bqES~u% z7#Q_U^1qg4QtVq7pezmrGfu{(fdHwYN8O^$V@RVy$aB=K^ksqN&~|*S#{^=_JsGz1 z-pZ(r`HXaUf``ivH#8l7a4b6XmO*MSj=7)Ib+O3BYr8a!>MAJ0TCMZb=1i4>|2Yj=S)?a~FEBv)+dFWOyO5LAiZvOAWp9Hyz*%kfjQM zK@@tK8pnRF?xFo55j_f}`eKOPr7T!Z7z1Bg%4^ivN}2Q8j0PD7FZ5IpTm~?bCYZ4x zxZI_NG*Yt&7s-2#M5vK`P?4v>^S`PF;es06(BVU7Ds45$`80r6txJPKU}|ueYETGl zH4qolc`+v7wG#?w1@U{S41ycphooP7$O^XF0f-sB{Aja+$6*QQy?3Cnmld2W)+vRu z%nFqKy0d}>ZM0aytYFT20C`!#N>p>v6}-`x74Rk{^z@>94Bb?e$T+>AyisW0v_aunPXf3>|>O0ytfR3otfQL`mt zRR$O30J&Tt_gILWP>D2mJOf8g6JeSn)EWev1tU#%iY^W7z5|ElQ;`OlC0TobY&&t3 zaSbela`N$A@__?njZECU17}~t0zETt3Nl{=WEtKyS)IU*<~Bp8|B(V|&Oa0O_5rTR z<4a;NaXpLpv%sVt6TKLJD=@4L2r0ObvNsfOG;-H7tnB&%xMFjIq6bMDF^8cpXXe^v z9oK8PQAYMQg4`C>8{t5B^{9>u8GPLAo&_VkrLrUB{*{>*nxE-dUd!5cT zO_`X;p03~>V?g92ZC5sOWfnq6sQQr0X7H+bRxUM|Z@wXz+>3P?Mh?L327HS$ z!X=4rM(Q$G+z!rygH)R;;BKkojN$kz%fgK>tJavym4n!3 zk~y(d`_JlFIo)0WrwTYq%ZML$iuFNkh8kC&shaTCyYe7oX=~&w_MQVv_c<%)L9P18 zS2&mUmFAYi1p{7j;q%Z2*luFN@QDVl2j?l0UeKu7z88@0EBIQRuI?83ia)rm_^Rbo=b# z3jW9smMvh$>1X&Kp(y|Gqhu<}EQhFq?+Snk3^`4xRHQi&{>NTjjgk00%l}cTA!7N% zEMEvqra{Z^zqP7yARw^(mL9Ui@`a&th%o}!onVn>NYaQ^3)Vq;4R!}9(I>YCOjEoT z{C3ggApAT(EAq z$w*aD??X_>qKw-y;HARjEwb5~@5G4!@b(I>%mdHBa^P%MNXU=&_6aG|LX8$%$_r~q zJ5L5%t;rg>qRH^XgOE3Rv9d`_L>ES^oWa{tn?xrDGW%4>RCpGNaHG#!6mU`Q>U`at zKx#7vZdWehmu_{Lx1(_ z3fmCsEi?o-5v-czfYPEeU@=l+kBTX?jvAUty7t1;xHRbGy zdJ00_LbyFAq5F#aANKN%^AtGa+`<{uu}HRL@KWJ_%vqOkk8k1Kb3YSigErS&0$zYK zA@EuSUE+ZX+;bmaVCE|njVr++mOx5SQ)q%&3faj*;AY+Kx35#2kPqko;C}ngGKr+u zDYIwHWRm;WUmt1jUk}!3Z`;#n-%{8&2m7kyAaXz51O>Uw={nVAY7e*HkFa4v@G3?Jgity*Q8hSa+B6r1?qjgvZzzd&I;cF&zbT{)wN(zGJ7w^GjnN5qM&(i3IgHG z#pO&_lZRy?VK(1`{F_y_WqDK20yCLdyOeV7GU!3(pi61v^0`h z*+^c(-`+f`$L6n(<_YjOS(`tO$XK^1^U7jiY#7~G=2=s#o^*xmVuVz>bO({5$hFeBeb;${d_<>4}%0k`P)W5F%MiCB8RkdsPKK8yFmr-L=-CE>0Pwtkhrv z#SPHDULfd%2&R+!Xi^d*hiuvSY1QJ3tDLNkLfaBS-bv24^jb@&f z;25J7qvTRAII;PHS%Y_O;OIi&4Mm4y{pge%Q~_gfwE|aQMW==xyQ7nI2R1o!ykjZa z)VrlFK%p=93;h!+Q1#LTp$So;PcuT}DW_Ky6dJQeyySRMnmDoff?2Z@1VfsrN@^l~ zBQ!&;G4OUK=DN{k7o|H+g+|ze;gWa9VEHP}9D>}jj0RqLTdc2WKnPW0Q0P0SRms(gCj?H%x~vMpa)7=Opo@yIhpSy0)v994`CI)rKcU1 zr35yHiauT zjuP=C5L}$cHQMlqere{{c*}Y0W%x)L0e~{j#}AtC$@DZ*6OBr^ufZS#4-z|xNP4Y| z5dLh%YZSDnjI-by4*F+UWj`~sw-w^IMpiNMbx{?;G=e;bQid5w_R0tGbd}tV`uVC@ z?W#~sAJU;91a7Nzj?A$ATm>&xSz{A|63I_9n9e|M_3h5As0Ag0$`Bv%j(9%36^x!7u$%%RFX9It!Uh;8Mg(6yGVO0apkZiruP)C2ARJlrMhA2tPvpRg1x| zq{QlJuP({ztgZlMGOP9bAQ|3xty5fbW28I=2 zsbT7N`p3LZF`a^%uIScjAL(=jI$g1zof@WYr+nbCe@wIaov!TG=}lO%>mw`C>B{x& z)G&2B-8-+-rTLvA(&c&YHqz-TbZVm5?|QFc>UR1PCV^kmmi$g700*YbL)g&z191&+ z++OFDF-&czoky|9EGB3fJ`waJ_t^Cwq!kq^i?{M(`dTufVeHT~0_k2=nQz-yDXXZx zlH=a{g8Trn=Lue#^OgzrT?&3nH6F@}{^oQEiCt*!asxZFGCg>TeJXET!w}#)5%twP zfEkni%s6-aT{v*sIBo!+0I?deDd~1!ODa4MJ00}I9ciDL^0kK@X){6)K;_oe_%%2cl*XN%4 z*m6YDe#jbKyV*3A8L!%}%e#)$F}?{>U`=bOhXPp9Lb#!cIzQUJ(Wui(;!8o2zy z9f3=ERGrsRvd20p4(79T#8adNMS;2e05ec@xzZ|I`W5jLn$kB)?2 zUhbmz({QaVHTB?YPzDAT==5wmzZcwqCZ3xB+1z{^8$r82G`IS-*1=7&OOYKUUhgMA zk?gZo<4!4H_2cEOwrrHO*azMReb+}*{a9Lu$OFz4rv@*021je4V)?Z~zJ2@LKDtUrE!=ePLPh21y3R8@Eddv(~|F?RQNxb_n(9Xf<< z?s$ugHNfg{AT;zi9B**($`|94g#j@n(8_Qe0x>XSyD5_>#O1Cam(NxAu>Op zkFE&r9>VhNd2ohsE85cuP%NW?6QH>nc&t~X8OA{TwY(RU8z0*Kp9M5`7NAiMhW>+* z$#3V|u@D(AD7Nf$o$ewhuLFrd7>Md03v_TDJ8*F^!cCW`Q9j*`Jzs=FqdUR5eYpn) zRgT1c)(%8e?{O!QWJC+LHH!4J{kVz4KOO1HME$G}YY<_Lrj&X{K4`KyEBUsD#Q{H388E2O^ zajM`IrmeQJ354@E(xgVLnT|x@r+4jfn?@j*(*xE*uY9Z);_1fRq)a6=?A!|ah93i< zJ!D;@!PFTyxPap2Wma+bxm(HbK@2JwdoEze3c^G1yK@*-+kp_&PO1kh zh^`*)$3|N}@NTgRe}_=Pbx)Y^7Q;``;Oq=pJnc%?If7(wMvqkZ5x6dR_Le^3C-j`1 zuDwPC#PkX`UMXLDu+!GRVeFBW?nr0F`*SQZ7x)a?&ehCa8HG=@u08xpnp)>Gd$>%K4^(T zD?R8PKB$}pPK5`0(6|rE7d=9w-X8QKA5=gSnj-+7*mjeF;7_kHxpddEBtr zT5%4rEQ}j#Mv)rv?n>aELy^>vo3#=h&XC9FI~8!{=QeX4C>fsnekim+#tYNo2pk*m z4rNQg>+WA$T?Lsn*c~rv3Y-?2l1K0Y|2W+sXWvx?cCpCG@ZYZ}LYeN-{&)n=#iVUh zjNTr`Ok#LDiaGo#YdX?q5b#CYu-jX*MscSUt|A5>kdO#R@HOpxwPQEckL^5y8Ftn@ zr0!Oa`m(pkZ|JEKDl*O~h|@$gkE)w&5cT@(E-;^S>83s#e)l1$weJpm^(3Ibv8m@&1Jskm%Qe3U-JWA^B`aIjcE48a@b=6WkMc0`x%{! zT^t{KlJM+z;G(lxy|*zYHJ}7-8hmhpe8An)_zMLMH%a3XfIprfj? zw&6=c3CI)&G$LxLXrmy643H>jMhJ5t0m5XcR4Rj1szOx-0)oa4C=sG@!q`rzsHn8k zDi9ShDki9?agH;#sD+@?j!`l7Kl_}0Zrxj{GTrp|zTfw+4Y4Q8DWSm#vI`2jMdb5uw=MP{8I zr81qbtk7vm0{A+el{}>vXR?7xm`QPTasG@^knbQ+`fkoxeioz`T0$ ztS@Vvz#1=^HR@O+hcwQiLH^Lr{w#W@S@doeJqw~FNAdjYYm|PAf9k7fA0Bi_vbp@+ zU!SnWQJaE6D(!eGt|*-QuY>@Ks};;PW)2``Z(^27CiO6Tnb}UJk0Rz|$=nO3pP3&J zvnMe}NM`VMk-DUv#H0;3|FM#J2%7Dzc^5I!BWeC0`w55IgY|78O=&_Z9J-(D~ls2VPl^&5OCKa5*r9&X1rdWMi?xH z;QFfyk?N+#yn)j2Q|h$sK^dl08}Nb zp{MO^@XIg80KA{{Xz>Gm>VZl15=Z7*{7-!)@{IB!QOYcORU-HWXH4(AXgBN$mJZ^s z^N(Hufv&n$TQ&f__GXeJet|UEw%VKJ_VK6q!QC>XQ1x9fDg=229Ve5GjX>O7upXwO2@XEoTmrPNH8-Q(R7<~6CEYJC3)!p6q;w;*h6d^LbW z8XrCWu(9*--BVUD8osZ_d{)%&uyt+S*YFFRmyf0jZ&|>?xJ0^j28rO`C$^8y*qe6slzElrU3*QL$H5o})D$>zN#oBv>&-N|MN z+k8XXJSBq73p&}HZnC+OZEiUpHjiVQZ%Uglij_#`ynp%6(7gYCzp>mWu+8~oljh@; z_qU|Y$_O@Z=w$N&lg$BavnSbH#5UiSHcyXW^P)~RXPRt&da*2{$Fca{eImy4K=zM!|?u9tp1)< zKR<#?`JG&H$teU^#q-&vSFpW6-h3d<9T0P#tTyg(kXbRLeaQJ|F8su&c-p(y?Cnq8 zN1bI5Swj_1dCoH!Kl}1e`OHpGRCF<~(h{X^!2SiKqT9inb)sVAOUuYZ8G?^!ac}CM z9=KP!`;l~LjKLw@nQMh*;fxi|RP#@EMu-VVvZNym*%2_pI+5SWiNSE9-rs&qn3>1( znqdI1zyRKW0ldERi14NCm1sn$D&k6oQqXXiXCKSIlo@W98J?ts<;7h6Vqe)ODpE*L ze+S@(+y_!$yI`lYIaG%OqUN!Ap_5-*&h$wEEK<^W%K z<+|~?ls#R^(&~xcyHs~6*VJF6>-Bcdr!YpBP{jmvqyW2qA!S zJJ2J$Dwle3EEQ*8NnQ56dJ^;Uue9-mg{tg&4m} zMt5eQ(cKT54bo(MI~Fw;)RSC5%6(WUeO7XM&CGZtG~WI10Rqi0WjK!IuJ=j&q|2s?}4ohl!*C)%p1RV z1Q(%kVi8I^0<|;STCxtyywknmQ}#m9&o`8O44vAn!)vn+>&sV@W^>kWT4e2d)B|YO ztUN5=H1Qkys^4@&f6(T3$*z*xoE{Gf5e{>kI|@^SX8tr}tSpAl#55E=e}tDzi6T2m z%5D$Jq8>+RH3OyWS|!V^rZS&OO>DH)SzL-KK5B-$ejn*}AVn1GAgMh?2~bn1tB4WB zD{tUIGr>EHUi06Ent+$nMS9#@jMzeP3lPx{O_i)eVnh-l-hzio4l$Rn8BJ6vQY@-^pd7aJxQsqr zxaG+nklcwsQB#|~6ZIF_mm?Qlje*0xl{$?$>Rt+pntCb=DBo2?O$2Wspsq=DH1}X# zzp4puL_l8oBGl7!D6y=z1I3~=awu(lZ_m~4&(q24q49FgWw6|x0kzVQOM#CpxL#SuOR?_6g1+%$0u54mL51e0is)^VmJK6uW>g~4`u0% zU(J@ckUTl|I;hacbu%gbGC5Xf`9}0}mKWr&cZk$lddNiIf+Q#UI-fRM-oV)@u=pA% z9nBW{4n=gc-8jL4xtY@VV42i3`D-DOT1%Qt>M}?Qr54MjIHgpZ zEh}(#EG#YqrK8!h2xqvd+)^Wz2SK^nQi&gwum_Y5@E?0ZB1ejZ;|$?EAG(y(XizS>p5VaTNNHSlWtO4x*BBzT z7JB5W*)kkINSAnA6n&(dXG!T3q?@PFuNKR?m%3@uB5eoGsMZcAk83TL<0pRQ5UI74 zk%<<|P7p4tr}(tlvIA$a5Iu`zYAxG9nk}2~pR5hNLDa}61o*~_(zn+@tw#rf88k4u0v~Rwp@uHl&>1lAyW5;?LvAbiPc%;vevScezjP3?e3;oAl7WT z9cSdp*|Je>$4~rPL!{Pn9hqpcoDDCyZJvo!SuY0TjN0ZDnaru=X^UkN?9GBST-A8wF79F#tEV||Er0FjXi;Ea6OL^8FOUx?IMo)xPI z3pogB$|L)r^3g29O+L7#5A<0nh?eUQaI0a@AhNj~L~efWt_n=L=% zjC|NYGPRa}5y4mPJ{M)d^?~xpKB#MXPT!Uu8?hAEHigUUy<2qpP&=Ho)~2PwK(_hBOYkRg5e zQ1U5{Z0Z}F%KGpv&d5{)$<$i*6KS!`(oKzEQ$wVw*Cqc85YEnLeA;ZGa8q{bNT$~E zF%jH+azJE<>Vq2UgRj9lmTD1h?CNyO8s#g5yLHOFSLovKWg`L zt>qyhLr&d=v!ELbYgcaWy!!q7Jn~=cy+0^VBg`a6U{a)Rro!~=JgVn)mL&$ALZv#k|pycbt*eoq5TU_w!ugecRfs9nboE zGA~8)J}~lPn0LP9Jrm@WHGQ;sSC41?C)c!}w&Y|O(t+A0{kDO7F@ZC&o&NGFM1*Oj@s&J{P(N_-M=ZLB$_T4@ zIm&+x5VW(b)?t!$chF9~VsUl4D#&VJ)+VM~l;{YYz?=C>bS-}pO0Lx}k=!d4w z5Rv>MqIH%`;Yipqp`wud(6>u#MXtd5@Eu%zy^0anoy`&Dq6L`=b8W%tE)1 z)!rKU_SiN0LhmXX%%GJloqtYg@n?zX6$JEN_}V_LB$XniZeuCsDIV}J@5;zeVwpB( z9(+uAS|KVd9+H5y^QE<^A=Z#@f2t|pRYJ*_@A=Z2WXOD9AgyUC2S@;WX;dOB4G>Gc zKTAeOVe%Ob};uop3mdli0HkyU>hS0!-TD_62 z!qkTSXJd&hD#Tc6Es3p(a=Sb=T=DJxL>AwLQX^VvbS^&Hy147NFk4Tv4;VVRh1hy( z#9k{F{xieeKvN^_Q;*UxQ$~;q5{ZZG{p{vj330oJXtzpOv?#pEojP0jb*!`ClfP#; z$98^Ya4e4Y#A++|(UusJY^NmAKHJt#*#~2OX=^hy5J|(0Krv9m;7hLq5%(vw>lGjS zx`xY+Qf{r~+<(AqxX%3;Fv4~2zo2h}14x6mjX*44GGgf5KWYu>+@BXR=#DH;;7oPy zH3xH4_&ayzo8-0#g(%Ns{xwzwfKnny3U!i1~bQ}5k{&h7^tbn zt(BSko-R62pN?(a?w8@Mv}1qC#TITk#%;QP9)fjJ@FgV(=o$%aR}k%A(MK+7pL+rv ztq~U~>9ys5^g_Uo9u}R!CcMhN`x1H`_okgf3wykyK4g~YDCo_Ca8L{x*U5A*GNfCx zmsT>GCO;P!`q^8QSB&8;gr$^d|D9Rupd~NoY{AP7-~1yLgZU%klav_QegJ8I4RX>n+sS3WYk_g-S@5gNf%~{*U$o-!5 z=>erco#M0YN1>7V=o2>Lhu$cEd0zxiO03O%0glMny)UiUl@%mAS3(;M5cl_W28b2U zRS;6F*?SRsgGP!Sec%M|&smeT-Cqcwv8}s3bPkyOg&{ZP4j~Sr3H&?NIHX3MK)2J0 z9agT!Tl8w|riXq+mqGIts>b57(8H9c%Vf$+3@QI{MQ$`#DLjB)N=aRK1>+k*>p%3Km zp*y{o>@$?6sOp_d@-CS#D#`EXaB#y`^QK>@B%cu_`H^(=IfDYs(AXDH5PAtw*PikJ z1VwoF3<6-fgs|tcpm)2fQPA%^)vclV%&y;LED_3%_Agc56nC&Ejfj}8hdVfc|O zo?&-WEb-84E$vuPk@I5_w+8cqs)VK~UJ*teV58oKlkn&lshmivxGt(x0KqmvU-Lwl zq`1kxub?mn_hD&7qU`)5N;H^8cTVF;kr|p>gv>`#flZUqQ{+7=4b%&f_YJ!DOOco` z)0j%m4$Z1IO$s-d?Yu4T_b^@yStG86(A=b&Ci$2oHj4%nyjl{J*dUhyvUfIIIf2CP z>|Wndt3f7tZdt%gs0%<< zjPZ~CIm{g4ginLDR?c8Lx2b(a`ymENm(e2z!(;)i8LDuCB%Z z0lI4cqY&&gU0J(VcSU{wOU^qmV?hR{RA<)WneY{u>2!86H|S!XibPl!U!)rzz_~)Y z_!kz&Q|4rthGG#dx?72+`HJDW#>erypX2!FVx$T+ISaf%hDyV&{$iYE+yJ8+qY-N=Kn$%;vuxN93|;tNQEru0DZ=q2TD_Y(p_ z7VpON$DP$Q!bSJLwu$;u2)rTH9l<0G+ZAMK$RM6q8`0>miv{P0LQ^(oaCGubzTz(h!cm znm<%7nsy>8{u;qVH&l=1YqVIt*6*ZCz0?SozCtx_s?>%A7GbDwE&dlpWg{Flj0jgY zIt{ICHLWPG{yT3XA6PnA>9EL^wXSnDx#~_)>mpT?(ja@y>S?e|8Gl!1{410k$0S^? z?!q$;a7ZYL?abYHpgbzO+p{VuSg z^>vt0tMmH-)H_RZgNmolxInbZjqXH&+_LXEo-=ILp!qVaqriqCAA#t0Xc*+X< zTiG#yFi$}=#JybY+1+4SSXxW9MleZ6sEJpLA*C2>7zGnq7e-OXlCyS;^ z@hg(25#nc)j_LEstuk063`$Q(Xpn-`JVtz|w$XnLSqKxqqPGn36RviS-<+VEeU&I+a!@q%A3pqeA^jAJai7I{j@`hg2f+J2>yL!L@o+UI|5I> z8R}{Ht+}l-H45q@!Gv4MC-W?@4-!&)7CWS;ml+gz*|kVPszTxgqu1lDp3nF%fIgZH6&Szm*x}^u_d_x_FK2Uc%b?M= z%Q>*3Hw($HT`hGru982=_X+bJYhwCW+zubF3~_wQU0 zK9MFVC>yi>s8+sC_uRCI=`Eb0wqDItZB6YOr}V{Gs1 zA3dj8aqAh9`-4xo?Fu%@EwH)Fz`S&db0Pjr!;A}XFXZXLq;PbUGfd!Sl(xH!y0+RdwH|C@2xg|@jyNW(|j+z}AcrfRo-m2i=_ zk?BztG<^*d)`6(91F57S^Z<6%^xmZ|DHZ`B_#4`Vqxp!~9TIix0|8O~#7kwja9Ql5 zYYCKX&HOlPw-INBqbpu|{B$D(d5v20|wbZxBg`Va^^yG9{IN1$OB6LEo zyL55`(%As@&ypoPL3nf%6|CUMdrJ}s6wM$>!hq|^F=og$aRd5bapm+}SuN^QDJ!TJGI_l{Txj%8 zdRCY85a)Y3dsi;K+f1NqM4=WZrW;~JcA}N5ICgU5YQj?p+n`BLG@4=Uq>8T8=h@U(yl5I_wTEAY% z$*G|NgC$AS(pEhhfO-f+7A>wO$eQ<$a%qLj<#_gtYYlA(nD67EU4LVVe2(UT1b}AM z%g<6EC$hH)hGM76k)fOCP~4+fxG4#t=*zu$hRoDidZuV8p0%YVYZY>jkA+;0A`>wi zAq5YQRk;`UG;E-7#!1$v%rXql^iFLQYl~8%me+q(Mifx!WlHV?%thy4i%;)?5ic3_ zN)0WQF5d}fh@U0-70j=Gn!N1Re|0h8;E$91oS^;|3@fr+x9n8q+GB?(5z#fS&01B{ z9<}6H(Ch~NN$}5~qI(m81RuDO&5fS9p_^5FXZ`_O^i)O?YQjs8x};l@ zBz)(gKS+f}4Xjf{C%XiKa(kURFY2#hoQesZG2Zvmhc>BjE}9u_Qmj=klw&`rI*QdEaT3BSQGjp+?? z*ArD-Y(P;hjxp?c_No(UuMzKtrm_rVr?ldp5>$6;AJzD^!s7pIos_D@Hp!!y@{6vL zo6`$pD3-y?0S#R>r}bR))F1j=qD3%0SWM z(+GPbS?|#u-8(3()YqIP19+Qq70}rds$~eH!q2T|irc#VOQDQAqS0VYdks_nEf;Ho zE}mydjyy3J@ZD`D7tfLY{G!?^Ts&7oU+8UglekJ#`|O+lq#MxR!Lau-R)DoxJN&y~ zOt@@*(aWvR1*5vF8XI94H!ADtVsGaW{!N(-#dgPN5#|W7-Ty9{INZyTUC6acSZolt z_}|zHw&=&ufr#E{UNPl#GlhWkyfRzclVu{9E5MN8@ZFHNGpAXLcwOO0V-VDj;p))okFrU3ff!R{Ca&wO*F^HBvpll=epp`4NULPgB03>?pmaNCg zPxRF)#Jxp`AFo;YxUlrNZpjHtXcSRoe~UgG%>ueM>+#@Vgnm>Xi3Wz}=x@d`_5Em0 z@&Qp=429S9ED-KfC>Rdy! zQ^?KLyI};C^h7Z-uWqAXEvvDe5uk!AZdr|c9B`Ulx4MZ~$X4>(q#*87-)SYUfE|8I zU@;iRb{@}|pD<}^zN{UOC5jY98f>693tvJ;rSJkNd}SwLv7$PP6;gr<7fXei;S^$a z_G5*fK?Sjht+Nacrx2?zIo=KWZ<8r}pJ4D`ULUPnO?l>)IrCFJ6LzWbtWjfiJ0hrm z{Px8+A*|F}{7ty(fYKllph6NRL~uVvw*(9oF|ae0v9+Wv<5eP)nq^FC?3STeldl(# zudQyBHseTNjD0AuNkRQmsegh|e>Dj|DTM2ij=xm;Q4|cwB~s^~@y39VgV}ZRYWZ53 zs=N;Jp}4XxD%B#X_<&JywML%x2RI|puNO$)N5uid6CPZ9b#Nvx%;FM-( z7|imj6z`Wu$4{0X{#iT{PPgr*Q*^fN;`<$iW98C9jLAZk$WhWg5GM=HpauHcsNZL@ z;F1(O_1`m_!=LOZ%0Qr+l>Taoitx0_i)VH$wH@L)yN7~8%=Scl044`6G zD*UgI>NzHL%pj7^K2N6pNKickkbk>LeX&$O#-xrlK+<+ruL!E|BK0bhx<{(N5ogT% z2B;@Sp7$?E{a1{{l=nIe6{<;8wdze`iL{qt zuvbURg)ntYFLMy=8X&!;pTb>AK}fy{p$B8>2@9 zTKuov1#x*LGkDW~Oy`jRivYB-tV8~*pAq>xM9L2V^v@;$mo5XXoszxg36-q>2IM<( z0{$8@k9Y485sw7mQYaFbNWa6|j@NC|+wN;u9>zQiI^v~tpq6-STjE6|wU*Oy0vlMM zSkA_&f0?jpx%+ZW)34yBcFwTuC&a|}G*QDM-NiKdRrrYM&2m|c--*VM)#kqkwZ?x3 zn4QA16us?>&-XtKJ9^*uKlRtN*^}$WK3}P6X|pEOzLPd{LhX-fd1;ozJofZ|iz3f{_V;MtX(K=Ae_Ayns!gu_ZgTB6nYCZ01&;qDyXN_B*(o1YewAIb ztLMB0Y4g(-q+Og=@O*Lc@cyTXjSG?9^U$Mo$tFqC?>~<}Bk*Sg{*1t%5%@C#e@5W{ zoe1CsDZ-iaXg>|dh*EdCV??3DXB#oxUg~yx9j>zS5k9xuX)m>vxx6Fr^Sm>Q97`O| z5zey05#D89pQC()%~R$pEqD0J?B3y!(8jR2BeiFDmsh~1*X@$_JU(YyQPJFtJn7{T z`&nWa?iD%gGaSWQrPq_^&Tu%L8hjp~QR(tHil@3Wmf4*S4H+9}^Vyu7;1R~9m`sQy z)4X1X$5-ZdWjcx-cAw_4m3bW!{BPo}tjaJI+sd4dB58F(c?F!OKR-0)bWM>Crl|`Sm?c8n%MdB9|Nl?h8}LjdcQ8e( z&u!(>2r7h}9m~I&rn;9nJm*>69^|0N;VE{ymxd^W{5*qyGfmCYo86Fdx7UaC?GBgE zYPUI^R;SJDJ0~Qd$Fn}uR3YiLy35OaK1b0x*5Sj4hiI4`IE{ZYP0gJ!e!|qe^Q{wH z=*c{l6)<8gTxQMid7S5EgfWFndE#J7`ng%-XTbpa=MtL}y@}gnD{)wDcDuvt9c~?x z2FJ=Pd@{H;S}Bmh6l%Um|9B4PX+$o?@U|HkEH zL0z9PSdmY+Cyc7lJel>Giug>P9L%1(f;#;0X_M+0)@Mrk=`(YK`u}Bkv=i7qQ?j2k zfijp=R^hPPotQusMamng_Gf*jsg>)U=!l)=DoGs0iEn z*6|qy4i}9MqUKR;sq_J~mU*o^!JSnFikze`Kf9CHY7eFewcOS$aAa_!#8*mDn%#5vAI-w#LS(lb#dTy1rFk`mTV1=LLVu`~y&2F!(D037|ag-nN zJQKr|TP|^UL3f1twdvinoRf<=X>Qqay|x^o-4c5NmUiVBDrl96(RsYBvV@Etp*`CY zr_bYX2y3NfC8f%oR^f4A0qGd$_K1m(iG-QR=WSqUDO$;mdJ*%V_MN=%-;Z zv&4~0b8oH8Yy36ztl)C?Nc{|5&jweXTtAO6|Gb_>&T<{T@eZHH-LzM$z!))sI&+a5 z8vDyINC+4!X^&|&(g|VGpN#nB4`L-8r3WtrXx8% z4?wYg_R#oHSwUGiLjT>~V2v&J7NRYb+bSwhJh={=$6l&A1y+nvDYvrBN5fn?+*l-6 z&(kV>(~3oFIpXxo%Ztjq6{7m^m{;y7cYBr{t+~A-$2kb27eDem%M8Df|BOnHhthUB z1+LkOin1NHipgaNp~tbf(&6<@^NNUiwaSVjL@vkUSR$H`EUlbMAH~tBc`KY{J|P*e zazDXEn*~%w(-9Jw7VJFt6dO8}DuafGtqPlmggo$ji9^VZ!@@;M##7BsFDnr+og2#8 zS?2YjCTK)w2xSt;Msw7q)7?IwyId1Rl(xh<)m?;rkwuQB(gE@d1+kR#PxT|b7aGYp8(KVHXTZ^4q0q7u;W zJoikmM|B`7Ddd%QpMv{PyoKa)RQXUhjOrex{%vz&y@B>r=0aB@{!w*6|1-mZe~1a?o1-_?J%Oa;!TSYbeO!c% zf>Ns!)r8i~HhU?iJeZr?tVNE($`UM0%A7^kA@Ol>an|BWm)Ja%%jI*1Tj#hdaqi-& z5++5~rPxuno>z1}f|7xjkx9lUlkKr%(1Bhk);UPU>0E}rGW@x*QRXfyLSl{zByX?u zcrmnl;Q@B2ZL|oZe2veblAqxyL9gfVj=MXvObJT zlr;i1Ez4`SRX8vj*gOb-J|1P+j6dy$Kp+o)oA7rSe>pb>0txH?^S_N@^nSQ05TL&e zOx16qlfz&}{{J7(+Yy8RSNukSPp@=i%JqMszZX5PW3k+-_iAIuVkYRp&X(q~l{>U@ zZwUrGw_W^jVwJBIW2GW~iG66yV7R?^BE7mD-FHCLDZTr}9DBk^(Wmz8bNrzG$HewH z?&K4X8ffW$n$ul^yV*?hvmB4R#A7SRU{W$-DlRTz%8KQM%ZFr5=M^;I(4^cu0u#eh zOw0|}2WWE^Bjil9q{&%FX1~lmqRd@TSXKfXy5%G6$3w?e<9Kic`Bfn)@RgOzp}~+} zQxVkgFQ%ym|Nq@|(X*Ffhu1#BVaHZdksI^hiqbM~c>!%1ii=l6Mkb_S!5fDKZxO=f zn^06`9b=v0uwz14G<>pH@=kDN63e^LYVuFj8>atL{>6{%>>ny{h%@jkbpL0O$yeVLjK_qy1zOT6VeJyuQ2pFT%vq1U|~y5 z?V?4qHKwW7kPZ6K{hgc;2RrdY9PGpwtv{HBPJG<)YdkvnH`HomcRK&ysb2}B5>dYr zX~U<|H!p4hZTOs#6L&`E`W3oAnGP8>}ChuTNNs3e2`BbEQcHFGAy z`yWz>hP?c5`9I3wf4tHEc(eZo*TiWb2AHODc~H(prj|3G>Hk>#M;rW4F#11IxT0qw zeEdTii6N8!Tk%ge_@8L>KS6ih;8pPS58^K}^}iK=8oyNgO)~l~;vYE^;r)-NLi^wJ ze{`I||4~N&r7IB&fv;<8OilG)&qT1eI-TJrW^pk6pK2eYY5Z2@GuoIx**=7;;WFuA z|Cs)d`Df^VsPo4#P~dT3^HhxhG(V%sD7&K_!91+~%e_(`dt09!KDB z@EQT;=cY+COdf$f!+gK<{CD00ayCx$mX;O!W?-kE9Y2C}41Qy;qEJr#4R-#M``$w5 z{Y7O<;>CXLe^Cz%ali`DZu5AS6?iKP3*ER*zErH^0Bb|XYM61CPpNeJ%5t$UVe^Rj zI9=Jm1q);R|C9U#VqCsInrDc5n(@HmokS;HubMZ~v3HnfLL;ru-C}7mEu+KL6eIn4$bl>t21U zzf(R$PlRg+WiH(A7G@2He04iH`hp+uc-tvhfFxhp5g5`7^s0_P4Cu%;9f2Is4WJdE zjiB|Q_S%lX7SKLdcLZ8NZvj07TDul{xT_^$T}L1e^mWi2&@bzt4?6vtj=(z5r$9G> z{s_7o^vr8J0)EgnpgntQ+RvavK>OF@u@lhCuI~txf?jY#M_>);9iW>)``!qB(6d1O zpiiww`WTph1dRhd^Jb(Ex(2iYbOUHTXkr8W2mJ)J74+j<;6LcSe}g`5NVxu1=!4#h zPu=E#F1Q`~pu0iqL1*3pebDzoTR~Uf34PGv8=&7$)7n7eK!3Um`k?XmKp*sZ(0b67 z8=(&xb1(EkH-a7lJ^w!F1WcQ=Eci>Ge)fc}Q3%?^V0!=q(=j@7hfcr0=l=uLPAax!S; z9_WMGUx7Yo^sCSZ9gHV=_key0dJwc0?{4dJ9LniU=!1Uv4)j5c;2z}7XkD-6OrrnM=gbxG#c`x)qzx^llL9g*cAGCHq^g*8l-2+;65c;6a z_{c<`0h;Fi0s5e){|J51s9&KE+6KA~)cYIsLGQq4j`x7RhbKP|g5KRN5a@#&es+Nl z1Kk*ny-?68-2(w9XnT)9U>)e)cpz^xXj`v9U=QfFqoEI)(Hr`>;Vq#L^g-VNoeX+g zU+9Cr0=f?L$9~WUJ^mQzgU&x5`k>bjfIdAJdm{8fk3R|eps#^CK`RDAAM~O@&niAGF^v=z}^z_ka#K5Bi|jjfDQmu%7^ZP#5TA(B4VV2R#+n zYu16z1>FqV3c3gM0Iug81id&J`nX~72GC)kpQJz^^o$Fk5BdSp(9D-3oos&4NDYTeG1LdUQVYLGPRgeNfkY=!32U z-3@x&0_cNox)}PWYubGU&xvJmy~t8smUI=#`+GK-U&SA9Sh{`k=po_B;dq zd^z+%D?l?rKLITQo#?`YvY=0cZUpV&4g_|Aeg(P@)K?J*bU#zmwt51A!Jw_6nV`RT zp$~ej5Bi|Bpc_Fisf0f0=}QBFy`b-dYJ=flRUlvm{T(zF^tk1Lz(UaVpw*xoE)4{3 z13hs?Ag}}Uyp@5#hoE189tJ(-@<1RKbEO+WlR@ua9SG!uHmnH*szBcWZ2&#FHV|k8 zop*I0&<1+KxYoQLLc;c(0tGbL90NAuZKS9?VydITS41EkGmE6pqoKs zasSk3pvj=UZ-YMQa?mQ!>p>ep<8FsO=p~?SpabrNK4|;~=+h0xcS9fa4$yqiynCS! z`Uz+QXybj*2Yql8^g+`ffIjG&N1%TW`X$h0(37`7AG8^?3UuB-pbvV~qtFN40@?;T z=P~GmegPVLF8Z&>p%1!$EA&A>eG2-Z+nS&cdI+=;H04?7gD!gx`k;fJhyGCXTcF9H z%bTGO>evH)&}z^I(5pZjK|cg-1NDQpgC@TM{bA_WL6bq-LGwXNU&Zw*P&&}vi=*4} z8Ctihm~JQc>Cv;ko5ax-Xx7U*0wYKwvj;{-j8*tc#9z;q9k{g~C;ei^^^2X@zxUFf z)!Ib^FE}@0FlsP-$KQPXy$5|a0crhWu8hj)dsNgVNI~*f03LFAM*v-Jke@5qLjN|v zR`4G(@}ut4^>+Y&2>b_3{Ea&QL*R+k9f3L%e~r#R4E!$e51IG}bp9aZZ}Al!fd@_e zdv*R8@P7mU4io4OIIeMuB0K>quH_ru)!X;b=*dipnl{}A|g6aQkJ|2X*HfFH@fx4<8ZIeIw% zsQ&%{eiHcWL3RJeuZynf9(}cN5-|BWC|c9*!JIx)oX3FwH29I?JRkg*!H*Q@72wBU ztq{&nO8++SdxC!>&UHVduGQnP1N;@>M`|A*g5LvckAO-4S6%-w_+NnEV&Xri^9Oa; zv^uO=zBcjwI)4oKbFp^mh5DrX7u{XdZ}NXW_!(HkJZI8>TGw9z{^#H?GVzOb{%zpL zVeJ#C{q6w&cJNo5^s9CK55bSRE^>ShgI@=Jr2b=&1!Ec3ToKzp_#41K)s+8}g82uZ z)@amE1miPW=dS?&NvzqfGx@(x_y0EVhu+j7KR%`RpIdeQ4)9+9{|1x(H9G%8@RP9y zj8uOPgTER4^(Ou6bp1g`VJ?QX;$tTM!#aNq_*=n`6u9Oy>um*|FU5I!C!|p z=Rx=p-F?^fd2~k_nG*2>HH7D&)v`w7-~xYY(4$M;9rcj z>4zr%yE=bR4^110wd&0#``7FC$AEtw_`{6-Nc3Oyer`Vaw_{BkssC63{&w)g)qm;} zZUa9GYv4v>`Z%Z9G=g^rczsO1_t1TB1OIBQo8LC-Mz!j`w}XGtrjEd^CjNSzAB!>i zN$^J-`5wU*=_iB#I{26-1#Xne2b9+iwT|j@=!B&rJFs>-w?1HO+>78+sLVFn?$2{ABRU zUhW9YHSuTa{Cx25!G2G>iT}OMuLA#4?El

ihnt_k9iE|BC&e-%S4hr2F3p{@ZVL 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 j{`o)Z5&i`3=Y9Xd Date: Sun, 7 Jun 2020 19:40:54 +0200 Subject: [PATCH 07/19] remove binary --- constantine/elliptic/ec_endomorphism_accel | Bin 142328 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100755 constantine/elliptic/ec_endomorphism_accel diff --git a/constantine/elliptic/ec_endomorphism_accel b/constantine/elliptic/ec_endomorphism_accel deleted file mode 100755 index a5326c3206b5710d6f103035b7d9031bbfedf8a7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 142328 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

ihnt_k9iE|BC&e-%S4hr2F3p{@ZVL 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 j{`o)Z5&i`3=Y9Xd Date: Sun, 7 Jun 2020 23:45:54 +0200 Subject: [PATCH 08/19] Add the GLV precomputations to the sage scripts --- sage/curve_family_bls12.sage | 42 +++++++++++++++++++++++++++++++++++- sage/curve_family_bn.sage | 42 +++++++++++++++++++++++++++++++++++- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/sage/curve_family_bls12.sage b/sage/curve_family_bls12.sage index 984c30115..adace597e 100644 --- a/sage/curve_family_bls12.sage +++ b/sage/curve_family_bls12.sage @@ -36,7 +36,47 @@ def compute_curve_characteristic(u_str): print(f' p mod 12: ' + str(p % 12)) print(f' p mod 16: ' + str(p % 16)) - print(f' cube roots of unity: ' + str(['0x' + Integer(root).hex() for root in GF(p)(1).nth_root(3, all=True)])) + print() + + print(f' Endomorphism-based acceleration when p mod 3 == 1') + print(f' Endomorphism can be field multiplication by one of the non-trivial cube root of unity 𝜑') + print(f' Rationale:') + print(f' curve equation is y² = x³ + b, and y² = (x𝜑)³ + b <=> y² = x³ + b (with 𝜑³ == 1) so we are still on the curve') + print(f' this means that multiplying by 𝜑 the x-coordinate is equivalent to a scalar multiplication by some λᵩ') + print(f' with λᵩ² + λᵩ + 1 ≡ 0 (mod CurveOrder), see below. Hence we have a 2 dimensional decomposition of the scalar multiplication') + print(f' i.e. For any [s]P, we can find a corresponding [k1]P + [k2][λᵩ]P with [λᵩ]P being a simple field multiplication by 𝜑') + print(f' Finding cube roots:') + print(f' x³−1=0 <=> (x−1)(x²+x+1) = 0, if x != 1, x solves (x²+x+1) = 0 <=> x = (-1±√3)/2') + print(f' cube roots of unity: ' + str(['0x' + Integer(root).hex() for root in GF(p)(1).nth_root(3, all=True)])) + print(f' GLV-2 decomposition of s into (k1, k2) on G1') + print(f' (k1, k2) = (s, 0) - 𝛼1 b1 - 𝛼2 b2') + print(f' 𝛼i = 𝛼\u0302i * s / r') + print(f' Lattice b1: ' + str(['0x' + b.hex() for b in [u^2-1, -1]])) + print(f' Lattice b2: ' + str(['0x' + b.hex() for b in [1, u^2]])) + + # Babai rounding + ahat1 = 2*u+1 + ahat2 = 6*u^2+4*u+1 + # We want a1 = ahat1 * s/r with m = 2 (for a 2-dim decomposition) and r the curve order + # To handle rounding errors we instead multiply by + # 𝜈 = (2^WordBitWidth)^w (i.e. the same as the R magic constant for Montgomery arithmetic) + # with 𝜈 > r and w minimal so that 𝜈 > r + # a1 = ahat1*𝜈/r * s/𝜈 + v = int(r).bit_length() + print(f' r.bit_length(): {v}') + v = int(((v + 64 - 1) // 64) * 64) # round to next multiple of 64 + print(f' 𝜈 > r, 𝜈: 2^{v}') + print(f' Babai roundings') + print(f' 𝛼\u03021: ' + '0x' + ahat1.hex()) + print(f' 𝛼\u03022: ' + '0x' + ahat2.hex()) + print(f' Handle rounding errors') + print(f' 𝛼1 = 𝛼\u03021 * s / r with 𝛼1 = (𝛼\u03021 * 𝜈/r) * s/𝜈') + print(f' 𝛼2 = 𝛼\u03022 * s / r with 𝛼2 = (𝛼\u03022 * 𝜈/r) * s/𝜈') + print(f' -----------------------------------------------------') + l1 = Integer(ahat1 << v) // r + l2 = Integer(ahat2 << v) // r + print(f' 𝛼1 = (0x{l1.hex()} * s) >> {v}') + print(f' 𝛼2 = (0x{l2.hex()} * s) >> {v}') if __name__ == "__main__": # Usage diff --git a/sage/curve_family_bn.sage b/sage/curve_family_bn.sage index 02801bf57..9e6af87b7 100644 --- a/sage/curve_family_bn.sage +++ b/sage/curve_family_bn.sage @@ -36,7 +36,47 @@ def compute_curve_characteristic(u_str): print(f' p mod 12: ' + str(p % 12)) print(f' p mod 16: ' + str(p % 16)) - print(f' cube roots of unity: ' + str(['0x' + Integer(root).hex() for root in GF(p)(1).nth_root(3, all=True)])) + print() + + print(f' Endomorphism-based acceleration when p mod 3 == 1') + print(f' Endomorphism can be field multiplication by one of the non-trivial cube root of unity 𝜑') + print(f' Rationale:') + print(f' curve equation is y² = x³ + b, and y² = (x𝜑)³ + b <=> y² = x³ + b (with 𝜑³ == 1) so we are still on the curve') + print(f' this means that multiplying by 𝜑 the x-coordinate is equivalent to a scalar multiplication by some λᵩ') + print(f' with λᵩ² + λᵩ + 1 ≡ 0 (mod CurveOrder), see below. Hence we have a 2 dimensional decomposition of the scalar multiplication') + print(f' i.e. For any [s]P, we can find a corresponding [k1]P + [k2][λᵩ]P with [λᵩ]P being a simple field multiplication by 𝜑') + print(f' Finding cube roots:') + print(f' x³−1=0 <=> (x−1)(x²+x+1) = 0, if x != 1, x solves (x²+x+1) = 0 <=> x = (-1±√3)/2') + print(f' cube roots of unity: ' + str(['0x' + Integer(root).hex() for root in GF(p)(1).nth_root(3, all=True)])) + print(f' GLV-2 decomposition of s into (k1, k2) on G1') + print(f' (k1, k2) = (s, 0) - 𝛼1 b1 - 𝛼2 b2') + print(f' 𝛼i = 𝛼\u0302i * s / r') + print(f' Lattice b1: ' + str(['0x' + b.hex() for b in [2*u+1, 6*u^2+4*u+1]])) + print(f' Lattice b2: ' + str(['0x' + b.hex() for b in [6*u^2+2*u, -2*u-1]])) + + # Babai rounding + ahat1 = 2*u+1 + ahat2 = 6*u^2+4*u+1 + # We want a1 = ahat1 * s/r with m = 2 (for a 2-dim decomposition) and r the curve order + # To handle rounding errors we instead multiply by + # 𝜈 = (2^WordBitWidth)^w (i.e. the same as the R magic constant for Montgomery arithmetic) + # with 𝜈 > r and w minimal so that 𝜈 > r + # a1 = ahat1*𝜈/r * s/𝜈 + v = int(r).bit_length() + print(f' r.bit_length(): {v}') + v = int(((v + 64 - 1) // 64) * 64) # round to next multiple of 64 + print(f' 𝜈 > r, 𝜈: 2^{v}') + print(f' Babai roundings') + print(f' 𝛼\u03021: ' + '0x' + ahat1.hex()) + print(f' 𝛼\u03022: ' + '0x' + ahat2.hex()) + print(f' Handle rounding errors') + print(f' 𝛼1 = 𝛼\u03021 * s / r with 𝛼1 = (𝛼\u03021 * 𝜈/r) * s/𝜈') + print(f' 𝛼2 = 𝛼\u03022 * s / r with 𝛼2 = (𝛼\u03022 * 𝜈/r) * s/𝜈') + print(f' -----------------------------------------------------') + l1 = Integer(ahat1 << v) // r + l2 = Integer(ahat2 << v) // r + print(f' 𝛼1 = (0x{l1.hex()} * s) >> {v}') + print(f' 𝛼2 = (0x{l2.hex()} * s) >> {v}') if __name__ == "__main__": # Usage From fbcf21901ebd100eb0f3c580c5c4da4bb41320e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mamy=20Andr=C3=A9-Ratsimbazafy?= Date: Mon, 8 Jun 2020 13:52:35 +0200 Subject: [PATCH 09/19] You can't avoid it, bigint multiplication is needed at one point --- constantine.nimble | 10 +- constantine/arithmetic.nim | 1 - constantine/arithmetic/bigints.nim | 9 ++ .../arithmetic/finite_fields_inversion.nim | 4 +- constantine/arithmetic/limbs.nim | 26 +++- constantine/arithmetic/limbs_montgomery.nim | 43 +++++- constantine/config/curves_derived.nim | 2 - constantine/config/curves_parser.nim | 2 +- constantine/primitives/extended_precision.nim | 16 +- tests/test_bigints.nim | 27 ++++ ...vs_gmp.nim => test_bigints_mod_vs_gmp.nim} | 2 +- tests/test_bigints_mul_vs_gmp.nim | 143 ++++++++++++++++++ 12 files changed, 268 insertions(+), 17 deletions(-) rename tests/{test_bigints_vs_gmp.nim => test_bigints_mod_vs_gmp.nim} (99%) create mode 100644 tests/test_bigints_mul_vs_gmp.nim diff --git a/constantine.nimble b/constantine.nimble index e20b0d584..2fc633987 100644 --- a/constantine.nimble +++ b/constantine.nimble @@ -59,7 +59,7 @@ task test, "Run all tests": test "", "tests/test_bigints.nim" test "", "tests/test_bigints_multimod.nim" - test "", "tests/test_bigints_vs_gmp.nim" + test "", "tests/test_bigints_mod_vs_gmp.nim" # Field test "", "tests/test_io_fields" @@ -92,7 +92,7 @@ task test, "Run all tests": test "-d:Constantine32", "tests/test_bigints.nim" test "-d:Constantine32", "tests/test_bigints_multimod.nim" - test "-d:Constantine32", "tests/test_bigints_vs_gmp.nim" + test "-d:Constantine32", "tests/test_bigints_mod_vs_gmp.nim" # Field test "-d:Constantine32", "tests/test_io_fields" @@ -209,7 +209,8 @@ task test_parallel, "Run all tests in parallel (via GNU parallel)": test "", "tests/test_bigints.nim", cmdFile test "", "tests/test_bigints_multimod.nim", cmdFile - test "", "tests/test_bigints_vs_gmp.nim", cmdFile + test "", "tests/test_bigints_mul_vs_gmp.nim", cmdFile + test "", "tests/test_bigints_mod_vs_gmp.nim", cmdFile # Field test "", "tests/test_io_fields", cmdFile @@ -245,7 +246,8 @@ task test_parallel, "Run all tests in parallel (via GNU parallel)": test "-d:Constantine32", "tests/test_bigints.nim", cmdFile test "-d:Constantine32", "tests/test_bigints_multimod.nim", cmdFile - test "-d:Constantine32", "tests/test_bigints_vs_gmp.nim", cmdFile + test "-d:Constantine32", "tests/test_bigints_mul_vs_gmp.nim", cmdFile + test "-d:Constantine32", "tests/test_bigints_mod_vs_gmp.nim", cmdFile # Field test "-d:Constantine32", "tests/test_io_fields", cmdFile diff --git a/constantine/arithmetic.nim b/constantine/arithmetic.nim index fd1d78c4e..5a857166f 100644 --- a/constantine/arithmetic.nim +++ b/constantine/arithmetic.nim @@ -7,7 +7,6 @@ # at your option. This file may not be copied, modified, or distributed except according to those terms. import - arithmetic/[limbs, limbs_modular, limbs_montgomery], arithmetic/bigints, arithmetic/[finite_fields, finite_fields_inversion] diff --git a/constantine/arithmetic/bigints.nim b/constantine/arithmetic/bigints.nim index 9501cbbba..bd38b0012 100644 --- a/constantine/arithmetic/bigints.nim +++ b/constantine/arithmetic/bigints.nim @@ -194,6 +194,15 @@ func cneg*(a: var BigInt, ctl: CTBool) = ## Negate if ``ctl`` is true a.limbs.cneg(ctl) +func prod*[rBits, aBits, bBits](r: var BigInt[rBits], a: BigInt[aBits], b: BigInt[bBits]) = + ## Multi-precision multiplication + ## r <- a*b + ## `a`, `b`, `r` can have different sizes + ## if r.bits < a.bits + b.bits + ## the multiplication will overflow. + ## It will be truncated if it cannot fit in r limbs. + r.limbs.prod(a.limbs, b.limbs) + # Bit Manipulation # ------------------------------------------------------------ diff --git a/constantine/arithmetic/finite_fields_inversion.nim b/constantine/arithmetic/finite_fields_inversion.nim index 661a8846d..a59706b9a 100644 --- a/constantine/arithmetic/finite_fields_inversion.nim +++ b/constantine/arithmetic/finite_fields_inversion.nim @@ -24,7 +24,7 @@ template repeat(num: int, body: untyped) = # Secp256k1 # ------------------------------------------------------------ -func invmod_addchain(r: var Fp[Secp256k1], a: Fp[Secp256k1]) = +func invmod_addchain(r: var Fp[Secp256k1], a: Fp[Secp256k1]) {.used.}= ## We invert via Little Fermat's theorem ## a^(-1) ≡ a^(p-2) (mod p) ## with p = "0xFFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFE FFFFFC2F" @@ -120,7 +120,7 @@ func invmod_addchain(r: var Fp[Secp256k1], a: Fp[Secp256k1]) = # Note: it only works for u positive, in particular BN254 doesn't work :/ # Is there a way to only use a^-u or even powers? -func invmod_addchain_bn[C](r: var Fp[C], a: Fp[C]) = +func invmod_addchain_bn[C](r: var Fp[C], a: Fp[C]) {.used.}= ## Inversion on BN prime fields with positive base parameter `u` ## via Little Fermat theorem and leveraging the prime low Hamming weight ## diff --git a/constantine/arithmetic/limbs.nim b/constantine/arithmetic/limbs.nim index 627595a18..1feed8b15 100644 --- a/constantine/arithmetic/limbs.nim +++ b/constantine/arithmetic/limbs.nim @@ -8,7 +8,8 @@ import ../config/common, - ../primitives + ../primitives, + ../../helpers/static_for # ############################################################ # @@ -258,6 +259,29 @@ func cneg*(a: var Limbs, ctl: CTBool) = carry = SecretWord(t < carry) # Carry on a[i] = t +func prod*[rLen, aLen, bLen](r: var Limbs[rLen], a: Limbs[aLen], b: Limbs[bLen]) = + ## Multi-precision multiplication + ## r <- a*b + ## + ## `a`, `b`, `r` can have a different number of limbs + ## if `r`.limbs.len < a.limbs.len + b.limbs.len + ## The result will be truncated, i.e. it will be + ## a * b (mod (2^WordBitwidth)^r.limbs.len) + + # We use Product Scanning / Comba multiplication + var t, u, v = SecretWord(0) + + staticFor i, 0, min(a.len+b.len, r.len): + const ib = min(b.len-1, i) + const ia = i - ib + staticFor j, 0, min(a.len - ia, ib+1): + mulAcc(t, u, v, a[ia+j], b[ib-j]) + + r[i] = v + v = u + u = t + t = SecretWord(0) + # Bit manipulation # ------------------------------------------------------------ diff --git a/constantine/arithmetic/limbs_montgomery.nim b/constantine/arithmetic/limbs_montgomery.nim index 42b1d3f68..76edde4ac 100644 --- a/constantine/arithmetic/limbs_montgomery.nim +++ b/constantine/arithmetic/limbs_montgomery.nim @@ -120,7 +120,7 @@ func montyMul_CIOS_nocarry(r: var Limbs, a, b, M: Limbs, m0ninv: BaseType) = discard t.csub(M, not(t < M)) r = t -func montyMul_CIOS(r: var Limbs, a, b, M: Limbs, m0ninv: BaseType) = +func montyMul_CIOS(r: var Limbs, a, b, M: Limbs, m0ninv: BaseType) {.used.} = ## Montgomery Multiplication using Coarse Grained Operand Scanning (CIOS) # - Analyzing and Comparing Montgomery Multiplication Algorithms # Cetin Kaya Koc and Tolga Acar and Burton S. Kaliski Jr. @@ -168,6 +168,43 @@ func montyMul_CIOS(r: var Limbs, a, b, M: Limbs, m0ninv: BaseType) = discard t.csub(M, tN.isNonZero() or not(t < M)) # TODO: (t >= M) is unnecessary for prime in the form (2^64)^w r = t +func montyMul_FIPS(r: var Limbs, a, b, M: Limbs, m0ninv: BaseType) = + ## Montgomery Multiplication using Finely Integrated Product Scanning (FIPS) + # - Architectural Enhancements for Montgomery + # Multiplication on Embedded RISC Processors + # Johann Großschädl and Guy-Armand Kamendje, 2003 + # https://pure.tugraz.at/ws/portalfiles/portal/2887154/ACNS2003_AEM.pdf + # + # - New Speed Records for Montgomery Modular + # Multiplication on 8-bit AVR Microcontrollers + # Zhe Liu and Johann Großschädl, 2013 + # https://eprint.iacr.org/2013/882.pdf + var z: typeof(r) # zero-init, ensure on stack and removes in-place problems in tower fields + const L = r.len + var t, u, v = SecretWord(0) + + staticFor i, 0, L: + staticFor j, 0, i: + mulAcc(t, u, v, a[j], b[i-j]) + mulAcc(t, u, v, z[j], M[i-j]) + mulAcc(t, u, v, a[i], b[0]) + z[i] = v * SecretWord(m0ninv) + mulAcc(t, u, v, z[i], M[0]) + v = u + u = t + t = SecretWord(0) + staticFor i, L, 2*L: + staticFor j, i-L+1, L: + mulAcc(t, u, v, a[j], b[i-j]) + mulAcc(t, u, v, z[j], M[i-j]) + z[i-L] = v + v = u + u = t + t = SecretWord(0) + + discard z.csub(M, v.isNonZero() or not(z < M)) + r = z + func montySquare_CIOS_nocarry(r: var Limbs, a, M: Limbs, m0ninv: BaseType) = ## Montgomery Multiplication using Coarse Grained Operand Scanning (CIOS) ## and no-carry optimization. @@ -255,7 +292,7 @@ func montySquare_CIOS(r: var Limbs, a, M: Limbs, m0ninv: BaseType) = # (_, t[N]) <- t[N+1] + C var carryR: Carry addC(carryR, t[N-1], tN, C, Carry(0)) - addC(carryR, tN, SecretWord(tNp1), Zero, carryR) + addC(carryR, tN, tNp1, Zero, carryR) discard t.csub(M, tN.isNonZero() or not(t < M)) # TODO: (t >= M) is unnecessary for prime in the form (2^64)^w r = t @@ -295,7 +332,7 @@ func montyMul*( when canUseNoCarryMontyMul: montyMul_CIOS_nocarry(r, a, b, M, m0ninv) else: - montyMul_CIOS(r, a, b, M, m0ninv) + montyMul_FIPS(r, a, b, M, m0ninv) func montySquare*(r: var Limbs, a, M: Limbs, m0ninv: static BaseType, canUseNoCarryMontySquare: static bool) = diff --git a/constantine/config/curves_derived.nim b/constantine/config/curves_derived.nim index 4cc8ace18..5b68e2589 100644 --- a/constantine/config/curves_derived.nim +++ b/constantine/config/curves_derived.nim @@ -32,8 +32,6 @@ macro genDerivedConstants*(): untyped = # "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 = diff --git a/constantine/config/curves_parser.nim b/constantine/config/curves_parser.nim index 064380b5e..1c2891662 100644 --- a/constantine/config/curves_parser.nim +++ b/constantine/config/curves_parser.nim @@ -11,7 +11,7 @@ import std/[macros, strutils], # Internal ../io/io_bigints, - ./type_bigint, ./precompute + ./type_bigint # Parsing is done in 2 steps: # 1. All declared parameters are collected in a {.compileTime.} seq[CurveParams] diff --git a/constantine/primitives/extended_precision.nim b/constantine/primitives/extended_precision.nim index 22a91f68b..ae0bc6f90 100644 --- a/constantine/primitives/extended_precision.nim +++ b/constantine/primitives/extended_precision.nim @@ -12,7 +12,10 @@ # # ############################################################ -import ./constant_time_types, ./addcarry_subborrow +import + ./constant_time_types, + ./addcarry_subborrow, + ./constant_time # ############################################################ # @@ -88,7 +91,7 @@ when sizeof(int) == 8: from ./extended_precision_64bit_uint128 import mul, muladd1, muladd2 else: from ./extended_precision_64bit_uint128 import unsafeDiv2n1n, mul, muladd1, muladd2 - export unsafeDiv2n1n, muladd1, muladd2 + export unsafeDiv2n1n, mul, muladd1, muladd2 # ############################################################ # @@ -126,3 +129,12 @@ func mulDoubleAdd2*[T: Ct[uint32]|Ct[uint64]](r2: var Carry, r1, r0: var T, a, b # result at most in (carry: 1, hi: 0xFFFFFFFF_FFFFFFFF, lo: 0x00000000_00000000) addC(carry, r0, r0, dLo, Carry(0)) addC(carry, r1, r1, T(dHi), carry) + +func mulAcc*[T: Ct[uint32]|Ct[uint64]](t, u, v: var T, a, b: T) {.inline.} = + ## (t, u, v) <- (t, u, v) + a * b + var UV: array[2, T] + var carry: Carry + mul(UV[1], UV[0], a, b) + addC(carry, v, v, UV[0], Carry(0)) + addC(carry, u, u, UV[1], carry) + t += T(carry) diff --git a/tests/test_bigints.nim b/tests/test_bigints.nim index 2a0ca5259..d6f36f6b1 100644 --- a/tests/test_bigints.nim +++ b/tests/test_bigints.nim @@ -138,6 +138,33 @@ proc mainArith() = discard a.add(SecretWord 1) check: bool(a == expected) + suite "Multi-precision multiplication": + test "Same size operand into double size result": + block: + var r: BigInt[256] + let a = BigInt[128].fromHex"0x12345678_FF11FFAA_00321321_CAFECAFE" + let b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF" + + let expected = BigInt[256].fromHex"fd5bdef43d64113f371ab5d8843beca889c07fd549b84d8a5001a8f102e0722" + + r.prod(a, b) + check: bool(r == expected) + r.prod(b, a) + check: bool(r == expected) + + test "Different size into large result": + block: + var r: BigInt[200] + let a = BigInt[29].fromHex"0x12345678" + let b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF" + + let expected = BigInt[200].fromHex"fd5bdee65f787f665f787f665f787f65621ca08" + + r.prod(a, b) + check: bool(r == expected) + r.prod(b, a) + check: bool(r == expected) + suite "Modular operations - small modulus": # Vectors taken from Stint - https://github.com/status-im/nim-stint test "100 mod 13": diff --git a/tests/test_bigints_vs_gmp.nim b/tests/test_bigints_mod_vs_gmp.nim similarity index 99% rename from tests/test_bigints_vs_gmp.nim rename to tests/test_bigints_mod_vs_gmp.nim index e55df0d83..de7fb018c 100644 --- a/tests/test_bigints_vs_gmp.nim +++ b/tests/test_bigints_mod_vs_gmp.nim @@ -148,7 +148,7 @@ proc main() = # Reexport as bigEndian for debugging discard mpz_export(aBuf[0].addr, aW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, a) discard mpz_export(mBuf[0].addr, mW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, m) - "\nModulus with operand\n" & + "\nModulus with operands\n" & " a (" & align($aBits, 4) & "-bit): " & aBuf.toHex & "\n" & " m (" & align($mBits, 4) & "-bit): " & mBuf.toHex & "\n" & "failed:" & "\n" & diff --git a/tests/test_bigints_mul_vs_gmp.nim b/tests/test_bigints_mul_vs_gmp.nim new file mode 100644 index 000000000..fb0993602 --- /dev/null +++ b/tests/test_bigints_mul_vs_gmp.nim @@ -0,0 +1,143 @@ +# 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/[random, macros, times, strutils], + # Third-party + gmp, stew/byteutils, + # Internal + ../constantine/io/io_bigints, + ../constantine/arithmetic, + ../constantine/primitives, + ../constantine/config/[common, type_bigint] + +# We test up to 1024-bit, more is really slow + +var bitSizeRNG {.compileTime.} = initRand(1234) + +macro testRandomModSizes(numSizes: static int, rBits, aBits, bBits, body: untyped): untyped = + ## Generate `numSizes` random bit sizes known at compile-time to test against GMP + ## for A mod M + result = newStmtList() + + for _ in 0 ..< numSizes: + let aBitsVal = bitSizeRNG.rand(126 .. 2048) + let bBitsVal = bitSizeRNG.rand(126 .. 2048) + let rBitsVal = bitSizeRNG.rand(62 .. 4096+128) + + result.add quote do: + block: + const `aBits` = `aBitsVal` + const `bBits` = `bBitsVal` + const `rBits` = `rBitsVal` + block: + `body` + +const # https://gmplib.org/manual/Integer-Import-and-Export.html + GMP_WordLittleEndian {.used.} = -1'i32 + GMP_WordNativeEndian {.used.} = 0'i32 + GMP_WordBigEndian {.used.} = 1'i32 + + GMP_MostSignificantWordFirst = 1'i32 + GMP_LeastSignificantWordFirst {.used.} = -1'i32 + +proc main() = + var gmpRng: gmp_randstate_t + gmp_randinit_mt(gmpRng) + # The GMP seed varies between run so that + # test coverage increases as the library gets tested. + # This requires to dump the seed in the console or the function inputs + # to be able to reproduce a bug + let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32 + echo "GMP seed: ", seed + gmp_randseed_ui(gmpRng, seed) + + var r, a, b: mpz_t + mpz_init(r) + mpz_init(a) + mpz_init(b) + + testRandomModSizes(128, rBits, aBits, bBits): + # echo "--------------------------------------------------------------------------------" + echo "Testing: random mul r (", align($rBits, 4), "-bit) <- a (", align($aBits, 4), "-bit) * b (", align($bBits, 4), "-bit) (full mul bits: ", align($(aBits+bBits), 4), "), r large enough? ", rBits >= aBits+bBits + + # Generate random value in the range 0 ..< 2^aBits + mpz_urandomb(a, gmpRng, aBits) + # Generate random modulus and ensure the MSB is set + mpz_urandomb(b, gmpRng, bBits) + mpz_setbit(r, aBits+bBits) + + # discard gmp_printf(" -- %#Zx mod %#Zx\n", a.addr, m.addr) + + ######################################################### + # Conversion buffers + const aLen = (aBits + 7) div 8 + const bLen = (bBits + 7) div 8 + + var aBuf: array[aLen, byte] + var bBuf: array[bLen, byte] + + {.push warnings: off.} # deprecated csize + var aW, bW: csize # Word written by GMP + {.pop.} + + discard mpz_export(aBuf[0].addr, aW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, a) + discard mpz_export(bBuf[0].addr, bW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, b) + + # Since the modulus is using all bits, it's we can test for exact amount copy + doAssert aLen >= aW, "Expected at most " & $aLen & " bytes but wrote " & $aW & " for " & toHex(aBuf) & " (big-endian)" + doAssert bLen >= bW, "Expected at most " & $bLen & " bytes but wrote " & $bW & " for " & toHex(bBuf) & " (big-endian)" + + # Build the bigint + let aTest = BigInt[aBits].fromRawUint(aBuf.toOpenArray(0, aW-1), bigEndian) + let bTest = BigInt[bBits].fromRawUint(bBuf.toOpenArray(0, bW-1), bigEndian) + + ######################################################### + # Multiplication + mpz_mul(r, a, b) + + # If a*b overflow the result size we truncate + const numWords = wordsRequired(rBits) + when numWords < wordsRequired(aBits+bBits): + echo " truncating from ", wordsRequired(aBits+bBits), " words to ", numWords, " (2^", WordBitwidth * numWords, ")" + var trunc: mpz_t + mpz_init(trunc) + trunc.mpz_ui_pow_ui(2, WordBitwidth * numWords) + r.mpz_mod(r, trunc) + + # Constantine + var rTest: BigInt[rBits] + rTest.prod(aTest, bTest) + + ######################################################### + # Check + const rLen = numWords * WordBitWidth + var rGMP: array[rLen, byte] + {.push warnings: off.} # deprecated csize + var rW: csize # Word written by GMP + {.pop.} + discard mpz_export(rGMP[0].addr, rW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, r) + + var rConstantine: array[rLen, byte] + exportRawUint(rConstantine, rTest, bigEndian) + + # Note: in bigEndian, GMP aligns left while constantine aligns right + doAssert rGMP.toOpenArray(0, rW-1) == rConstantine.toOpenArray(rLen-rW, rLen-1), block: + # Reexport as bigEndian for debugging + discard mpz_export(aBuf[0].addr, aW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, a) + discard mpz_export(bBuf[0].addr, bW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, b) + "\nMultiplication with operands\n" & + " a (" & align($aBits, 4) & "-bit): " & aBuf.toHex & "\n" & + " b (" & align($bBits, 4) & "-bit): " & bBuf.toHex & "\n" & + "into r of size " & align($rBits, 4) & "-bit failed:" & "\n" & + " GMP: " & rGMP.toHex() & "\n" & + " Constantine: " & rConstantine.toHex() & "\n" & + "(Note that GMP aligns bytes left while constantine aligns bytes right)" + +main() From 2e47d37e58059455c55ebd31ad99d2b3e411ee3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mamy=20Andr=C3=A9-Ratsimbazafy?= Date: Mon, 8 Jun 2020 20:07:30 +0200 Subject: [PATCH 10/19] Add bigint multiplication discarding some low words --- constantine/arithmetic/bigints.nim | 19 +++ constantine/arithmetic/limbs.nim | 88 ++++++++--- constantine/config/precompute.nim | 2 +- tests/test_bigints.nim | 98 ++++++++++++ tests/test_bigints_mul_high_words_vs_gmp.nim | 151 +++++++++++++++++++ tests/test_bigints_mul_vs_gmp.nim | 5 +- tests/test_ec_bls12_381.nim | 2 +- tests/test_ec_bn254.nim | 2 +- 8 files changed, 341 insertions(+), 26 deletions(-) create mode 100644 tests/test_bigints_mul_high_words_vs_gmp.nim diff --git a/constantine/arithmetic/bigints.nim b/constantine/arithmetic/bigints.nim index bd38b0012..125794b99 100644 --- a/constantine/arithmetic/bigints.nim +++ b/constantine/arithmetic/bigints.nim @@ -203,6 +203,25 @@ func prod*[rBits, aBits, bBits](r: var BigInt[rBits], a: BigInt[aBits], b: BigIn ## It will be truncated if it cannot fit in r limbs. r.limbs.prod(a.limbs, b.limbs) +func prod_high_words*[rBits, aBits, bBits](r: var BigInt[rBits], a: BigInt[aBits], b: BigInt[bBits], lowestWordIndex: static int) = + ## Multi-precision multiplication keeping only high words + ## r <- a*b >> (2^WordBitWidth)^lowestWordIndex + ## + ## `a`, `b`, `r` can have a different number of limbs + ## if `r`.limbs.len < a.limbs.len + b.limbs.len - lowestWordIndex + ## The result will be truncated, i.e. it will be + ## a * b >> (2^WordBitWidth)^lowestWordIndex (mod (2^WordBitwidth)^r.limbs.len) + ## + # This is useful for + # - Barret reduction + # - Approximating multiplication by a fractional constant in the form f(a) = K/C * a + # with K and C known at compile-time. + # We can instead find a well chosen M = (2^WordBitWidth)^w, with M > C (i.e. M is a power of 2 bigger than C) + # Precompute P = K*M/C at compile-time + # and at runtime do P*a/M <=> P*a >> WordBitWidth*w + # i.e. prod_high_words(result, P, a, w) + r.limbs.prod_high_words(a.limbs, b.limbs, lowestWordIndex) + # Bit Manipulation # ------------------------------------------------------------ diff --git a/constantine/arithmetic/limbs.nim b/constantine/arithmetic/limbs.nim index 1feed8b15..e600acf4f 100644 --- a/constantine/arithmetic/limbs.nim +++ b/constantine/arithmetic/limbs.nim @@ -166,7 +166,28 @@ func isOdd*(a: Limbs): SecretBool = ## Returns true if a is odd SecretBool(a[0] and SecretWord(1)) -# Arithmetic +# Bit manipulation +# ------------------------------------------------------------ + +func shiftRight*(a: var Limbs, k: int) {.inline.}= + ## Shift right by k. + ## + ## k MUST be less than the base word size (2^32 or 2^64) + # We don't reuse shr as this is an in-place operation + # Do we need to return the shifted out part? + # + # Note: for speed, loading a[i] and a[i+1] + # instead of a[i-1] and a[i] + # is probably easier to parallelize for the compiler + # (antidependence WAR vs loop-carried dependence RAW) + + # checkWordShift(k) + + for i in 0 ..< a.len-1: + a[i] = (a[i] shr k) or (a[i+1] shl (WordBitWidth - k)) + a[a.len-1] = a[a.len-1] shr k + +# Basic Arithmetic # ------------------------------------------------------------ func add*(a: var Limbs, b: Limbs): Carry = @@ -252,13 +273,18 @@ func cneg*(a: var Limbs, ctl: CTBool) = # So we need to xor all words and then add 1 # The "+1" might carry # So we fuse the 2 steps - let mask = -SecretWord(ctl) # Obtain a 0xFF... or 0x00... mask + let mask = -SecretWord(ctl) # Obtain a 0xFF... or 0x00... mask var carry = SecretWord(ctl) for i in 0 ..< a.len: let t = (a[i] xor mask) + carry # XOR with mask and add 0x01 or 0x00 respectively - carry = SecretWord(t < carry) # Carry on + carry = SecretWord(t < carry) # Carry on a[i] = t +{.pop.} # inline + +# Multiplication +# ------------------------------------------------------------ + func prod*[rLen, aLen, bLen](r: var Limbs[rLen], a: Limbs[aLen], b: Limbs[bLen]) = ## Multi-precision multiplication ## r <- a*b @@ -282,26 +308,50 @@ func prod*[rLen, aLen, bLen](r: var Limbs[rLen], a: Limbs[aLen], b: Limbs[bLen]) u = t t = SecretWord(0) -# Bit manipulation -# ------------------------------------------------------------ + staticFor i, a.len+b.len, r.len: + r[i] = SecretWord(0) -func shiftRight*(a: var Limbs, k: int) = - ## Shift right by k. +func prod_high_words*[rLen, aLen, bLen]( + r: var Limbs[rLen], + a: Limbs[aLen], b: Limbs[bLen], + lowestWordIndex: static int) = + ## Multi-precision multiplication keeping only high words + ## r <- a*b >> (2^WordBitWidth)^lowestWordIndex ## - ## k MUST be less than the base word size (2^32 or 2^64) - # We don't reuse shr as this is an in-place operation - # Do we need to return the shifted out part? + ## `a`, `b`, `r` can have a different number of limbs + ## if `r`.limbs.len < a.limbs.len + b.limbs.len - lowestWordIndex + ## The result will be truncated, i.e. it will be + ## a * b >> (2^WordBitWidth)^lowestWordIndex (mod (2^WordBitwidth)^r.limbs.len) # - # Note: for speed, loading a[i] and a[i+1] - # instead of a[i-1] and a[i] - # is probably easier to parallelize for the compiler - # (antidependence WAR vs loop-carried dependence RAW) + # This is useful for + # - Barret reduction + # - Approximating multiplication by a fractional constant in the form f(a) = K/C * a + # with K and C known at compile-time. + # We can instead find a well chosen M = (2^WordBitWidth)^w, with M > C (i.e. M is a power of 2 bigger than C) + # Precompute P = K*M/C at compile-time + # and at runtime do P*a/M <=> P*a >> (WordBitWidth*w) + # i.e. prod_high_words(result, P, a, w) - # checkWordShift(k) + # We use Product Scanning / Comba multiplication + var t, u, v = SecretWord(0) # Will raise warning on empty iterations - for i in 0 ..< a.len-1: - a[i] = (a[i] shr k) or (a[i+1] shl (WordBitWidth - k)) - a[a.len-1] = a[a.len-1] shr k + # The previous 2 columns can affect the lowest word due to carries + # but not the ones before (we accumulate in 3 words (t, u, v)) + const w = lowestWordIndex - 2 + + staticFor i, max(0, w), min(a.len+b.len, r.len+lowestWordIndex): + const ib = min(b.len-1, i) + const ia = i - ib + staticFor j, 0, min(a.len - ia, ib+1): + mulAcc(t, u, v, a[ia+j], b[ib-j]) + + when i >= lowestWordIndex: + r[i-lowestWordIndex] = v + v = u + u = t + t = SecretWord(0) + + staticFor i, max(0, a.len+b.len-lowestWordIndex), r.len: + r[i] = SecretWord(0) -{.pop.} # inline {.pop.} # raises no exceptions diff --git a/constantine/config/precompute.nim b/constantine/config/precompute.nim index dc2e9b3d2..b6c7f6b4a 100644 --- a/constantine/config/precompute.nim +++ b/constantine/config/precompute.nim @@ -18,7 +18,7 @@ import # ############################################################ # -# Modular primitives +# BigInt primitives # # ############################################################ # diff --git a/tests/test_bigints.nim b/tests/test_bigints.nim index d6f36f6b1..45726bbad 100644 --- a/tests/test_bigints.nim +++ b/tests/test_bigints.nim @@ -165,6 +165,104 @@ proc mainArith() = r.prod(b, a) check: bool(r == expected) + test "Destination is properly zero-padded if multiplicands are too short": + block: + var r = BigInt[200].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF_DE" + let a = BigInt[29].fromHex"0x12345678" + let b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF" + + let expected = BigInt[200].fromHex"fd5bdee65f787f665f787f665f787f65621ca08" + + r.prod(a, b) + check: bool(r == expected) + r.prod(b, a) + check: bool(r == expected) + + suite "Multi-precision multiplication keeping only high words": + test "Same size operand into double size result - discard first word": + block: + var r: BigInt[256] + let a = BigInt[128].fromHex"0x12345678_FF11FFAA_00321321_CAFECAFE" + let b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF" + + when WordBitWidth == 32: + let expected = BigInt[256].fromHex"fd5bdef43d64113f371ab5d8843beca889c07fd549b84d8a5001a8f" + else: + let expected = BigInt[256].fromHex"fd5bdef43d64113f371ab5d8843beca889c07fd549b84d8" + + r.prod_high_words(a, b, 1) + check: bool(r == expected) + r.prod_high_words(b, a, 1) + check: bool(r == expected) + + test "Same size operand into double size result - discard first 3 words": + block: + var r: BigInt[256] + let a = BigInt[128].fromHex"0x12345678_FF11FFAA_00321321_CAFECAFE" + let b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF" + + when WordBitWidth == 32: + let expected = BigInt[256].fromHex"fd5bdef43d64113f371ab5d8843beca889c07fd" + else: + let expected = BigInt[256].fromHex"fd5bdef43d64113" + + r.prod_high_words(a, b, 3) + check: bool(r == expected) + r.prod_high_words(b, a, 3) + check: bool(r == expected) + + test "All lower words trigger a carry": + block: + var r: BigInt[256] + let a = BigInt[256].fromHex"0xFFFFF000_FFFFF111_FFFFFFFA_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF" + let b = BigInt[256].fromHex"0xFFFFFFFF_FFFFF222_FFFFFFFB_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF" + + # Full product: + # fffff000_ffffe335_00ddc21a_00cf3972_00008109_00000013_ffffffff_fffffffe + # 00000fff_00001ccb_00000009_00000000_00000000_00000000_00000000_00000001 + let expected = BigInt[256].fromHex"0xfffff000_ffffe335_00ddc21a_00cf3972_00008109_00000013_ffffffff_fffffffe" + when WordBitWidth == 32: + const startWord = 8 + else: + const startWord = 4 + + r.prod_high_words(a, b, startWord) + check: bool(r == expected) + r.prod_high_words(b, a, startWord) + check: bool(r == expected) + + test "Different size into large result": + block: + var r: BigInt[200] + let a = BigInt[29].fromHex"0x12345678" + let b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF" + + when WordBitWidth == 32: + let expected = BigInt[200].fromHex"fd5bdee65f787f665f787f6" + else: + let expected = BigInt[200].fromHex"fd5bdee" + + r.prod_high_words(a, b, 2) + check: bool(r == expected) + r.prod_high_words(b, a, 2) + check: bool(r == expected) + + test "Destination is properly zero-padded if multiplicands are too short": + block: + var r = BigInt[200].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF_DE" + let a = BigInt[29].fromHex"0x12345678" + let b = BigInt[128].fromHex"0xDEADBEEF_DEADBEEF_DEADBEEF_DEADBEEF" + + when WordBitWidth == 32: + let expected = BigInt[200].fromHex"fd5bdee65f787f665f787f6" + else: + let expected = BigInt[200].fromHex"fd5bdee" + + r.prod_high_words(a, b, 2) + check: bool(r == expected) + r.prod_high_words(b, a, 2) + check: bool(r == expected) + suite "Modular operations - small modulus": # Vectors taken from Stint - https://github.com/status-im/nim-stint test "100 mod 13": diff --git a/tests/test_bigints_mul_high_words_vs_gmp.nim b/tests/test_bigints_mul_high_words_vs_gmp.nim new file mode 100644 index 000000000..85c48f243 --- /dev/null +++ b/tests/test_bigints_mul_high_words_vs_gmp.nim @@ -0,0 +1,151 @@ +# 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/[random, macros, times, strutils], + # Third-party + gmp, stew/byteutils, + # Internal + ../constantine/io/io_bigints, + ../constantine/arithmetic, + ../constantine/primitives, + ../constantine/config/[common, type_bigint] + +# We test up to 1024-bit, more is really slow + +var bitSizeRNG {.compileTime.} = initRand(1234) + +macro testRandomModSizes(numSizes: static int, rBits, aBits, bBits, wordsStartIndex, body: untyped): untyped = + ## Generate `numSizes` random bit sizes known at compile-time to test against GMP + ## for A mod M + result = newStmtList() + + for _ in 0 ..< numSizes: + let aBitsVal = bitSizeRNG.rand(126 .. 2048) + let bBitsVal = bitSizeRNG.rand(126 .. 2048) + let rBitsVal = bitSizeRNG.rand(62 .. 4096+128) + let wordsStartIndexVal = bitSizeRNG.rand(1 .. wordsRequired(4096+128)) + + result.add quote do: + block: + const `aBits` = `aBitsVal` + const `bBits` = `bBitsVal` + const `rBits` = `rBitsVal` + const `wordsStartIndex` = `wordsStartIndexVal` + + block: + `body` + +const # https://gmplib.org/manual/Integer-Import-and-Export.html + GMP_WordLittleEndian {.used.} = -1'i32 + GMP_WordNativeEndian {.used.} = 0'i32 + GMP_WordBigEndian {.used.} = 1'i32 + + GMP_MostSignificantWordFirst = 1'i32 + GMP_LeastSignificantWordFirst {.used.} = -1'i32 + +proc main() = + var gmpRng: gmp_randstate_t + gmp_randinit_mt(gmpRng) + # The GMP seed varies between run so that + # test coverage increases as the library gets tested. + # This requires to dump the seed in the console or the function inputs + # to be able to reproduce a bug + let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32 + echo "GMP seed: ", seed + gmp_randseed_ui(gmpRng, seed) + + var r, a, b: mpz_t + mpz_init(r) + mpz_init(a) + mpz_init(b) + + testRandomModSizes(128, rBits, aBits, bBits, wordsStartIndex): + # echo "--------------------------------------------------------------------------------" + echo "Testing: random mul_high_words r (", align($rBits, 4), + "-bit, keeping from ", wordsStartIndex, + " word index) <- a (", align($aBits, 4), + "-bit) * b (", align($bBits, 4), "-bit) (full mul bits: ", align($(aBits+bBits), 4), + "), r large enough? ", wordsRequired(rBits) >= wordsRequired(aBits+bBits) - wordsStartIndex + + # Generate random value in the range 0 ..< 2^aBits + mpz_urandomb(a, gmpRng, aBits) + # Generate random modulus and ensure the MSB is set + mpz_urandomb(b, gmpRng, bBits) + mpz_setbit(r, aBits+bBits) + + # discard gmp_printf(" -- %#Zx mod %#Zx\n", a.addr, m.addr) + + ######################################################### + # Conversion buffers + const aLen = (aBits + 7) div 8 + const bLen = (bBits + 7) div 8 + + var aBuf: array[aLen, byte] + var bBuf: array[bLen, byte] + + {.push warnings: off.} # deprecated csize + var aW, bW: csize # Word written by GMP + {.pop.} + + discard mpz_export(aBuf[0].addr, aW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, a) + discard mpz_export(bBuf[0].addr, bW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, b) + + # Since the modulus is using all bits, it's we can test for exact amount copy + doAssert aLen >= aW, "Expected at most " & $aLen & " bytes but wrote " & $aW & " for " & toHex(aBuf) & " (big-endian)" + doAssert bLen >= bW, "Expected at most " & $bLen & " bytes but wrote " & $bW & " for " & toHex(bBuf) & " (big-endian)" + + # Build the bigint + let aTest = BigInt[aBits].fromRawUint(aBuf.toOpenArray(0, aW-1), bigEndian) + let bTest = BigInt[bBits].fromRawUint(bBuf.toOpenArray(0, bW-1), bigEndian) + + ######################################################### + # Multiplication + drop low words + mpz_mul(r, a, b) + var shift: mpz_t + mpz_init(shift) + r.mpz_tdiv_q_2exp(r, WordBitwidth * wordsStartIndex) + + # If a*b overflow the result size we truncate + const numWords = wordsRequired(rBits) + when numWords < wordsRequired(aBits+bBits): + echo " truncating from ", wordsRequired(aBits+bBits), " words to ", numWords, " (2^", WordBitwidth * numWords, ")" + r.mpz_tdiv_r_2exp(r, WordBitwidth * numWords) + + # Constantine + var rTest: BigInt[rBits] + rTest.prod_high_words(aTest, bTest, wordsStartIndex) + + ######################################################### + # Check + const rLen = numWords * WordBitWidth + var rGMP: array[rLen, byte] + {.push warnings: off.} # deprecated csize + var rW: csize # Word written by GMP + {.pop.} + discard mpz_export(rGMP[0].addr, rW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, r) + + var rConstantine: array[rLen, byte] + exportRawUint(rConstantine, rTest, bigEndian) + + # Note: in bigEndian, GMP aligns left while constantine aligns right + doAssert rGMP.toOpenArray(0, rW-1) == rConstantine.toOpenArray(rLen-rW, rLen-1), block: + # Reexport as bigEndian for debugging + discard mpz_export(aBuf[0].addr, aW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, a) + discard mpz_export(bBuf[0].addr, bW.addr, GMP_MostSignificantWordFirst, 1, GMP_WordNativeEndian, 0, b) + "\nMultiplication with operands\n" & + " a (" & align($aBits, 4) & "-bit): " & aBuf.toHex & "\n" & + " b (" & align($bBits, 4) & "-bit): " & bBuf.toHex & "\n" & + " keeping words starting from: " & $wordsStartIndex & "\n" & + "into r of size " & align($rBits, 4) & "-bit failed:" & "\n" & + " GMP: " & rGMP.toHex() & "\n" & + " Constantine: " & rConstantine.toHex() & "\n" & + "(Note that GMP aligns bytes left while constantine aligns bytes right)" + +main() diff --git a/tests/test_bigints_mul_vs_gmp.nim b/tests/test_bigints_mul_vs_gmp.nim index fb0993602..9b34fb676 100644 --- a/tests/test_bigints_mul_vs_gmp.nim +++ b/tests/test_bigints_mul_vs_gmp.nim @@ -106,10 +106,7 @@ proc main() = const numWords = wordsRequired(rBits) when numWords < wordsRequired(aBits+bBits): echo " truncating from ", wordsRequired(aBits+bBits), " words to ", numWords, " (2^", WordBitwidth * numWords, ")" - var trunc: mpz_t - mpz_init(trunc) - trunc.mpz_ui_pow_ui(2, WordBitwidth * numWords) - r.mpz_mod(r, trunc) + r.mpz_tdiv_r_2exp(r, WordBitwidth * numWords) # Constantine var rTest: BigInt[rBits] diff --git a/tests/test_ec_bls12_381.nim b/tests/test_ec_bls12_381.nim index 2927b027b..ca97e06a8 100644 --- a/tests/test_ec_bls12_381.nim +++ b/tests/test_ec_bls12_381.nim @@ -48,7 +48,7 @@ proc test( doAssert: bool(Q == reference) doAssert: bool(Q == impl) -suite "BLS12_381 implementation (and unsafe reference impl) vs SageMath": +suite "Scalar Multiplication: BLS12_381 implementation (and unsafe reference impl) vs SageMath": # Generated via sage sage/testgen_bls12_381.sage test( id = 1, diff --git a/tests/test_ec_bn254.nim b/tests/test_ec_bn254.nim index 18b5cda10..d684407c2 100644 --- a/tests/test_ec_bn254.nim +++ b/tests/test_ec_bn254.nim @@ -48,7 +48,7 @@ proc test( doAssert: bool(Q == reference) doAssert: bool(Q == impl) -suite "BN254 implementation (and unsafe reference impl) vs SageMath": +suite "Scalar Multiplication: BN254 implementation (and unsafe reference impl) vs SageMath": # Generated via sage sage/testgen_bn254_snarks.sage test( id = 1, From 50df2f57f23c5ea90401c527cf61faad26852f82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mamy=20Andr=C3=A9-Ratsimbazafy?= Date: Tue, 9 Jun 2020 00:57:28 +0200 Subject: [PATCH 11/19] Implement the lattice decomposition in sage --- constantine/arithmetic/limbs.nim | 12 +-- constantine/config/curves.nim | 10 ++- constantine/config/curves_declaration.nim | 6 +- constantine/config/curves_derived.nim | 18 +++- constantine/config/curves_parser.nim | 33 +++++--- .../quadratic_extensions.nim | 1 - sage/bn254_lattice_decomposition.sage | 84 +++++++++++++++++++ sage/curve_family_bn.sage | 10 ++- tests/test_precomputed.nim | 20 +++-- 9 files changed, 157 insertions(+), 37 deletions(-) create mode 100644 sage/bn254_lattice_decomposition.sage diff --git a/constantine/arithmetic/limbs.nim b/constantine/arithmetic/limbs.nim index e600acf4f..dacd0578a 100644 --- a/constantine/arithmetic/limbs.nim +++ b/constantine/arithmetic/limbs.nim @@ -296,6 +296,7 @@ func prod*[rLen, aLen, bLen](r: var Limbs[rLen], a: Limbs[aLen], b: Limbs[bLen]) # We use Product Scanning / Comba multiplication var t, u, v = SecretWord(0) + var z: Limbs[rLen] # zero-init, ensure on stack and removes in-place problems staticFor i, 0, min(a.len+b.len, r.len): const ib = min(b.len-1, i) @@ -303,13 +304,12 @@ func prod*[rLen, aLen, bLen](r: var Limbs[rLen], a: Limbs[aLen], b: Limbs[bLen]) staticFor j, 0, min(a.len - ia, ib+1): mulAcc(t, u, v, a[ia+j], b[ib-j]) - r[i] = v + z[i] = v v = u u = t t = SecretWord(0) - staticFor i, a.len+b.len, r.len: - r[i] = SecretWord(0) + r = z func prod_high_words*[rLen, aLen, bLen]( r: var Limbs[rLen], @@ -334,6 +334,7 @@ func prod_high_words*[rLen, aLen, bLen]( # We use Product Scanning / Comba multiplication var t, u, v = SecretWord(0) # Will raise warning on empty iterations + var z: Limbs[rLen] # zero-init, ensure on stack and removes in-place problems # The previous 2 columns can affect the lowest word due to carries # but not the ones before (we accumulate in 3 words (t, u, v)) @@ -346,12 +347,11 @@ func prod_high_words*[rLen, aLen, bLen]( mulAcc(t, u, v, a[ia+j], b[ib-j]) when i >= lowestWordIndex: - r[i-lowestWordIndex] = v + z[i-lowestWordIndex] = v v = u u = t t = SecretWord(0) - staticFor i, max(0, a.len+b.len-lowestWordIndex), r.len: - r[i] = SecretWord(0) + r = z {.pop.} # raises no exceptions diff --git a/constantine/config/curves.nim b/constantine/config/curves.nim index 7a3809fa2..cc6ecdb37 100644 --- a/constantine/config/curves.nim +++ b/constantine/config/curves.nim @@ -172,9 +172,13 @@ macro getBN_param_6u_minus_1_BE*(C: static Curve): untyped = # Endomorphism # ------------------------------------------------------- -macro getCubicRootOfUnity*(C: static Curve): untyped = - ## Get a non-trivial cubic root of unity - result = bindSym($C & "_cubicRootOfUnity") +macro getCubicRootOfUnity_mod_p*(C: static Curve): untyped = + ## Get a non-trivial cubic root of unity (mod p) with p the prime field + result = bindSym($C & "_cubicRootOfUnity_mod_p") + +macro getCubicRootOfUnity_mod_r*(C: static Curve): untyped = + ## Get a non-trivial cubic root of unity (mod r) with r the curve order + result = bindSym($C & "_cubicRootOfUnity_mod_r") # ############################################################ # diff --git a/constantine/config/curves_declaration.nim b/constantine/config/curves_declaration.nim index f631c855b..d0da9ab7a 100644 --- a/constantine/config/curves_declaration.nim +++ b/constantine/config/curves_declaration.nim @@ -91,7 +91,9 @@ declareCurves: family: BarretoNaehrig bn_u_bitwidth: 63 bn_u: "0x44E992B44A6909F1" # u: 4965661367192848881 - cubicRootOfUnity: "0x59e26bcea0d48bacd4f263f1acdb5c4f5763473177fffffe" + cubicRootOfUnity_modP: "0x59e26bcea0d48bacd4f263f1acdb5c4f5763473177fffffe" + # For sanity checks + cubicRootOfUnity_modR: "0x30644e72e131a029048b6e193fd84104cc37a73fec2bc5e9b8ca0b2d36636f23" # G1 Equation: Y^2 = X^3 + 3 # G2 Equation: Y^2 = X^3 + 3/(9+𝑖) @@ -142,7 +144,7 @@ declareCurves: modulus: "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" family: BarretoLynnScott # u: -(2^63 + 2^62 + 2^60 + 2^57 + 2^48 + 2^16) - cubicRootOfUnity: "0x5f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffe" + cubicRootOfUnity_mod_p: "0x5f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffe" # G1 Equation: y² = x³ + 4 # G2 Equation: y² = x³ + 4 (1+i) diff --git a/constantine/config/curves_derived.nim b/constantine/config/curves_derived.nim index 5b68e2589..93805d478 100644 --- a/constantine/config/curves_derived.nim +++ b/constantine/config/curves_derived.nim @@ -124,15 +124,15 @@ macro genDerivedConstants*(): untyped = ) ) - # const MyCurve_cubicRootOfUnity + # const MyCurve_cubicRootOfUnity_mod_p block: - let cubicHex = ident(curve & "_cubicRootOfUnityHex") - let cubic = used(curve & "_cubicRootOfUnity") + let cubicHex = ident(curve & "_cubicRootOfUnity_modP_Hex") + let cubic = used(curve & "_cubicRootOfUnity_mod_p") let M = bindSym(curve & "_Modulus") let r2modM = ident(curve & "_R2modP") let m0ninv = ident(curve & "_NegInvModWord") result.add quote do: - when declared(`cubichex`): + when declared(`cubicHex`): const `cubic` = block: var cubic: Fp[Curve(`curveSym`)] montyResidue_precompute( @@ -141,6 +141,16 @@ macro genDerivedConstants*(): untyped = `M`, `r2modM`, `m0ninv` ) cubic + # const MyCurve_cubicRootOfUnity_mod_r + block: # For scalar decomposition sanity checks + let cubicHex = ident(curve & "_cubicRootOfUnity_modR_Hex") + let cubic = used(curve & "_cubicRootOfUnity_mod_r") + let getCurveOrderBitwidth = ident"getCurveOrderBitwidth" + result.add quote do: + when declared(`cubicHex`): + const `cubic` = fromHex(BigInt[ + `getCurveOrderBitwidth`(Curve(`curveSym`)) + ], `cubicHex`) if CurveFamilies[curveSym] == BarretoNaehrig: # when declared(MyCurve_BN_param_u): diff --git a/constantine/config/curves_parser.nim b/constantine/config/curves_parser.nim index 1c2891662..98162e2c1 100644 --- a/constantine/config/curves_parser.nim +++ b/constantine/config/curves_parser.nim @@ -99,9 +99,6 @@ type nonresidue_quad_fp: NimNode # nnkIntLit nonresidue_cube_fp2: NimNode # nnkPar(nnkIntLit, nnkIntLit) - # Endomorphisms - cubicRootOfUnity: NimNode # nnkStrLit - # Curve parameters eq_form: CurveEquationForm coef_A: CurveCoef @@ -112,6 +109,10 @@ type sexticTwist: SexticTwist sexticNonResidue_fp2: NimNode # nnkPar(nnkIntLit, nnkIntLit) + # Endomorphisms + cubicRootOfUnity_modP: NimNode # nnkStrLit + cubicRootOfUnity_modR: NimNode # nnkStrLit + family: CurveFamily # BN family # ------------------------ @@ -185,8 +186,10 @@ proc parseCurveDecls(defs: var seq[CurveParams], curves: NimNode) = params.bn_u_bitwidth = sectionVal elif sectionId.eqIdent"bn_u": params.bn_u = sectionVal - elif sectionId.eqident"cubicRootOfUnity": - params.cubicRootOfUnity = sectionVal + elif sectionId.eqident"cubicRootOfUnity_modP": + params.cubicRootOfUnity_modP = sectionVal + elif sectionId.eqident"cubicRootOfUnity_modR": + params.cubicRootOfUnity_modR = sectionVal elif sectionId.eqIdent"eq_form": params.eq_form = parseEnum[CurveEquationForm]($sectionVal) elif sectionId.eqIdent"coef_a": @@ -277,13 +280,6 @@ proc genMainConstants(defs: var seq[CurveParams]): NimNode = MapCurveFamily.add nnkExprColonExpr.newTree( curve, newLit(family) ) - # Endomorphisms - # ----------------------------------------------- - if not curveDef.cubicRootOfUnity.isNil: - curveExtraStmts.add newConstStmt( - exported($curve & "_cubicRootOfUnityHex"), - curveDef.cubicRootOfUnity - ) # Curve equation # ----------------------------------------------- @@ -327,6 +323,19 @@ proc genMainConstants(defs: var seq[CurveParams]): NimNode = curveDef.sexticNonResidue_fp2 ) + # Endomorphisms + # ----------------------------------------------- + if not curveDef.cubicRootOfUnity_modP.isNil: + curveExtraStmts.add newConstStmt( + exported($curve & "_cubicRootOfUnity_modP_Hex"), + curveDef.cubicRootOfUnity_modP + ) + if not curveDef.cubicRootOfUnity_modR.isNil: + curveExtraStmts.add newConstStmt( + exported($curve & "_cubicRootOfUnity_modR_Hex"), + curveDef.cubicRootOfUnity_modR + ) + # BN curves # ----------------------------------------------- if family == BarretoNaehrig: diff --git a/constantine/tower_field_extensions/quadratic_extensions.nim b/constantine/tower_field_extensions/quadratic_extensions.nim index f7a6a12b8..f0b1538c2 100644 --- a/constantine/tower_field_extensions/quadratic_extensions.nim +++ b/constantine/tower_field_extensions/quadratic_extensions.nim @@ -8,7 +8,6 @@ import ../arithmetic, - ../config/common, ../primitives, ./tower_common diff --git a/sage/bn254_lattice_decomposition.sage b/sage/bn254_lattice_decomposition.sage new file mode 100644 index 000000000..53cd624e1 --- /dev/null +++ b/sage/bn254_lattice_decomposition.sage @@ -0,0 +1,84 @@ +# 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 GLV Endomorphism +# Lattice Decomposition +# +# ############################################################ + +# Parameters +u = Integer('0x44E992B44A6909F1') +p = 36*u^4 + 36*u^3 + 24*u^2 + 6*u + 1 +r = 36*u^4 + 36*u^3 + 18*u^2 + 6*u + 1 +cofactor = 1 + +# Cube root of unity (mod r) formula for any BN curves +lambda1 = (-(36*u^3+18*u^2+6*u+2)) +assert lambda1^3 % r == 1 +print('λᵩ1 : ' + lambda1.hex()) +print('λᵩ1+r: ' + (lambda1+r).hex()) + +lambda2 = (36*u^4-1) +assert lambda2^3 % r == 1 +print('λᵩ2 : ' + lambda2.hex()) + +# Finite fields +F = GF(p) +# K2. = PolynomialRing(F) +# F2. = F.extension(u^2+9) +# K6. = PolynomialRing(F2) +# F6. = F2.extension(v^3-beta) +# K12. = PolynomialRing(F6) +# K12. = F6.extension(w^2-eta) + +# Curves +b = 3 +G1 = EllipticCurve(F, [0, b]) +# G2 = EllipticCurve(F2, [0, b/beta]) + +# Lattice +b = [ + [2*u+1, 6*u^2+4*u+1], + [6*u^2+2*u, -2*u-1] +] +# Babai rounding +ahat = [2*u+1, 6*u^2+4*u+1] +v = int(r).bit_length() +v = int(((v + 64 - 1) // 64) * 64) # round to next multiple of 64 + +l = [Integer(a << v) // r for a in ahat] + +def getGLV2_decomp(scalar): + + a0 = (l[0] * scalar) >> v + a1 = (l[1] * scalar) >> v + + k0 = scalar - a0 * b[0][0] - a1 * b[1][0] + k1 = 0 - a0 * b[0][1] - a1 * b[1][1] + + assert int(k0).bit_length() <= (int(r).bit_length() + 1) // 2 + assert int(k1).bit_length() <= (int(r).bit_length() + 1) // 2 + + assert scalar == (k0 + k1 * (lambda1 % r)) % r + assert scalar == (k0 + k1 * (lambda2 % r)) % r + + return k0, k1 + +# Test generator +set_random_seed(1337) + +for i in range(10): + print('---------------------------------------') + scalar = randrange(r) # Pick an integer below curve order + print('scalar: ' + Integer(scalar).hex()) + + k0, k1 = getGLV2_decomp(scalar) + print('k0: ' + k0.hex()) + print('k1: ' + k1.hex()) diff --git a/sage/curve_family_bn.sage b/sage/curve_family_bn.sage index 9e6af87b7..a54648795 100644 --- a/sage/curve_family_bn.sage +++ b/sage/curve_family_bn.sage @@ -22,8 +22,8 @@ def compute_curve_characteristic(u_str): r = 36*u^4 + 36*u^3 + 18*u^2 + 6*u + 1 print(f'BN family - {p.nbits()} bits') - print(' Prime modulus: 0x' + p.hex()) - print(' Curve order: 0x' + r.hex()) + print(' Prime modulus p: 0x' + p.hex()) + print(' Curve order r: 0x' + r.hex()) print(' Parameter u: ' + u_str) if u < 0: print(' Parameter u (hex): -0x' + (-u).hex()) @@ -43,11 +43,13 @@ def compute_curve_characteristic(u_str): print(f' Rationale:') print(f' curve equation is y² = x³ + b, and y² = (x𝜑)³ + b <=> y² = x³ + b (with 𝜑³ == 1) so we are still on the curve') print(f' this means that multiplying by 𝜑 the x-coordinate is equivalent to a scalar multiplication by some λᵩ') - print(f' with λᵩ² + λᵩ + 1 ≡ 0 (mod CurveOrder), see below. Hence we have a 2 dimensional decomposition of the scalar multiplication') + print(f' with λᵩ² + λᵩ + 1 ≡ 0 (mod r) and 𝜑² + 𝜑 + 1 ≡ 0 (mod p), see below.') + print(f' Hence we have a 2 dimensional decomposition of the scalar multiplication') print(f' i.e. For any [s]P, we can find a corresponding [k1]P + [k2][λᵩ]P with [λᵩ]P being a simple field multiplication by 𝜑') print(f' Finding cube roots:') print(f' x³−1=0 <=> (x−1)(x²+x+1) = 0, if x != 1, x solves (x²+x+1) = 0 <=> x = (-1±√3)/2') - print(f' cube roots of unity: ' + str(['0x' + Integer(root).hex() for root in GF(p)(1).nth_root(3, all=True)])) + print(f' cube roots of unity 𝜑 (mod p): ' + str(['0x' + Integer(root).hex() for root in GF(p)(1).nth_root(3, all=True)])) + print(f' cube roots of unity λᵩ (mod r): ' + str(['0x' + Integer(root).hex() for root in GF(r)(1).nth_root(3, all=True)])) print(f' GLV-2 decomposition of s into (k1, k2) on G1') print(f' (k1, k2) = (s, 0) - 𝛼1 b1 - 𝛼2 b2') print(f' 𝛼i = 𝛼\u0302i * s / r') diff --git a/tests/test_precomputed.nim b/tests/test_precomputed.nim index 40a123033..b787af783 100644 --- a/tests/test_precomputed.nim +++ b/tests/test_precomputed.nim @@ -9,19 +9,29 @@ import std/unittest, ../constantine/arithmetic, ../constantine/config/curves, - ../constantine/io/io_fields + ../constantine/io/[io_bigints, io_fields] proc checkCubeRootOfUnity(curve: static Curve) = - test $curve & " cube root of unity": - var cru = curve.getCubicRootOfUnity() + test $curve & " cube root of unity (mod p)": + var cru = curve.getCubicRootOfUnity_mod_p() cru.square() - cru *= curve.getCubicRootOfUnity() + cru *= curve.getCubicRootOfUnity_mod_p() check: bool cru.isOne() + test $curve & " cube root of unity (mod r)": + var cru: BigInt[3 * curve.getCurveOrderBitwidth()] + cru.prod(curve.getCubicRootOfUnity_mod_r(), curve.getCubicRootOfUnity_mod_r()) + cru.prod(cru, curve.getCubicRootOfUnity_mod_r()) + + var r: BigInt[curve.getCurveOrderBitwidth()] + r.reduce(cru, curve.getCurveOrder) + + check: bool r.isOne() + proc main() = suite "Sanity checks on precomputed values": checkCubeRootOfUnity(BN254_Snarks) - checkCubeRootOfUnity(BLS12_381) + # checkCubeRootOfUnity(BLS12_381) main() From 4862f230ebe44544338d0524c5b7d20e1e29394b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mamy=20Andr=C3=A9-Ratsimbazafy?= Date: Tue, 9 Jun 2020 01:30:24 +0200 Subject: [PATCH 12/19] Proper decomposition for BN254 --- constantine/arithmetic/bigints.nim | 25 ++++- .../elliptic/ec_endomorphism_accel.nim | 106 +++++++++++++++++- 2 files changed, 127 insertions(+), 4 deletions(-) diff --git a/constantine/arithmetic/bigints.nim b/constantine/arithmetic/bigints.nim index 125794b99..3ef2ea251 100644 --- a/constantine/arithmetic/bigints.nim +++ b/constantine/arithmetic/bigints.nim @@ -90,6 +90,14 @@ func cswap*(a, b: var BigInt, ctl: CTBool) = ## memory accesses are done (unless the compiler tries to be clever) cswap(a.limbs, b.limbs, ctl) +func copyTruncatedFrom*[dBits, sBits: static int](dst: var BigInt[dBits], src: BigInt[sBits]) = + ## Copy `src` into `dst` + ## if `dst` is not big enough, only the low words are copied + ## if `src` is smaller than `dst` the higher words of `dst` will NOT be overwritten + + for wordIdx in 0 ..< min(dst.limbs.len, src.limbs.len): + dst.limbs[wordIdx] = src.limbs[wordIdx] + # Comparison # ------------------------------------------------------------ @@ -149,8 +157,13 @@ func add*(a: var BigInt, b: SecretWord): SecretBool = ## Returns the carry (SecretBool) add(a.limbs, b) +func `+=`*(a: var BigInt, b: BigInt) = + ## Constant-time in-place addition + ## Discards the carry + discard add(a.limbs, b.limbs) + func `+=`*(a: var BigInt, b: SecretWord) = - ## Constant-time in-pace addition + ## Constant-time in-place addition ## Discards the carry discard add(a.limbs, b) @@ -159,6 +172,16 @@ func sub*(a: var BigInt, b: BigInt): SecretBool = ## Returns the borrow (SecretBool) sub(a.limbs, b.limbs) +func `-=`*(a: var BigInt, b: BigInt) = + ## Constant-time in-place substraction + ## Discards the borrow + discard sub(a.limbs, b.limbs) + +func `-=`*(a: var BigInt, b: SecretWord) = + ## Constant-time in-place substraction + ## Discards the borrow + discard sub(a.limbs, b) + func double*(a: var BigInt): SecretBool = ## Constant-time in-place doubling ## Returns the carry diff --git a/constantine/elliptic/ec_endomorphism_accel.nim b/constantine/elliptic/ec_endomorphism_accel.nim index 315a63420..80e8ab841 100644 --- a/constantine/elliptic/ec_endomorphism_accel.nim +++ b/constantine/elliptic/ec_endomorphism_accel.nim @@ -10,9 +10,11 @@ import # Standard Library std/typetraits, # Internal + ../../helpers/static_for, ../primitives, - ../config/[common, curves], + ../config/[common, curves, type_bigint], ../arithmetic, + ../io/io_bigints, ../towers, ./ec_weierstrass_affine, ./ec_weierstrass_projective @@ -269,6 +271,62 @@ func buildLookupTable[M: static int, F]( # The recoding allows usage of 2^(n-1) table instead of the usual 2^n with NAF let msb = u.log2() # No undefined, u != 0 lut[u].sum(lut[u.clearBit(msb)], endomorphisms[msb-1]) + # } # highlight bug, ... + +# Chapter 6.3.1 - Guide to Pairing-based Cryptography +const Lattice_BN254_Snarks_G1: array[2, array[2, tuple[b: BigInt[127], isNeg: bool]]] = [ + # Curve of order 254 -> mini scalars of size 127 + # u = 0x44E992B44A6909F1 + [(BigInt[127].fromHex"0x89d3256894d213e3", false), # 2u + 1 + (BigInt[127].fromHex"0x6f4d8248eeb859fd0be4e1541221250b", false)], # 6u² + 4u + 1 + [(BigInt[127].fromHex"0x6f4d8248eeb859fc8211bbeb7d4f1128", false), # 6u² + 2u + (BigInt[127].fromHex"0x89d3256894d213e3", true)] # -2u - 1 +] + +const Babai_BN254_Snarks_G1 = [ + # Vector for Babai rounding + BigInt[127].fromHex"0x89d3256894d213e3", # 2u + 1 + BigInt[127].fromHex"0x6f4d8248eeb859fd0be4e1541221250b" # 6u² + 4u + 1 +] + +func decomposeScalar_BN254_Snarks_G1[M, scalBits, miniBits: static int]( + scalar: BigInt[scalBits], + miniScalars: var MultiScalar[M, miniBits] + ) = + ## Decompose a secret scalar into mini-scalar exploiting + ## BN254_Snarks specificities. + ## + ## TODO: Generalize to all BN curves + ## - needs a Lattice type + ## - needs to better support negative bigints, (extra bit for sign?) + + static: doAssert miniBits == (scalBits + M - 1) div M + # 𝛼0 = (0x2d91d232ec7e0b3d7 * s) >> 256 + # 𝛼1 = (0x24ccef014a773d2d25398fd0300ff6565 * s) >> 256 + const + w = BN254_Snarks.getCurveOrderBitwidth().wordsRequired() + alphaHats = (BigInt[66].fromHex"0x2d91d232ec7e0b3d7", + BigInt[130].fromHex"0x24ccef014a773d2d25398fd0300ff6565") + + var alphas{.noInit.} : array[M, BigInt[scalBits]] # TODO size 66+254 and 130+254 + + staticFor i, 0, M: + alphas[i].prod_high_words(alphaHats[i], scalar, w) + + # We have k0 = s - 𝛼0 b00 - 𝛼1 b10 + # and kj = 0 - 𝛼j b0j - 𝛼1 b1j + var k: array[M, BigInt[scalBits]] + k[0] = scalar + for miniScalarIdx in 0 ..< M: + for basisIdx in 0 ..< M: + var alphaB {.noInit.}: BigInt[scalBits] + alphaB.prod(alphas[basisIdx], Lattice_BN254_Snarks_G1[basisIdx][miniScalarIdx].b) # TODO small lattice size + if Lattice_BN254_Snarks_G1[basisIdx][miniScalarIdx].isNeg: + k[miniScalarIdx] += alphaB + else: + k[miniScalarIdx] -= alphaB + + miniScalars[miniScalarIdx].copyTruncatedFrom(k[miniScalarIdx]) # Sanity checks # ---------------------------------------------------------------- @@ -338,7 +396,7 @@ when isMainModule: let msb = u.log2() # No undefined, u != 0 lut[u] = lut[u.clearBit(msb)] & " + " & endomorphisms[msb] - proc main() = + proc main_lut() = const M = 4 # GLS-4 decomposition const miniBitwidth = 4 # Bitwidth of the miniscalars resulting from scalar decomposition @@ -375,4 +433,46 @@ when isMainModule: echo lut_reuse doAssert lut == lut_reuse - main() + main_lut() + echo "---------------------------------------------" + + proc main_decomp() = + const M = 2 + const scalBits = BN254_Snarks.getCurveOrderBitwidth() + const miniBits = (scalBits+M-1) div M + + block: + let scalar = BigInt[scalBits].fromHex( + "0x24a0b87203c7a8def0018c95d7fab106373aebf920265c696f0ae08f8229b3f3" + ) + + var decomp: MultiScalar[M, miniBits] + decomposeScalar_BN254_Snarks_G1(scalar, decomp) + + doAssert: bool(decomp[0] == BigInt[127].fromHex"14928105460c820ccc9a25d0d953dbfe") + doAssert: bool(decomp[1] == BigInt[127].fromHex"13a2f911eb48a578844b901de6f41660") + + block: + let scalar = BigInt[scalBits].fromHex( + "24554fa6d0c06f6dc51c551dea8b058cd737fc8d83f7692fcebdd1842b3092c4" + ) + + var decomp: MultiScalar[M, miniBits] + decomposeScalar_BN254_Snarks_G1(scalar, decomp) + + doAssert: bool(decomp[0] == BigInt[127].fromHex"28cf7429c3ff8f7e82fc419e90cc3a2") + doAssert: bool(decomp[1] == BigInt[127].fromHex"457efc201bdb3d2e6087df36430a6db6") + + block: + let scalar = BigInt[scalBits].fromHex( + "288c20b297b9808f4e56aeb70eabf269e75d055567ff4e05fe5fb709881e6717" + ) + + var decomp: MultiScalar[M, miniBits] + decomposeScalar_BN254_Snarks_G1(scalar, decomp) + + doAssert: bool(decomp[0] == BigInt[127].fromHex"4da8c411566c77e00c902eb542aaa66b") + doAssert: bool(decomp[1] == BigInt[127].fromHex"5aa8f2f15afc3217f06677702bd4e41a") + + + main_decomp() From b84657f3a420b660889ce58f66b8327e72d5a66d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mamy=20Andr=C3=A9-Ratsimbazafy?= Date: Wed, 10 Jun 2020 21:38:19 +0200 Subject: [PATCH 13/19] Prepare the code for a new scalar mul --- benchmarks/bench_ec_swei_proj_g1.nim | 8 +- benchmarks/bench_elliptic_template.nim | 6 +- constantine/elliptic/ec_scalar_mul.nim | 218 ++++++++++++++++++ .../elliptic/ec_weierstrass_projective.nim | 206 +---------------- tests/test_ec_bls12_381.nim | 4 +- tests/test_ec_bn254.nim | 4 +- tests/test_ec_weierstrass_projective_g1.nim | 18 +- 7 files changed, 250 insertions(+), 214 deletions(-) create mode 100644 constantine/elliptic/ec_scalar_mul.nim diff --git a/benchmarks/bench_ec_swei_proj_g1.nim b/benchmarks/bench_ec_swei_proj_g1.nim index 5d5c3d5f5..e079ba238 100644 --- a/benchmarks/bench_ec_swei_proj_g1.nim +++ b/benchmarks/bench_ec_swei_proj_g1.nim @@ -10,7 +10,7 @@ import # Internals ../constantine/config/curves, ../constantine/arithmetic, - ../constantine/elliptic/ec_weierstrass_projective, + ../constantine/elliptic/[ec_weierstrass_projective, ec_scalar_mul], # Helpers ../helpers/static_for, ./bench_elliptic_template, @@ -51,11 +51,11 @@ proc main() = separator() doublingBench(ECP_SWei_Proj[Fp[curve]], Iters) separator() - scalarMulBench(ECP_SWei_Proj[Fp[curve]], scratchSpaceSize = 1 shl 2, MulIters) + scalarMulGenericBench(ECP_SWei_Proj[Fp[curve]], scratchSpaceSize = 1 shl 2, MulIters) separator() - scalarMulBench(ECP_SWei_Proj[Fp[curve]], scratchSpaceSize = 1 shl 3, MulIters) + scalarMulGenericBench(ECP_SWei_Proj[Fp[curve]], scratchSpaceSize = 1 shl 3, MulIters) separator() - scalarMulBench(ECP_SWei_Proj[Fp[curve]], scratchSpaceSize = 1 shl 4, MulIters) + scalarMulGenericBench(ECP_SWei_Proj[Fp[curve]], scratchSpaceSize = 1 shl 4, MulIters) separator() # scalarMulUnsafeDoubleAddBench(ECP_SWei_Proj[Fp[curve]], MulIters) # separator() diff --git a/benchmarks/bench_elliptic_template.nim b/benchmarks/bench_elliptic_template.nim index d1ccf7351..b372805a4 100644 --- a/benchmarks/bench_elliptic_template.nim +++ b/benchmarks/bench_elliptic_template.nim @@ -120,7 +120,7 @@ proc doublingBench*(T: typedesc, iters: int) = bench("EC Double G1", T, iters): r.double(P) -proc scalarMulBench*(T: typedesc, scratchSpaceSize: static int, iters: int) = +proc scalarMulGenericBench*(T: typedesc, scratchSpaceSize: static int, iters: int) = const bits = T.F.C.getCurveOrderBitwidth() var r {.noInit.}: T @@ -132,9 +132,9 @@ proc scalarMulBench*(T: typedesc, scratchSpaceSize: static int, iters: int) = var scratchSpace{.noInit.}: array[scratchSpaceSize, T] - bench("EC ScalarMul G1 (scratchsize = " & $scratchSpaceSize & ')', T, iters): + bench("EC ScalarMul Generic G1 (scratchsize = " & $scratchSpaceSize & ')', T, iters): r = P - r.scalarMul(exponentCanonical, scratchSpace) + r.scalarMulGeneric(exponentCanonical, scratchSpace) # import ../tests/support/ec_reference_scalar_mult # diff --git a/constantine/elliptic/ec_scalar_mul.nim b/constantine/elliptic/ec_scalar_mul.nim new file mode 100644 index 000000000..53709ac42 --- /dev/null +++ b/constantine/elliptic/ec_scalar_mul.nim @@ -0,0 +1,218 @@ +# 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_projective, + ./ec_endomorphism_accel + +# ############################################################ +# # +# Scalar Multiplication # +# # +# ############################################################ +# +# Scalar multiplication is a key algorithm for cryptographic protocols: +# - it is slow, +# - it is performance critical as it is used to generate signatures and authenticate messages +# - it is a high-value target as the "scalar" is very often the user secret key +# +# A safe scalar multiplication MUST: +# - Use no branching (to prevent timing and simple power analysis attacks) +# - Always do the same memory accesses (in particular for table lookups) (to prevent cache-timing attacks) +# - Not expose the bitlength of the exponent (use the curve order bitlength instead) +# +# Constantine does not make an extra effort to defend against the smart-cards +# and embedded device attacks: +# - Differential Power-Analysis which may allow for example retrieving bit content depending on the cost of writing 0 or 1 +# (Address-bit DPA by Itoh, Izu and Takenaka) +# - Electro-Magnetic which can be used in a similar way to power analysis but based on EM waves +# - Fault Attacks which can be used by actively introducing faults (via a laser for example) in an algorithm +# +# The current security efforts are focused on preventing attacks +# that are effective remotely including through the network, +# a colocated VM or a malicious process on your phone. +# +# - 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 +# +# - 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 + +template checkScalarMulScratchspaceLen(len: int) = + ## CHeck that there is a minimum of scratchspace to hold the temporaries + debug: + assert len >= 2, "Internal Error: the scratchspace for scalar multiplication should be equal or greater than 2" + +func getWindowLen(bufLen: int): uint = + ## Compute the maximum window size that fits in the scratchspace buffer + checkScalarMulScratchspaceLen(bufLen) + result = 4 + while (1 shl result) + 1 > bufLen: + dec result + +func scalarMulPrologue( + P: var ECP_SWei_Proj, + scratchspace: var openarray[ECP_SWei_Proj] + ): uint = + ## Setup the scratchspace + ## Returns the fixed-window size for scalar mul with window optimization + result = scratchspace.len.getWindowLen() + # Precompute window content, special case for window = 1 + # (i.e scratchspace has only space for 2 temporaries) + # The content scratchspace[2+k] is set at [k]P + # with scratchspace[0] untouched + if result == 1: + scratchspace[1] = P + else: + scratchspace[2] = P + for k in 2 ..< 1 shl result: + scratchspace[k+1].sum(scratchspace[k], P) + + # Set a to infinity + P.setInf() + +func scalarMulDoubling( + P: var ECP_SWei_Proj, + exponent: openArray[byte], + tmp: var ECP_SWei_Proj, + window: uint, + acc, acc_len: var uint, + e: var int + ): tuple[k, bits: uint] {.inline.} = + ## Doubling steps of doubling and add for scalar multiplication + ## Get the next k bits in range [1, window) + ## and double k times + ## Returns the number of doubling done and the corresponding bits. + ## + ## Updates iteration variables and accumulators + # + # ⚠️: Extreme care should be used to not leak + # the exponent bits nor its real bitlength + # i.e. if the exponent is zero but encoded in a + # 256-bit integer, only "256" should leak + # as for most applications like ECDSA or BLS signature schemes + # the scalar is the user secret key. + + # Get the next bits + # acc/acc_len must be uint to avoid Nim runtime checks leaking bits + # e is public + var k = window + if acc_len < window: + if e < exponent.len: + acc = (acc shl 8) or exponent[e].uint + inc e + acc_len += 8 + else: # Drained all exponent bits + k = acc_len + + let bits = (acc shr (acc_len - k)) and ((1'u32 shl k) - 1) + acc_len -= k + + # We have k bits and can do k doublings + for i in 0 ..< k: + tmp.double(P) + P = tmp + + return (k, bits) + + +func scalarMulGeneric*( + P: var ECP_SWei_Proj, + scalar: openArray[byte], + scratchspace: var openArray[ECP_SWei_Proj] + ) = + ## Elliptic Curve Scalar Multiplication + ## + ## P <- [k] P + ## + ## This uses fixed-window optimization if possible + ## `scratchspace` MUST be of size 2 .. 2^4 + ## + ## This is suitable to use with secret `scalar`, in particular + ## to derive a public key from a private key or + ## to sign a message. + ## + ## Particular care has been given to defend against the following side-channel attacks: + ## - timing attacks: all exponents of the same length + ## will take the same time including + ## a "zero" exponent of length 256-bit + ## - cache-timing attacks: Constantine does use a precomputed table + ## but when extracting a value from the table + ## the whole table is always accessed with the same pattern + ## preventing malicious attacks through CPU cache delay analysis. + ## - simple power-analysis and electromagnetic attacks: Constantine always do the same + ## double and add sequences and those cannot be analyzed to distinguish + ## the exponent 0 and 1. + ## + ## I.e. As far as the author know, Constantine implements all countermeasures to the known + ## **remote** attacks on ECC implementations. + ## + ## Disclaimer: + ## Constantine is provided as-is without any guarantees. + ## Use at your own risks. + ## Thorough evaluation of your threat model, the security of any cryptographic library you are considering, + ## and the secrets you put in jeopardy is strongly advised before putting data at risk. + ## The author would like to remind users that the best code can only mitigate + ## but not protect against human failures which are the weakest links and largest + ## backdoors to secrets exploited today. + ## + ## Constantine is resistant to + ## - Fault Injection attacks: Constantine does not have branches that could + ## be used to skip some additions and reveal which were dummy and which were real. + ## Dummy operations are like the double-and-add-always timing attack countermeasure. + ## + ## + ## Constantine DOES NOT defend against Address-Bit Differential Power Analysis attacks by default, + ## which allow differentiating between writing a 0 or a 1 to a memory cell. + ## This is a threat for smart-cards and embedded devices (for example to handle authentication to a cable or satellite service) + ## Constantine can be extended to use randomized projective coordinates to foil this attack. + + let window = scalarMulPrologue(P, scratchspace) + + # We process bits with from most to least significant. + # At each loop iteration with have acc_len bits in acc. + # To maintain constant-time the number of iterations + # or the number of operations or memory accesses should be the same + # regardless of acc & acc_len + var + acc, acc_len: uint + e = 0 + while acc_len > 0 or e < scalar.len: + let (k, bits) = scalarMulDoubling( + P, scalar, scratchspace[0], + window, acc, acc_len, e + ) + + # Window lookup: we set scratchspace[1] to the lookup value + # If the window length is 1 it's already set. + if window > 1: + # otherwise we need a constant-time lookup + # in particular we need the same memory accesses, we can't + # just index the openarray with the bits to avoid cache attacks. + for i in 1 ..< 1 shl k: + let ctl = SecretWord(i) == SecretWord(bits) + scratchspace[1].ccopy(scratchspace[1+i], ctl) + + # Multiply with the looked-up value + # we need to keep the product only ig the exponent bits are not all zeroes + scratchspace[0].sum(P, scratchspace[1]) + P.ccopy(scratchspace[0], SecretWord(bits).isNonZero()) + +func scalarMul*( + P: var ECP_SWei_Proj, + scalar: BigInt + ) = + ## Elliptic Curve Scalar Multiplication + ## + ## P <- [k] P diff --git a/constantine/elliptic/ec_weierstrass_projective.nim b/constantine/elliptic/ec_weierstrass_projective.nim index 04b14164a..f14378168 100644 --- a/constantine/elliptic/ec_weierstrass_projective.nim +++ b/constantine/elliptic/ec_weierstrass_projective.nim @@ -108,6 +108,14 @@ func neg*(P: var ECP_SWei_Proj) = ## Negate ``P`` P.y.neg(P.y) +func cneg*(P: var ECP_SWei_Proj, ctl: CTBool) = + ## Conditional negation. + ## Negate if ``ctl`` is true + var Q{.noInit.}: typeof(P) + Q.x = P.x + Q.y.neg(P.y) + P.ccopy(Q, ctl) + func sum*[F]( r: var ECP_SWei_Proj[F], P, Q: ECP_SWei_Proj[F] @@ -279,197 +287,7 @@ func `+=`*[F](P: var ECP_SWei_Proj[F], Q: ECP_SWei_Proj[F]) = tmp.sum(P, Q) P = tmp -# ############################################################ -# # -# Scalar Multiplication # -# # -# ############################################################ -# -# Scalar multiplication is a key algorithm for cryptographic protocols: -# - it is slow, -# - it is performance critical as it is used to generate signatures and authenticate messages -# - it is a high-value target as the "scalar" is very often the user secret key -# -# A safe scalar multiplication MUST: -# - Use no branching (to prevent timing and simple power analysis attacks) -# - Always do the same memory accesses (in particular for table lookups) (to prevent cache-timing attacks) -# - Not expose the bitlength of the exponent (use the curve order bitlength instead) -# -# Constantine does not make an extra effort to defend against the smart-cards -# and embedded device attacks: -# - Differential Power-Analysis which may allow for example retrieving bit content depending on the cost of writing 0 or 1 -# (Address-bit DPA by Itoh, Izu and Takenaka) -# - Electro-Magnetic which can be used in a similar way to power analysis but based on EM waves -# - Fault Attacks which can be used by actively introducing faults (via a laser for example) in an algorithm -# -# The current security efforts are focused on preventing attacks -# that are effective remotely including through the network, -# a colocated VM or a malicious process on your phone. -# -# - 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 -# -# - 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 - -template checkScalarMulScratchspaceLen(len: int) = - ## CHeck that there is a minimum of scratchspace to hold the temporaries - debug: - assert len >= 2, "Internal Error: the scratchspace for scalar multiplication should be equal or greater than 2" - -func getWindowLen(bufLen: int): uint = - ## Compute the maximum window size that fits in the scratchspace buffer - checkScalarMulScratchspaceLen(bufLen) - result = 4 - while (1 shl result) + 1 > bufLen: - dec result - -func scalarMulPrologue( - P: var ECP_SWei_Proj, - scratchspace: var openarray[ECP_SWei_Proj] - ): uint = - ## Setup the scratchspace - ## Returns the fixed-window size for scalar mul with window optimization - result = scratchspace.len.getWindowLen() - # Precompute window content, special case for window = 1 - # (i.e scratchspace has only space for 2 temporaries) - # The content scratchspace[2+k] is set at [k]P - # with scratchspace[0] untouched - if result == 1: - scratchspace[1] = P - else: - scratchspace[2] = P - for k in 2 ..< 1 shl result: - scratchspace[k+1].sum(scratchspace[k], P) - - # Set a to infinity - P.setInf() - -func scalarMulDoubling( - P: var ECP_SWei_Proj, - exponent: openArray[byte], - tmp: var ECP_SWei_Proj, - window: uint, - acc, acc_len: var uint, - e: var int - ): tuple[k, bits: uint] {.inline.} = - ## Doubling steps of doubling and add for scalar multiplication - ## Get the next k bits in range [1, window) - ## and double k times - ## Returns the number of doubling done and the corresponding bits. - ## - ## Updates iteration variables and accumulators - # - # ⚠️: Extreme care should be used to not leak - # the exponent bits nor its real bitlength - # i.e. if the exponent is zero but encoded in a - # 256-bit integer, only "256" should leak - # as for most applications like ECDSA or BLS signature schemes - # the scalar is the user secret key. - - # Get the next bits - # acc/acc_len must be uint to avoid Nim runtime checks leaking bits - # e is public - var k = window - if acc_len < window: - if e < exponent.len: - acc = (acc shl 8) or exponent[e].uint - inc e - acc_len += 8 - else: # Drained all exponent bits - k = acc_len - - let bits = (acc shr (acc_len - k)) and ((1'u32 shl k) - 1) - acc_len -= k - - # We have k bits and can do k doublings - for i in 0 ..< k: - tmp.double(P) - P = tmp - - return (k, bits) - - -func scalarMul*( - P: var ECP_SWei_Proj, - scalar: openArray[byte], - scratchspace: var openArray[ECP_SWei_Proj] - ) = - ## Elliptic Curve Scalar Multiplication - ## - ## P <- [k] P - ## - ## This uses fixed-window optimization if possible - ## `scratchspace` MUST be of size 2 .. 2^4 - ## - ## This is suitable to use with secret `scalar`, in particular - ## to derive a public key from a private key or - ## to sign a message. - ## - ## Particular care has been given to defend against the following side-channel attacks: - ## - timing attacks: all exponents of the same length - ## will take the same time including - ## a "zero" exponent of length 256-bit - ## - cache-timing attacks: Constantine does use a precomputed table - ## but when extracting a value from the table - ## the whole table is always accessed with the same pattern - ## preventing malicious attacks through CPU cache delay analysis. - ## - simple power-analysis and electromagnetic attacks: Constantine always do the same - ## double and add sequences and those cannot be analyzed to distinguish - ## the exponent 0 and 1. - ## - ## I.e. As far as the author know, Constantine implements all countermeasures to the known - ## **remote** attacks on ECC implementations. - ## - ## Disclaimer: - ## Constantine is provided as-is without any guarantees. - ## Use at your own risks. - ## Thorough evaluation of your threat model, the security of any cryptographic library you are considering, - ## and the secrets you put in jeopardy is strongly advised before putting data at risk. - ## The author would like to remind users that the best code can only mitigate - ## but not protect against human failures which are the weakest links and largest - ## backdoors to secrets exploited today. - ## - ## Constantine is resistant to - ## - Fault Injection attacks: Constantine does not have branches that could - ## be used to skip some additions and reveal which were dummy and which were real. - ## Dummy operations are like the double-and-add-always timing attack countermeasure. - ## - ## - ## Constantine DOES NOT defend against Address-Bit Differential Power Analysis attacks by default, - ## which allow differentiating between writing a 0 or a 1 to a memory cell. - ## This is a threat for smart-cards and embedded devices (for example to handle authentication to a cable or satellite service) - ## Constantine can be extended to use randomized projective coordinates to foil this attack. - - let window = scalarMulPrologue(P, scratchspace) - - # We process bits with from most to least significant. - # At each loop iteration with have acc_len bits in acc. - # To maintain constant-time the number of iterations - # or the number of operations or memory accesses should be the same - # regardless of acc & acc_len - var - acc, acc_len: uint - e = 0 - while acc_len > 0 or e < scalar.len: - let (k, bits) = scalarMulDoubling( - P, scalar, scratchspace[0], - window, acc, acc_len, e - ) - - # Window lookup: we set scratchspace[1] to the lookup value - # If the window length is 1 it's already set. - if window > 1: - # otherwise we need a constant-time lookup - # in particular we need the same memory accesses, we can't - # just index the openarray with the bits to avoid cache attacks. - for i in 1 ..< 1 shl k: - let ctl = SecretWord(i) == SecretWord(bits) - scratchspace[1].ccopy(scratchspace[1+i], ctl) - - # Multiply with the looked-up value - # we need to keep the product only ig the exponent bits are not all zeroes - scratchspace[0].sum(P, scratchspace[1]) - P.ccopy(scratchspace[0], SecretWord(bits).isNonZero()) +func double*[F](P: var ECP_SWei_Proj[F]) = + var tmp {.noInit.}: ECP_SWei_Proj[F] + tmp.double(P) + P = tmp diff --git a/tests/test_ec_bls12_381.nim b/tests/test_ec_bls12_381.nim index ca97e06a8..f8f51ce8d 100644 --- a/tests/test_ec_bls12_381.nim +++ b/tests/test_ec_bls12_381.nim @@ -13,7 +13,7 @@ import ../constantine/config/[common, curves], ../constantine/arithmetic, ../constantine/io/[io_bigints, io_ec], - ../constantine/elliptic/[ec_weierstrass_projective], + ../constantine/elliptic/[ec_weierstrass_projective, ec_scalar_mul], # Test utilities ./support/ec_reference_scalar_mult @@ -42,7 +42,7 @@ proc test( reference = P scratchSpace: array[1 shl 4, EC] - impl.scalarMul(exponentCanonical, scratchSpace) + impl.scalarMulGeneric(exponentCanonical, scratchSpace) reference.unsafe_ECmul_double_add(exponentCanonical) doAssert: bool(Q == reference) diff --git a/tests/test_ec_bn254.nim b/tests/test_ec_bn254.nim index d684407c2..6d229bfeb 100644 --- a/tests/test_ec_bn254.nim +++ b/tests/test_ec_bn254.nim @@ -13,7 +13,7 @@ import ../constantine/config/[common, curves], ../constantine/arithmetic, ../constantine/io/[io_bigints, io_ec], - ../constantine/elliptic/[ec_weierstrass_projective], + ../constantine/elliptic/[ec_weierstrass_projective, ec_scalar_mul], # Test utilities ./support/ec_reference_scalar_mult @@ -42,7 +42,7 @@ proc test( reference = P scratchSpace: array[1 shl 4, EC] - impl.scalarMul(exponentCanonical, scratchSpace) + impl.scalarMulGeneric(exponentCanonical, scratchSpace) reference.unsafe_ECmul_double_add(exponentCanonical) doAssert: bool(Q == reference) diff --git a/tests/test_ec_weierstrass_projective_g1.nim b/tests/test_ec_weierstrass_projective_g1.nim index e6cccb4fd..d901aa8f6 100644 --- a/tests/test_ec_weierstrass_projective_g1.nim +++ b/tests/test_ec_weierstrass_projective_g1.nim @@ -13,7 +13,7 @@ import ../constantine/config/[common, curves], ../constantine/arithmetic, ../constantine/io/io_bigints, - ../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective], + ../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective, ec_scalar_mul], # Test utilities ../helpers/prng_unsafe, ./support/ec_reference_scalar_mult @@ -194,7 +194,7 @@ suite "Elliptic curve in Short Weierstrass form y² = x³ + a x + b with project reference = a scratchSpace{.noInit.}: array[1 shl 4, ECP_SWei_Proj[F]] - impl.scalarMul(exponentCanonical, scratchSpace) + impl.scalarMulGeneric(exponentCanonical, scratchSpace) reference.unsafe_ECmul_double_add(exponentCanonical) check: @@ -223,7 +223,7 @@ suite "Elliptic curve in Short Weierstrass form y² = x³ + a x + b with project reference = a scratchSpace{.noInit.}: array[1 shl 4, ECP_SWei_Proj[F]] - impl.scalarMul(exponentCanonical, scratchSpace) + impl.scalarMulGeneric(exponentCanonical, scratchSpace) reference.unsafe_ECmul_double_add(exponentCanonical) check: @@ -256,7 +256,7 @@ suite "Elliptic curve in Short Weierstrass form y² = x³ + a x + b with project reference = a scratchSpace{.noInit.}: array[1 shl 4, ECP_SWei_Proj[F]] - impl.scalarMul(exponentCanonical, scratchSpace) + impl.scalarMulGeneric(exponentCanonical, scratchSpace) reference.unsafe_ECmul_double_add(exponentCanonical) check: @@ -288,7 +288,7 @@ suite "Elliptic curve in Short Weierstrass form y² = x³ + a x + b with project reference = a scratchSpace{.noInit.}: array[1 shl 4, ECP_SWei_Proj[F]] - impl.scalarMul(exponentCanonical, scratchSpace) + impl.scalarMulGeneric(exponentCanonical, scratchSpace) reference.unsafe_ECmul_double_add(exponentCanonical) check: @@ -323,20 +323,20 @@ suite "Elliptic curve in Short Weierstrass form y² = x³ + a x + b with project fImpl.sum(a, b) fReference.sum(a, b) - fImpl.scalarMul(exponentCanonical, scratchSpace) + fImpl.scalarMulGeneric(exponentCanonical, scratchSpace) fReference.unsafe_ECmul_double_add(exponentCanonical) # [k]a + [k]b - Distributed var kaImpl = a var kaRef = a - kaImpl.scalarMul(exponentCanonical, scratchSpace) + kaImpl.scalarMulGeneric(exponentCanonical, scratchSpace) kaRef.unsafe_ECmul_double_add(exponentCanonical) var kbImpl = b var kbRef = b - kbImpl.scalarMul(exponentCanonical, scratchSpace) + kbImpl.scalarMulGeneric(exponentCanonical, scratchSpace) kbRef.unsafe_ECmul_double_add(exponentCanonical) var kakbImpl{.noInit.}, kakbRef{.noInit.}: ECP_SWei_Proj[F] @@ -370,7 +370,7 @@ suite "Elliptic curve in Short Weierstrass form y² = x³ + a x + b with project reference = a scratchSpace{.noInit.}: array[1 shl 4, ECP_SWei_Proj[F]] - impl.scalarMul(exponentCanonical, scratchSpace) + impl.scalarMulGeneric(exponentCanonical, scratchSpace) reference.unsafe_ECmul_double_add(exponentCanonical) check: bool(impl == reference) From 3500bbe5d659386982ec93d524c149024965d845 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mamy=20Andr=C3=A9-Ratsimbazafy?= Date: Wed, 10 Jun 2020 22:19:00 +0200 Subject: [PATCH 14/19] We compile, and now debugging hunt --- constantine/arithmetic/bigints.nim | 17 +- constantine/arithmetic/limbs.nim | 19 ++ .../elliptic/ec_endomorphism_accel.nim | 97 ++++++++- constantine/elliptic/ec_scalar_mul.nim | 12 +- tests/test_ec_bn254.nim | 191 +++++++++--------- 5 files changed, 236 insertions(+), 100 deletions(-) diff --git a/constantine/arithmetic/bigints.nim b/constantine/arithmetic/bigints.nim index 3ef2ea251..9b682eaa9 100644 --- a/constantine/arithmetic/bigints.nim +++ b/constantine/arithmetic/bigints.nim @@ -136,11 +136,17 @@ func cadd*(a: var BigInt, b: BigInt, ctl: SecretBool): SecretBool = (SecretBool) cadd(a.limbs, b.limbs, ctl) func csub*(a: var BigInt, b: BigInt, ctl: SecretBool): SecretBool = - ## Constant-time in-place conditional addition - ## The addition is only performed if ctl is "true" - ## The result carry is always computed. + ## Constant-time in-place conditional substraction + ## The substraction is only performed if ctl is "true" + ## The result borrow is always computed. (SecretBool) csub(a.limbs, b.limbs, ctl) +func csub*(a: var BigInt, b: SecretWord, ctl: SecretBool): SecretBool = + ## Constant-time in-place conditional substraction + ## The substraction is only performed if ctl is "true" + ## The result borrow is always computed. + (SecretBool) csub(a.limbs, b, ctl) + func cdouble*(a: var BigInt, ctl: SecretBool): SecretBool = ## Constant-time in-place conditional doubling ## The doubling is only performed if ctl is "true" @@ -172,6 +178,11 @@ func sub*(a: var BigInt, b: BigInt): SecretBool = ## Returns the borrow (SecretBool) sub(a.limbs, b.limbs) +func sub*(a: var BigInt, b: SecretWord): SecretBool = + ## Constant-time in-place substraction + ## Returns the borrow + (SecretBool) sub(a.limbs, b) + func `-=`*(a: var BigInt, b: BigInt) = ## Constant-time in-place substraction ## Discards the borrow diff --git a/constantine/arithmetic/limbs.nim b/constantine/arithmetic/limbs.nim index dacd0578a..a25822c91 100644 --- a/constantine/arithmetic/limbs.nim +++ b/constantine/arithmetic/limbs.nim @@ -236,6 +236,14 @@ func sub*(a: var Limbs, b: Limbs): Borrow = for i in 0 ..< a.len: subB(result, a[i], a[i], b[i], result) +func sub*(a: var Limbs, w: SecretWord): Borrow = + ## Limbs substraction, sub a number that fits in a word + ## Returns the borrow + result = Borrow(0) + subB(result, a[0], a[0], w, result) + for i in 1 ..< a.len: + subB(result, a[i], a[i], Zero, result) + func csub*(a: var Limbs, b: Limbs, ctl: SecretBool): Borrow = ## Limbs conditional substraction ## Returns the borrow @@ -251,6 +259,17 @@ func csub*(a: var Limbs, b: Limbs, ctl: SecretBool): Borrow = subB(result, diff, a[i], b[i], result) ctl.ccopy(a[i], diff) +func csub*(a: var Limbs, w: SecretWord, ctl: SecretBool): Borrow = + ## Limbs conditional substraction, sub a number that fits in a word + ## Returns the borrow + result = Carry(0) + var diff: SecretWord + subB(result, diff, a[0], w, result) + ctl.ccopy(a[0], diff) + for i in 1 ..< a.len: + subB(result, diff, a[i], Zero, result) + ctl.ccopy(a[i], diff) + func diff*(r: var Limbs, a, b: Limbs): Borrow = ## Diff `a` and `b` into `r` ## `r` is initialized/overwritten diff --git a/constantine/elliptic/ec_endomorphism_accel.nim b/constantine/elliptic/ec_endomorphism_accel.nim index 80e8ab841..eee36c7a6 100644 --- a/constantine/elliptic/ec_endomorphism_accel.nim +++ b/constantine/elliptic/ec_endomorphism_accel.nim @@ -114,7 +114,8 @@ type ## http://graphics.stanford.edu/~seander/bithacks.html#FixedSignExtend digit {.bitsize:2.}: int8 - +# TODO: use unsigned to avoid checks and potentially leaking secrets +# or push checks off (or prove that checks can be elided once Nim has Z3 in the compiler) proc `[]`(recoding: Recoded, digitIdx: int): int8 {.inline.}= ## 0 <= digitIdx < LengthInDigits @@ -202,7 +203,9 @@ func nDimMultiScalarRecoding[M, LengthInBits, LengthInDigits: static int]( # For that floored division, bji may be negative!!! # In particular floored division of -1 is -1 not 0. # This means that arithmetic right shift must be used instead of logical right shift - static: doAssert LengthInDigits == LengthInBits + 1 + static: doAssert LengthInDigits == LengthInBits, + "Length in digits: " & $LengthInDigits & " Length in bits: " & $LengthInBits + # " # VScode broken highlight # assert src[0].isOdd - Only happen on implementation error, we don't want to leak a single bit var k = src # Keep the source multiscalar in registers @@ -267,10 +270,10 @@ func buildLookupTable[M: static int, F]( # 3. Use Montgomery simultaneous inversion to have the table in # affine coordinate so that we can use mixed addition in teh main loop lut[0] = P - for u in 1 ..< 1 shl (M-1): + for u in 1'u32 ..< 1 shl (M-1): # The recoding allows usage of 2^(n-1) table instead of the usual 2^n with NAF let msb = u.log2() # No undefined, u != 0 - lut[u].sum(lut[u.clearBit(msb)], endomorphisms[msb-1]) + lut[u].sum(lut[u.clearBit(msb)], endomorphisms[msb]) # } # highlight bug, ... # Chapter 6.3.1 - Guide to Pairing-based Cryptography @@ -308,7 +311,7 @@ func decomposeScalar_BN254_Snarks_G1[M, scalBits, miniBits: static int]( alphaHats = (BigInt[66].fromHex"0x2d91d232ec7e0b3d7", BigInt[130].fromHex"0x24ccef014a773d2d25398fd0300ff6565") - var alphas{.noInit.} : array[M, BigInt[scalBits]] # TODO size 66+254 and 130+254 + var alphas{.noInit.}: array[M, BigInt[scalBits]] # TODO size 66+254 and 130+254 staticFor i, 0, M: alphas[i].prod_high_words(alphaHats[i], scalar, w) @@ -328,6 +331,90 @@ func decomposeScalar_BN254_Snarks_G1[M, scalBits, miniBits: static int]( miniScalars[miniScalarIdx].copyTruncatedFrom(k[miniScalarIdx]) +func tableIndex(glv: GLV_SAC, bit: int): SecretWord = + ## Compose the secret table index from + ## the GLV-SAC representation and the "bit" accessed + # TODO: + # We are currently storing 2-bit for 0, 1, -1 in the GLV-SAC representation + # but since columns have all the same sign, determined by k0, + # we only need 0 and 1 dividing storage per 2 + staticFor i, 1, GLV_SAC.M: + result = result or SecretWord((glv[i][bit] and 1) shl i) + +func isNeg(glv: GLV_SAC, bit: int): SecretBool = + ## Returns true if the bit requires substraction + SecretBool(glv[0][bit] < 0) + +func secretLookup[T](dst: var T, table: openArray[T], index: SecretWord) = + ## Load a table[index] into `dst` + ## This is constant-time, whatever the `index`, its value is not leaked + ## This is also protected against cache-timing attack by always scanning the whole table + for i in 0 ..< table.len: + let selector = SecretWord(i) == index + dst.ccopy(table[i], selector) + +func scalarMulGLV_BN254*( + P: var ECP_SWei_Proj, + scalar: BigInt[BN254_Snarks.getCurveOrderBitwidth()] + ) = + ## Elliptic Curve Scalar Multiplication + ## + ## P <- [k] P + ## + ## This is a scalar multiplication accelerated by an endomorphism + ## via the GLV (Gallant-lambert-Vanstone) decomposition. + const M = 2 + + # 1. Compute endomorphisms + var endomorphisms{.noInit.}: array[M-1, typeof(P)] + endomorphisms[0] = P + endomorphisms[0].x *= BN254_Snarks.getCubicRootOfUnity_mod_p() + + # 2. Decompose scalar into mini-scalars + const L = (BN254_Snarks.getCurveOrderBitwidth() + M - 1) div M + var miniScalars {.noInit.}: array[M, BigInt[L]] + scalar.decomposeScalar_BN254_Snarks_G1( + miniScalars + ) + + # 3. TODO: handle negative mini-scalars + # Either negate the associated base and the scalar (in the `endomorphisms` array) + # Or use Algorithm 3 from Faz et al which can encode the sign + # in the GLV representation at the low low price of 1 bit + + # 4. Precompute lookup table + var lut: array[1 shl (M-1), ECP_SWei_Proj] + buildLookupTable(P, endomorphisms, lut) + # TODO: Montgomery simultaneous inversion (or other simultaneous inversion techniques) + # so that we use mixed addition formulas in the main loop + + # 5. Recode the miniscalars + # we need the base miniscalar (that encodes the sign) + # to be odd, and this in constant-time to protect the secret least-significant bit. + var k0isOdd = miniScalars[0].isOdd() + discard miniScalars[0].csub(SecretWord(1), not k0isOdd) + + var recoded {.noInit.}: GLV_SAC[2, L] + recoded.nDimMultiScalarRecoding(miniScalars) + + # 6. Proceed to GLV accelerated scalar multiplication + var Q {.noInit.}: typeof(P) + Q.secretLookup(lut, recoded.tableIndex(L-1)) + Q.cneg(recoded.isNeg(L-1)) + + for i in countdown(L-2, 0): + Q.double() + var tmp {.noInit.}: typeof(Q) + tmp.secretLookup(lut, recoded.tableIndex(i)) + tmp.cneg(recoded.isNeg(i)) + Q += tmp + + # Now we need to correct if the sign miniscalar was not odd + # we missed an addition + lut[0] += Q # Contains Q + P0 + P = Q + P.ccopy(lut[0], not k0isOdd) + # Sanity checks # ---------------------------------------------------------------- # See page 7 of diff --git a/constantine/elliptic/ec_scalar_mul.nim b/constantine/elliptic/ec_scalar_mul.nim index 53709ac42..bff92476b 100644 --- a/constantine/elliptic/ec_scalar_mul.nim +++ b/constantine/elliptic/ec_scalar_mul.nim @@ -212,7 +212,17 @@ func scalarMulGeneric*( func scalarMul*( P: var ECP_SWei_Proj, scalar: BigInt - ) = + ) {.inline.} = ## Elliptic Curve Scalar Multiplication ## ## P <- [k] P + # This calls endomorphism accelerated scalar mul if available + # or the generic scalar mul otherwise + when ECP_SWei_Proj.F.C == BN254_Snarks: + scalarMulGLV_BN254(P, scalar) + else: + var + scratchSpace: array[1 shl 4, ECP_SWei_Proj] + scalarCanonicalBE: array[(scalar.bits+7)div 8, byte] # canonical big endian representation + scalarCanonicalBE.exportRawUint(scalar, bigEndian) # Export is constant-time + P.scalarMulGeneric(scratchSpace) diff --git a/tests/test_ec_bn254.nim b/tests/test_ec_bn254.nim index 6d229bfeb..13a8c68a2 100644 --- a/tests/test_ec_bn254.nim +++ b/tests/test_ec_bn254.nim @@ -13,7 +13,7 @@ import ../constantine/config/[common, curves], ../constantine/arithmetic, ../constantine/io/[io_bigints, io_ec], - ../constantine/elliptic/[ec_weierstrass_projective, ec_scalar_mul], + ../constantine/elliptic/[ec_weierstrass_projective, ec_scalar_mul, ec_endomorphism_accel], # Test utilities ./support/ec_reference_scalar_mult @@ -33,20 +33,29 @@ proc test( var Q: EC let qOK = Q.fromHex(Qx, Qy) - let exponent = EC.F.C.matchingBigInt.fromHex(scalar) + let exponent = BigInt[EC.F.C.getCurveOrderBitwidth()].fromHex(scalar) var exponentCanonical: array[(exponent.bits+7) div 8, byte] exponentCanonical.exportRawUint(exponent, bigEndian) var impl = P reference = P + endo = P scratchSpace: array[1 shl 4, EC] impl.scalarMulGeneric(exponentCanonical, scratchSpace) reference.unsafe_ECmul_double_add(exponentCanonical) + endo.scalarMulGLV_BN254(exponent) + + echo "Q: ", Q.toHex() + echo "reference: ", reference.toHex() + echo "impl: ", impl.toHex() + echo "endo: ", endo.toHex() + doAssert doAssert: bool(Q == reference) doAssert: bool(Q == impl) + doAssert: bool(Q == endo) suite "Scalar Multiplication: BN254 implementation (and unsafe reference impl) vs SageMath": # Generated via sage sage/testgen_bn254_snarks.sage @@ -60,92 +69,92 @@ suite "Scalar Multiplication: BN254 implementation (and unsafe reference impl) v Qy = "2fa00719ce37465dbe7037f723ed5df08c76b9a27a4dd80d86c0ee5157349b96" ) - test( - id = 2, - EC = ECP_SWei_Proj[Fp[BN254_Snarks]], - Px = "2724750abe620fce759b6f18729e40f891a514160d477811a44b222372cc4ea3", - Py = "105cdcbe363921790a56bf2696e73642447c60b814827ca4dba86c814912c98a", - scalar = "2f5c2960850eabadab1e5595ff0bf841206885653e7f2024248b281a86744790", - Qx = "57d2dcbc665fb93fd5119bb982c29700d025423d60a42b5fe17210fd5a868fd", - Qy = "2abad564ff78fbc266dfb77bdd110b22271136b33ce5049fb3ca05107787abc" - ) - - test( - id = 3, - EC = ECP_SWei_Proj[Fp[BN254_Snarks]], - Px = "39bc19c41835082f86ca046b71875b051575072e4d6a4aeedac31eee34b07df", - Py = "1fdbf42fc20421e1e775fd93ed1888d614f7e39067e7443f21b6a4817481c346", - scalar = "29e140c33f706c0111443699b0b8396d8ead339a3d6f3c212b08749cf2a16f6b", - Qx = "83895d1c7a2b15a5dfe9371983196591415182978e8ff0e83262e32d768c712", - Qy = "2ed8b88e1cd08814ce1d1929d0e4bba6fb5897f915b3525cf12349256da95499" - ) - - test( - id = 4, - EC = ECP_SWei_Proj[Fp[BN254_Snarks]], - Px = "157a3e1ff9dabccced9746e19855a9438098be6d734f07d1c069aa1bd05b8d87", - Py = "1c96bf3e48bc1a6635d93d4f1302a0eba39bd907c5d861f2a9d0c714ee60f04d", - scalar = "29b05bd55963e262e0fa458c76297fb5be3ec1421fdb1354789f68fdce81dc2c", - Qx = "196aeca74447934eeaba0f2263177fcb7eb239985814f8ef2d7bf08677108c9", - Qy = "1f5aa4c7df4a9855113c63d8fd55c512c7e919b8ae0352e280bdb1009299c3b2" - ) - - test( - id = 5, - EC = ECP_SWei_Proj[Fp[BN254_Snarks]], - Px = "2f260967d4cd5d15f98c0a0a9d5abaae0c70d3b8d83e1e884586cd6ece395fe7", - Py = "2a102c7aebdfaa999d5a99984148ada142f72f5d4158c10368a2e13dded886f6", - scalar = "1796de74c1edac90d102e7c33f3fad94304eaff4a67a018cae678774d377f6cd", - Qx = "28c73e276807863ecf4ae60b1353790f10f176ca8c55b3db774e33c569ef39d5", - Qy = "c386e24828cead255ec7657698559b23a26fc9bd5db70a1fe20b48ecfbd6db9" - ) - - test( - id = 6, - EC = ECP_SWei_Proj[Fp[BN254_Snarks]], - Px = "1b4ccef57f4411360a02b8228e4251896c9492ff93a69ba3720da0cd46a04e83", - Py = "1fabcb215bd7c06ead2e6b0167497efc2cdd3dbacf69bcb0244142fd63c1e405", - scalar = "116741cd19dac61c5e77877fc6fef40f363b164b501dfbdbc09e17ea51d6beb0", - Qx = "192ca2e120b0f5296baf7cc47bfebbbc74748c8847bbdbe485bcb796de2622aa", - Qy = "8bc6b1aa4532c727be8fd21a8176d55bc721c727af327f601f7a8dff655b0b9" - ) - - test( - id = 7, - EC = ECP_SWei_Proj[Fp[BN254_Snarks]], - Px = "2807c88d6759280d6bd83a54d349a533d1a66dc32f72cab8114ab707f10e829b", - Py = "dbf0d486aeed3d303880f324faa2605aa0219e35661bc88150470c7df1c0b61", - scalar = "2a5976268563870739ced3e6efd8cf53887e8e4426803377095708509dd156ca", - Qx = "2841f67de361436f64e582a134fe36ab7196334c758a07e732e1cf1ccb35a476", - Qy = "21fb9b8311e53832044be5ff024f737aee474bc504c7c158fe760cc999da8612" - ) - - test( - id = 8, - EC = ECP_SWei_Proj[Fp[BN254_Snarks]], - Px = "2754a174a33a55f2a31573767e9bf5381b47dca1cbebc8b68dd4df58b3f1cc2", - Py = "f222f59c8893ad87c581dacb3f8b6e7c20e7a13bc5fb6e24262a3436d663b1", - scalar = "25d596bf6caf4565fbfd22d81f9cef40c8f89b1e5939f20caa1b28056e0e4f58", - Qx = "2b48dd3ace8e403c2905f00cdf13814f0dbecb0c0465e6455fe390cc9730f5a", - Qy = "fe65f0cd4ae0d2e459daa4163f32deed1250b5c384eb5aeb933162a41793d25" - ) - - test( - id = 9, - EC = ECP_SWei_Proj[Fp[BN254_Snarks]], - Px = "273bf6c679d8e880034590d16c007bbabc6c65ed870a263b5d1ce7375c18fd7", - Py = "2904086cb9e33657999229b082558a74c19b2b619a0499afb2e21d804d8598ee", - scalar = "67a499a389129f3902ba6140660c431a56811b53de01d043e924711bd341e53", - Qx = "1d827e4569f17f068457ffc52f1c6ed7e2ec89b8b520efae48eff41827f79128", - Qy = "be8c488bb9587bcb0faba916277974afe12511e54fbd749e27d3d7efd998713" - ) - - test( - id = 10, - EC = ECP_SWei_Proj[Fp[BN254_Snarks]], - Px = "ec892c09a5f1c68c1bfec7780a1ebd279739383f2698eeefbba745b3e717fd5", - Py = "23d273a1b9750fe1d4ebd4b7c25f4a8d7d94f6662c436305cca8ff2cdbd3f736", - scalar = "d2f09ceaa2638b7ac3d7d4aa9eff7a12e93dc85db0f9676e5f19fb86d6273e9", - Qx = "305d7692b141962a4a92038adfacc0d2691e5589ed097a1c661cc48c84e2b64e", - Qy = "bafa230a0f5cc2fa3cf07fa46312cb724fc944b097890fa60f2cf42a1be7963" - ) + # test( + # id = 2, + # EC = ECP_SWei_Proj[Fp[BN254_Snarks]], + # Px = "2724750abe620fce759b6f18729e40f891a514160d477811a44b222372cc4ea3", + # Py = "105cdcbe363921790a56bf2696e73642447c60b814827ca4dba86c814912c98a", + # scalar = "2f5c2960850eabadab1e5595ff0bf841206885653e7f2024248b281a86744790", + # Qx = "57d2dcbc665fb93fd5119bb982c29700d025423d60a42b5fe17210fd5a868fd", + # Qy = "2abad564ff78fbc266dfb77bdd110b22271136b33ce5049fb3ca05107787abc" + # ) + + # test( + # id = 3, + # EC = ECP_SWei_Proj[Fp[BN254_Snarks]], + # Px = "39bc19c41835082f86ca046b71875b051575072e4d6a4aeedac31eee34b07df", + # Py = "1fdbf42fc20421e1e775fd93ed1888d614f7e39067e7443f21b6a4817481c346", + # scalar = "29e140c33f706c0111443699b0b8396d8ead339a3d6f3c212b08749cf2a16f6b", + # Qx = "83895d1c7a2b15a5dfe9371983196591415182978e8ff0e83262e32d768c712", + # Qy = "2ed8b88e1cd08814ce1d1929d0e4bba6fb5897f915b3525cf12349256da95499" + # ) + + # test( + # id = 4, + # EC = ECP_SWei_Proj[Fp[BN254_Snarks]], + # Px = "157a3e1ff9dabccced9746e19855a9438098be6d734f07d1c069aa1bd05b8d87", + # Py = "1c96bf3e48bc1a6635d93d4f1302a0eba39bd907c5d861f2a9d0c714ee60f04d", + # scalar = "29b05bd55963e262e0fa458c76297fb5be3ec1421fdb1354789f68fdce81dc2c", + # Qx = "196aeca74447934eeaba0f2263177fcb7eb239985814f8ef2d7bf08677108c9", + # Qy = "1f5aa4c7df4a9855113c63d8fd55c512c7e919b8ae0352e280bdb1009299c3b2" + # ) + + # test( + # id = 5, + # EC = ECP_SWei_Proj[Fp[BN254_Snarks]], + # Px = "2f260967d4cd5d15f98c0a0a9d5abaae0c70d3b8d83e1e884586cd6ece395fe7", + # Py = "2a102c7aebdfaa999d5a99984148ada142f72f5d4158c10368a2e13dded886f6", + # scalar = "1796de74c1edac90d102e7c33f3fad94304eaff4a67a018cae678774d377f6cd", + # Qx = "28c73e276807863ecf4ae60b1353790f10f176ca8c55b3db774e33c569ef39d5", + # Qy = "c386e24828cead255ec7657698559b23a26fc9bd5db70a1fe20b48ecfbd6db9" + # ) + + # test( + # id = 6, + # EC = ECP_SWei_Proj[Fp[BN254_Snarks]], + # Px = "1b4ccef57f4411360a02b8228e4251896c9492ff93a69ba3720da0cd46a04e83", + # Py = "1fabcb215bd7c06ead2e6b0167497efc2cdd3dbacf69bcb0244142fd63c1e405", + # scalar = "116741cd19dac61c5e77877fc6fef40f363b164b501dfbdbc09e17ea51d6beb0", + # Qx = "192ca2e120b0f5296baf7cc47bfebbbc74748c8847bbdbe485bcb796de2622aa", + # Qy = "8bc6b1aa4532c727be8fd21a8176d55bc721c727af327f601f7a8dff655b0b9" + # ) + + # test( + # id = 7, + # EC = ECP_SWei_Proj[Fp[BN254_Snarks]], + # Px = "2807c88d6759280d6bd83a54d349a533d1a66dc32f72cab8114ab707f10e829b", + # Py = "dbf0d486aeed3d303880f324faa2605aa0219e35661bc88150470c7df1c0b61", + # scalar = "2a5976268563870739ced3e6efd8cf53887e8e4426803377095708509dd156ca", + # Qx = "2841f67de361436f64e582a134fe36ab7196334c758a07e732e1cf1ccb35a476", + # Qy = "21fb9b8311e53832044be5ff024f737aee474bc504c7c158fe760cc999da8612" + # ) + + # test( + # id = 8, + # EC = ECP_SWei_Proj[Fp[BN254_Snarks]], + # Px = "2754a174a33a55f2a31573767e9bf5381b47dca1cbebc8b68dd4df58b3f1cc2", + # Py = "f222f59c8893ad87c581dacb3f8b6e7c20e7a13bc5fb6e24262a3436d663b1", + # scalar = "25d596bf6caf4565fbfd22d81f9cef40c8f89b1e5939f20caa1b28056e0e4f58", + # Qx = "2b48dd3ace8e403c2905f00cdf13814f0dbecb0c0465e6455fe390cc9730f5a", + # Qy = "fe65f0cd4ae0d2e459daa4163f32deed1250b5c384eb5aeb933162a41793d25" + # ) + + # test( + # id = 9, + # EC = ECP_SWei_Proj[Fp[BN254_Snarks]], + # Px = "273bf6c679d8e880034590d16c007bbabc6c65ed870a263b5d1ce7375c18fd7", + # Py = "2904086cb9e33657999229b082558a74c19b2b619a0499afb2e21d804d8598ee", + # scalar = "67a499a389129f3902ba6140660c431a56811b53de01d043e924711bd341e53", + # Qx = "1d827e4569f17f068457ffc52f1c6ed7e2ec89b8b520efae48eff41827f79128", + # Qy = "be8c488bb9587bcb0faba916277974afe12511e54fbd749e27d3d7efd998713" + # ) + + # test( + # id = 10, + # EC = ECP_SWei_Proj[Fp[BN254_Snarks]], + # Px = "ec892c09a5f1c68c1bfec7780a1ebd279739383f2698eeefbba745b3e717fd5", + # Py = "23d273a1b9750fe1d4ebd4b7c25f4a8d7d94f6662c436305cca8ff2cdbd3f736", + # scalar = "d2f09ceaa2638b7ac3d7d4aa9eff7a12e93dc85db0f9676e5f19fb86d6273e9", + # Qx = "305d7692b141962a4a92038adfacc0d2691e5589ed097a1c661cc48c84e2b64e", + # Qy = "bafa230a0f5cc2fa3cf07fa46312cb724fc944b097890fa60f2cf42a1be7963" + # ) From 09410f64617281f2496c46b5aaeef86644b9b73c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mamy=20Andr=C3=A9-Ratsimbazafy?= Date: Sat, 13 Jun 2020 19:13:10 +0200 Subject: [PATCH 15/19] More helpers to debug GLV scalar Mul --- constantine/config/curves_declaration.nim | 2 +- .../elliptic/ec_endomorphism_accel.nim | 100 ++++++++++++++++-- sage/bn254_lattice_decomposition.sage | 65 +++++++++--- tests/test_ec_bn254.nim | 1 - 4 files changed, 144 insertions(+), 24 deletions(-) diff --git a/constantine/config/curves_declaration.nim b/constantine/config/curves_declaration.nim index d0da9ab7a..62309962d 100644 --- a/constantine/config/curves_declaration.nim +++ b/constantine/config/curves_declaration.nim @@ -91,7 +91,7 @@ declareCurves: family: BarretoNaehrig bn_u_bitwidth: 63 bn_u: "0x44E992B44A6909F1" # u: 4965661367192848881 - cubicRootOfUnity_modP: "0x59e26bcea0d48bacd4f263f1acdb5c4f5763473177fffffe" + cubicRootOfUnity_modP: "0x30644e72e131a0295e6dd9e7e0acccb0c28f069fbb966e3de4bd44e5607cfd48" # For sanity checks cubicRootOfUnity_modR: "0x30644e72e131a029048b6e193fd84104cc37a73fec2bc5e9b8ca0b2d36636f23" diff --git a/constantine/elliptic/ec_endomorphism_accel.nim b/constantine/elliptic/ec_endomorphism_accel.nim index eee36c7a6..de08937ed 100644 --- a/constantine/elliptic/ec_endomorphism_accel.nim +++ b/constantine/elliptic/ec_endomorphism_accel.nim @@ -203,7 +203,7 @@ func nDimMultiScalarRecoding[M, LengthInBits, LengthInDigits: static int]( # For that floored division, bji may be negative!!! # In particular floored division of -1 is -1 not 0. # This means that arithmetic right shift must be used instead of logical right shift - static: doAssert LengthInDigits == LengthInBits, + static: doAssert LengthInDigits == LengthInBits+1, "Length in digits: " & $LengthInDigits & " Length in bits: " & $LengthInBits # " # VScode broken highlight # assert src[0].isOdd - Only happen on implementation error, we don't want to leak a single bit @@ -339,7 +339,7 @@ func tableIndex(glv: GLV_SAC, bit: int): SecretWord = # but since columns have all the same sign, determined by k0, # we only need 0 and 1 dividing storage per 2 staticFor i, 1, GLV_SAC.M: - result = result or SecretWord((glv[i][bit] and 1) shl i) + result = result or SecretWord((glv[i][bit] and 1) shl (i-1)) func isNeg(glv: GLV_SAC, bit: int): SecretBool = ## Returns true if the bit requires substraction @@ -366,13 +366,13 @@ func scalarMulGLV_BN254*( const M = 2 # 1. Compute endomorphisms - var endomorphisms{.noInit.}: array[M-1, typeof(P)] + var endomorphisms: array[M-1, typeof(P)] # TODO: zero-init not required endomorphisms[0] = P endomorphisms[0].x *= BN254_Snarks.getCubicRootOfUnity_mod_p() # 2. Decompose scalar into mini-scalars const L = (BN254_Snarks.getCurveOrderBitwidth() + M - 1) div M - var miniScalars {.noInit.}: array[M, BigInt[L]] + var miniScalars: array[M, BigInt[L]] # TODO: zero-init not required scalar.decomposeScalar_BN254_Snarks_G1( miniScalars ) @@ -383,7 +383,7 @@ func scalarMulGLV_BN254*( # in the GLV representation at the low low price of 1 bit # 4. Precompute lookup table - var lut: array[1 shl (M-1), ECP_SWei_Proj] + var lut: array[1 shl (M-1), ECP_SWei_Proj] # TODO: zero-init not required buildLookupTable(P, endomorphisms, lut) # TODO: Montgomery simultaneous inversion (or other simultaneous inversion techniques) # so that we use mixed addition formulas in the main loop @@ -394,17 +394,17 @@ func scalarMulGLV_BN254*( var k0isOdd = miniScalars[0].isOdd() discard miniScalars[0].csub(SecretWord(1), not k0isOdd) - var recoded {.noInit.}: GLV_SAC[2, L] + var recoded: GLV_SAC[2, L+1] # zero-init required recoded.nDimMultiScalarRecoding(miniScalars) # 6. Proceed to GLV accelerated scalar multiplication - var Q {.noInit.}: typeof(P) + var Q: typeof(P) # TODO: zero-init not required Q.secretLookup(lut, recoded.tableIndex(L-1)) Q.cneg(recoded.isNeg(L-1)) for i in countdown(L-2, 0): Q.double() - var tmp {.noInit.}: typeof(Q) + var tmp: typeof(Q) # TODO: zero-init not required tmp.secretLookup(lut, recoded.tableIndex(i)) tmp.cneg(recoded.isNeg(i)) Q += tmp @@ -563,3 +563,87 @@ when isMainModule: main_decomp() + + echo "---------------------------------------------" + + # This tests the multiplication against the Table 1 + # of the paper + + # Coef Decimal Binary GLV-SAC recoded + # | k0 | | 11 | | 0 1 0 1 1 | | 1 -1 1 -1 1 | + # | k1 | = | 6 | = | 0 0 1 1 0 | = | 1 -1 0 -1 0 | + # | k2 | | 14 | | 0 1 1 1 0 | | 1 0 0 -1 0 | + # | k3 | | 3 | | 0 0 0 1 1 | | 0 0 1 -1 1 | + + # i | 3 2 1 0 + # -------------------+---------------------------------------------------------------------- + # 2Q | 2P0+2P1+2P2 2P0+2P1+4P2 6P0+4P1+8P2+2P3 10P0+6P1+14P2+2P3 + # Q + sign_i T[ki] | P0+P1+2P2 3P0+2P1+4P2+P3 5P0+3P1+7P2+P3 11P0+6P1+14P2+3P3 + + type Endo = enum + P0 + P1 + P2 + P3 + + func buildLookupTable_reuse[M: static int]( + P: Endo, + endomorphisms: array[M-1, Endo], + lut: var array[1 shl (M-1), set[Endo]], + ) = + ## Checking the LUT by building strings of endomorphisms additions + ## This reuses previous table entries so that only one addition is done + ## per new entries + lut[0].incl P + for u in 1'u32 ..< 1 shl (M-1): + let msb = u.log2() # No undefined, u != 0 + lut[u] = lut[u.clearBit(msb)] + {endomorphisms[msb]} + + + proc mainFullMul() = + const M = 4 # GLS-4 decomposition + const miniBitwidth = 4 # Bitwidth of the miniscalars resulting from scalar decomposition + const L = miniBitwidth + 1 # Bitwidth of the recoded scalars + + var k: MultiScalar[M, miniBitwidth] + var kRecoded: GLV_SAC[M, L] + + k[0].fromUint(11) + k[1].fromUint(6) + k[2].fromuint(14) + k[3].fromUint(3) + + kRecoded.nDimMultiScalarRecoding(k) + + echo kRecoded.toString() + + var lut: array[1 shl (M-1), set[Endo]] + let + P = P0 + endomorphisms = [P1, P2, P3] + + buildLookupTable_reuse(P, endomorphisms, lut) + echo lut + + var Q: array[Endo, int] + + # Multiplication + assert bool k[0].isOdd() + # Q = sign_l-1 P[K_l-1] + let idx = kRecoded.tableIndex(L-1) + for p in lut[int(idx)]: + Q[p] = if kRecoded.isNeg(L-1).bool: -1 else: 1 + # Loop + for i in countdown(L-2, 0): + # Q = 2Q + for val in Q.mitems: val *= 2 + echo "2Q: ", Q + # Q = Q + sign_l-1 P[K_l-1] + let idx = kRecoded.tableIndex(i) + for p in lut[int(idx)]: + Q[p] += (if kRecoded.isNeg(i).bool: -1 else: 1) + echo "Q + sign_l-1 P[K_l-1]: ", Q + + echo Q + + mainFullMul() diff --git a/sage/bn254_lattice_decomposition.sage b/sage/bn254_lattice_decomposition.sage index 53cd624e1..929762006 100644 --- a/sage/bn254_lattice_decomposition.sage +++ b/sage/bn254_lattice_decomposition.sage @@ -20,14 +20,15 @@ r = 36*u^4 + 36*u^3 + 18*u^2 + 6*u + 1 cofactor = 1 # Cube root of unity (mod r) formula for any BN curves -lambda1 = (-(36*u^3+18*u^2+6*u+2)) -assert lambda1^3 % r == 1 -print('λᵩ1 : ' + lambda1.hex()) -print('λᵩ1+r: ' + (lambda1+r).hex()) +lambda1_r = (-(36*u^3+18*u^2+6*u+2)) +assert lambda1_r^3 % r == 1 +print('λᵩ1 : ' + lambda1_r.hex()) +print('λᵩ1+r: ' + (lambda1_r+r).hex()) +print('λᵩ1+r: ' + (lambda1_r+r).hex()) -lambda2 = (36*u^4-1) -assert lambda2^3 % r == 1 -print('λᵩ2 : ' + lambda2.hex()) +lambda2_r = (36*u^4-1) +assert lambda2_r^3 % r == 1 +print('λᵩ2 : ' + lambda2_r.hex()) # Finite fields F = GF(p) @@ -43,6 +44,30 @@ b = 3 G1 = EllipticCurve(F, [0, b]) # G2 = EllipticCurve(F2, [0, b/beta]) +(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()) + +# Check +def checkEndo(): + P = G1.random_point() + (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 Q1 == Qendo1 + assert Q2 == Qendo1 + + print('Endomorphism OK with 𝜑1') + +checkEndo() + # Lattice b = [ [2*u+1, 6*u^2+4*u+1], @@ -66,19 +91,31 @@ def getGLV2_decomp(scalar): assert int(k0).bit_length() <= (int(r).bit_length() + 1) // 2 assert int(k1).bit_length() <= (int(r).bit_length() + 1) // 2 - assert scalar == (k0 + k1 * (lambda1 % r)) % r - assert scalar == (k0 + k1 * (lambda2 % r)) % r + assert scalar == (k0 + k1 * (lambda1_r % r)) % r + assert scalar == (k0 + k1 * (lambda2_r % r)) % r return k0, k1 -# Test generator -set_random_seed(1337) - -for i in range(10): - print('---------------------------------------') +def scalarMulGLV(): scalar = randrange(r) # Pick an integer below curve order print('scalar: ' + Integer(scalar).hex()) k0, k1 = getGLV2_decomp(scalar) print('k0: ' + k0.hex()) print('k1: ' + k1.hex()) + + P0 = G1.random_point() + P1 = (lambda1_r % r) * P0 + (Px, Py, Pz) = P0 + P1_endo = G1([Px*phi1 % p, Py, Pz]) + + expected = scalar * P0 + decomp = k0*P0 + k1*P1 + assert expected == decomp + +# Test generator +set_random_seed(1337) + +for i in range(1): + print('---------------------------------------') + scalarMulGLV() diff --git a/tests/test_ec_bn254.nim b/tests/test_ec_bn254.nim index 13a8c68a2..e2743b3f2 100644 --- a/tests/test_ec_bn254.nim +++ b/tests/test_ec_bn254.nim @@ -51,7 +51,6 @@ proc test( echo "reference: ", reference.toHex() echo "impl: ", impl.toHex() echo "endo: ", endo.toHex() - doAssert doAssert: bool(Q == reference) doAssert: bool(Q == impl) From 4ecd12640580433bf11e59574b25ce4f04af9fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mamy=20Andr=C3=A9-Ratsimbazafy?= Date: Sat, 13 Jun 2020 19:42:45 +0200 Subject: [PATCH 16/19] Fix conditional negation --- constantine/elliptic/ec_weierstrass_projective.nim | 1 + constantine/io/io_ec.nim | 2 +- sage/testgen_bls12_381.sage | 2 +- sage/testgen_bn254_snarks.sage | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/constantine/elliptic/ec_weierstrass_projective.nim b/constantine/elliptic/ec_weierstrass_projective.nim index f14378168..9d207444e 100644 --- a/constantine/elliptic/ec_weierstrass_projective.nim +++ b/constantine/elliptic/ec_weierstrass_projective.nim @@ -114,6 +114,7 @@ func cneg*(P: var ECP_SWei_Proj, ctl: CTBool) = var Q{.noInit.}: typeof(P) Q.x = P.x Q.y.neg(P.y) + Q.z = P.z P.ccopy(Q, ctl) func sum*[F]( diff --git a/constantine/io/io_ec.nim b/constantine/io/io_ec.nim index eab593055..35c6edd0a 100644 --- a/constantine/io/io_ec.nim +++ b/constantine/io/io_ec.nim @@ -42,7 +42,7 @@ func toHex*(P: ECP_SWei_Proj): string = result &= ", y: " result &= P.y.tohex(bigEndian) result &= ", z: " - result &= P.y.tohex(bigEndian) + result &= P.z.tohex(bigEndian) result &= ')' func fromHex*(dst: var ECP_SWei_Proj, x, y: string): bool {.raises: [ValueError].}= diff --git a/sage/testgen_bls12_381.sage b/sage/testgen_bls12_381.sage index ee96098a5..fb8151927 100644 --- a/sage/testgen_bls12_381.sage +++ b/sage/testgen_bls12_381.sage @@ -50,7 +50,7 @@ for i in range(10): print('Qx: ' + Integer(Qx).hex()) print('Qy: ' + Integer(Qy).hex()) print('Qz: ' + Integer(Qz).hex()) - +print('=========================================') # CurveOrder sanity check # diff --git a/sage/testgen_bn254_snarks.sage b/sage/testgen_bn254_snarks.sage index 0e9a5f009..9afd828c9 100644 --- a/sage/testgen_bn254_snarks.sage +++ b/sage/testgen_bn254_snarks.sage @@ -50,6 +50,7 @@ for i in range(10): print('Qx: ' + Integer(Qx).hex()) print('Qy: ' + Integer(Qy).hex()) print('Qz: ' + Integer(Qz).hex()) +print('=========================================') # CurveOrder sanity check # From 10ded15de898e6f7e1703a319418898f74c90a1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mamy=20Andr=C3=A9-Ratsimbazafy?= Date: Sat, 13 Jun 2020 21:07:01 +0200 Subject: [PATCH 17/19] Endomorphism accelerated scalar mul working for BN254 curve --- .../elliptic/ec_endomorphism_accel.nim | 6 +- .../elliptic/ec_weierstrass_projective.nim | 11 ++ constantine/io/io_ec.nim | 12 +- sage/bn254_lattice_decomposition.sage | 73 ++++++- tests/test_ec_bn254.nim | 183 +++++++++--------- 5 files changed, 179 insertions(+), 106 deletions(-) diff --git a/constantine/elliptic/ec_endomorphism_accel.nim b/constantine/elliptic/ec_endomorphism_accel.nim index de08937ed..164244388 100644 --- a/constantine/elliptic/ec_endomorphism_accel.nim +++ b/constantine/elliptic/ec_endomorphism_accel.nim @@ -371,8 +371,8 @@ func scalarMulGLV_BN254*( endomorphisms[0].x *= BN254_Snarks.getCubicRootOfUnity_mod_p() # 2. Decompose scalar into mini-scalars - const L = (BN254_Snarks.getCurveOrderBitwidth() + M - 1) div M - var miniScalars: array[M, BigInt[L]] # TODO: zero-init not required + const L = (BN254_Snarks.getCurveOrderBitwidth() + M - 1) div M + 1 + var miniScalars: array[M, BigInt[L-1]] # TODO: zero-init not required scalar.decomposeScalar_BN254_Snarks_G1( miniScalars ) @@ -394,7 +394,7 @@ func scalarMulGLV_BN254*( var k0isOdd = miniScalars[0].isOdd() discard miniScalars[0].csub(SecretWord(1), not k0isOdd) - var recoded: GLV_SAC[2, L+1] # zero-init required + var recoded: GLV_SAC[2, L] # zero-init required recoded.nDimMultiScalarRecoding(miniScalars) # 6. Proceed to GLV accelerated scalar multiplication diff --git a/constantine/elliptic/ec_weierstrass_projective.nim b/constantine/elliptic/ec_weierstrass_projective.nim index 9d207444e..d818b8a2a 100644 --- a/constantine/elliptic/ec_weierstrass_projective.nim +++ b/constantine/elliptic/ec_weierstrass_projective.nim @@ -292,3 +292,14 @@ func double*[F](P: var ECP_SWei_Proj[F]) = var tmp {.noInit.}: ECP_SWei_Proj[F] tmp.double(P) P = tmp + +func affineFromProjective*[F](aff: var ECP_SWei_Proj[F], proj: ECP_SWei_Proj) = + # TODO: for now we reuse projective coordinate backend + # TODO: simultaneous inversion + + var invZ {.noInit.}: F + invZ.inv(proj.z) + + aff.z.setOne() + aff.x.prod(proj.x, invZ) + aff.y.prod(proj.y, invZ) diff --git a/constantine/io/io_ec.nim b/constantine/io/io_ec.nim index 35c6edd0a..d487a5eaa 100644 --- a/constantine/io/io_ec.nim +++ b/constantine/io/io_ec.nim @@ -37,12 +37,14 @@ func toHex*(P: ECP_SWei_Proj): string = ## TODO: only normalize and don't display the Z coordinate ## ## This proc output may change format in the future - result = $P.F.C & "(x: " - result &= P.x.tohex(bigEndian) + + var aff {.noInit.}: typeof(P) + aff.affineFromProjective(P) + + result = $aff.F.C & "(x: " + result &= aff.x.tohex(bigEndian) result &= ", y: " - result &= P.y.tohex(bigEndian) - result &= ", z: " - result &= P.z.tohex(bigEndian) + result &= aff.y.tohex(bigEndian) result &= ')' func fromHex*(dst: var ECP_SWei_Proj, x, y: string): bool {.raises: [ValueError].}= diff --git a/sage/bn254_lattice_decomposition.sage b/sage/bn254_lattice_decomposition.sage index 929762006..1478cf485 100644 --- a/sage/bn254_lattice_decomposition.sage +++ b/sage/bn254_lattice_decomposition.sage @@ -96,26 +96,91 @@ def getGLV2_decomp(scalar): return k0, k1 -def scalarMulGLV(): - scalar = randrange(r) # Pick an integer below curve order +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] = 1 + for i in range(0, l-1): # l-2 inclusive + b[0][i] = 2 * ((k[0] >> (i+1)) & 1) - 1 + for j in range(1, m): + for i in range(0, l): + b[j][i] = b[0][i] * (k[j] & 1) + k[j] = (k[j]//2) - (b[j][i] // 2) + + 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 scalarMulGLV(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) print('k0: ' + k0.hex()) print('k1: ' + k1.hex()) - P0 = G1.random_point() 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 == 1 + 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 ---------------') + print('b0 L-1: ' + str(b[0][L-1])) + Q = b[0][L-1] * lut[b[1][L-1] & 1] + for i in range(L-2, -1, -1): + Q *= 2 + Q += b[0][i] * lut[b[1][i] & 1] + + if even: + Q += P0 + + print('final Q: ' + pointToString(Q)) + print('expected: ' + pointToString(expected)) + assert Q == expected # TODO debug + # Test generator set_random_seed(1337) for i in range(1): print('---------------------------------------') - scalarMulGLV() + # scalar = randrange(r) # Pick an integer below curve order + # P = G1.random_point() + scalar = Integer('0x0e08a292f940cfb361cc82bc24ca564f51453708c9745a9cf8707b11c84bc448') + P = G1([ + Integer('0x22d3af0f3ee310df7fc1a2a204369ac13eb4a48d969a27fcd2861506b2dc0cd7'), + Integer('0x1c994169687886ccd28dd587c29c307fb3cab55d796d73a5be0bbf9aab69912e'), + Integer(1) + ]) + scalarMulGLV(scalar, P) diff --git a/tests/test_ec_bn254.nim b/tests/test_ec_bn254.nim index e2743b3f2..cbadb7bec 100644 --- a/tests/test_ec_bn254.nim +++ b/tests/test_ec_bn254.nim @@ -47,11 +47,6 @@ proc test( reference.unsafe_ECmul_double_add(exponentCanonical) endo.scalarMulGLV_BN254(exponent) - echo "Q: ", Q.toHex() - echo "reference: ", reference.toHex() - echo "impl: ", impl.toHex() - echo "endo: ", endo.toHex() - doAssert: bool(Q == reference) doAssert: bool(Q == impl) doAssert: bool(Q == endo) @@ -68,92 +63,92 @@ suite "Scalar Multiplication: BN254 implementation (and unsafe reference impl) v Qy = "2fa00719ce37465dbe7037f723ed5df08c76b9a27a4dd80d86c0ee5157349b96" ) - # test( - # id = 2, - # EC = ECP_SWei_Proj[Fp[BN254_Snarks]], - # Px = "2724750abe620fce759b6f18729e40f891a514160d477811a44b222372cc4ea3", - # Py = "105cdcbe363921790a56bf2696e73642447c60b814827ca4dba86c814912c98a", - # scalar = "2f5c2960850eabadab1e5595ff0bf841206885653e7f2024248b281a86744790", - # Qx = "57d2dcbc665fb93fd5119bb982c29700d025423d60a42b5fe17210fd5a868fd", - # Qy = "2abad564ff78fbc266dfb77bdd110b22271136b33ce5049fb3ca05107787abc" - # ) - - # test( - # id = 3, - # EC = ECP_SWei_Proj[Fp[BN254_Snarks]], - # Px = "39bc19c41835082f86ca046b71875b051575072e4d6a4aeedac31eee34b07df", - # Py = "1fdbf42fc20421e1e775fd93ed1888d614f7e39067e7443f21b6a4817481c346", - # scalar = "29e140c33f706c0111443699b0b8396d8ead339a3d6f3c212b08749cf2a16f6b", - # Qx = "83895d1c7a2b15a5dfe9371983196591415182978e8ff0e83262e32d768c712", - # Qy = "2ed8b88e1cd08814ce1d1929d0e4bba6fb5897f915b3525cf12349256da95499" - # ) - - # test( - # id = 4, - # EC = ECP_SWei_Proj[Fp[BN254_Snarks]], - # Px = "157a3e1ff9dabccced9746e19855a9438098be6d734f07d1c069aa1bd05b8d87", - # Py = "1c96bf3e48bc1a6635d93d4f1302a0eba39bd907c5d861f2a9d0c714ee60f04d", - # scalar = "29b05bd55963e262e0fa458c76297fb5be3ec1421fdb1354789f68fdce81dc2c", - # Qx = "196aeca74447934eeaba0f2263177fcb7eb239985814f8ef2d7bf08677108c9", - # Qy = "1f5aa4c7df4a9855113c63d8fd55c512c7e919b8ae0352e280bdb1009299c3b2" - # ) - - # test( - # id = 5, - # EC = ECP_SWei_Proj[Fp[BN254_Snarks]], - # Px = "2f260967d4cd5d15f98c0a0a9d5abaae0c70d3b8d83e1e884586cd6ece395fe7", - # Py = "2a102c7aebdfaa999d5a99984148ada142f72f5d4158c10368a2e13dded886f6", - # scalar = "1796de74c1edac90d102e7c33f3fad94304eaff4a67a018cae678774d377f6cd", - # Qx = "28c73e276807863ecf4ae60b1353790f10f176ca8c55b3db774e33c569ef39d5", - # Qy = "c386e24828cead255ec7657698559b23a26fc9bd5db70a1fe20b48ecfbd6db9" - # ) - - # test( - # id = 6, - # EC = ECP_SWei_Proj[Fp[BN254_Snarks]], - # Px = "1b4ccef57f4411360a02b8228e4251896c9492ff93a69ba3720da0cd46a04e83", - # Py = "1fabcb215bd7c06ead2e6b0167497efc2cdd3dbacf69bcb0244142fd63c1e405", - # scalar = "116741cd19dac61c5e77877fc6fef40f363b164b501dfbdbc09e17ea51d6beb0", - # Qx = "192ca2e120b0f5296baf7cc47bfebbbc74748c8847bbdbe485bcb796de2622aa", - # Qy = "8bc6b1aa4532c727be8fd21a8176d55bc721c727af327f601f7a8dff655b0b9" - # ) - - # test( - # id = 7, - # EC = ECP_SWei_Proj[Fp[BN254_Snarks]], - # Px = "2807c88d6759280d6bd83a54d349a533d1a66dc32f72cab8114ab707f10e829b", - # Py = "dbf0d486aeed3d303880f324faa2605aa0219e35661bc88150470c7df1c0b61", - # scalar = "2a5976268563870739ced3e6efd8cf53887e8e4426803377095708509dd156ca", - # Qx = "2841f67de361436f64e582a134fe36ab7196334c758a07e732e1cf1ccb35a476", - # Qy = "21fb9b8311e53832044be5ff024f737aee474bc504c7c158fe760cc999da8612" - # ) - - # test( - # id = 8, - # EC = ECP_SWei_Proj[Fp[BN254_Snarks]], - # Px = "2754a174a33a55f2a31573767e9bf5381b47dca1cbebc8b68dd4df58b3f1cc2", - # Py = "f222f59c8893ad87c581dacb3f8b6e7c20e7a13bc5fb6e24262a3436d663b1", - # scalar = "25d596bf6caf4565fbfd22d81f9cef40c8f89b1e5939f20caa1b28056e0e4f58", - # Qx = "2b48dd3ace8e403c2905f00cdf13814f0dbecb0c0465e6455fe390cc9730f5a", - # Qy = "fe65f0cd4ae0d2e459daa4163f32deed1250b5c384eb5aeb933162a41793d25" - # ) - - # test( - # id = 9, - # EC = ECP_SWei_Proj[Fp[BN254_Snarks]], - # Px = "273bf6c679d8e880034590d16c007bbabc6c65ed870a263b5d1ce7375c18fd7", - # Py = "2904086cb9e33657999229b082558a74c19b2b619a0499afb2e21d804d8598ee", - # scalar = "67a499a389129f3902ba6140660c431a56811b53de01d043e924711bd341e53", - # Qx = "1d827e4569f17f068457ffc52f1c6ed7e2ec89b8b520efae48eff41827f79128", - # Qy = "be8c488bb9587bcb0faba916277974afe12511e54fbd749e27d3d7efd998713" - # ) - - # test( - # id = 10, - # EC = ECP_SWei_Proj[Fp[BN254_Snarks]], - # Px = "ec892c09a5f1c68c1bfec7780a1ebd279739383f2698eeefbba745b3e717fd5", - # Py = "23d273a1b9750fe1d4ebd4b7c25f4a8d7d94f6662c436305cca8ff2cdbd3f736", - # scalar = "d2f09ceaa2638b7ac3d7d4aa9eff7a12e93dc85db0f9676e5f19fb86d6273e9", - # Qx = "305d7692b141962a4a92038adfacc0d2691e5589ed097a1c661cc48c84e2b64e", - # Qy = "bafa230a0f5cc2fa3cf07fa46312cb724fc944b097890fa60f2cf42a1be7963" - # ) + test( + id = 2, + EC = ECP_SWei_Proj[Fp[BN254_Snarks]], + Px = "2724750abe620fce759b6f18729e40f891a514160d477811a44b222372cc4ea3", + Py = "105cdcbe363921790a56bf2696e73642447c60b814827ca4dba86c814912c98a", + scalar = "2f5c2960850eabadab1e5595ff0bf841206885653e7f2024248b281a86744790", + Qx = "57d2dcbc665fb93fd5119bb982c29700d025423d60a42b5fe17210fd5a868fd", + Qy = "2abad564ff78fbc266dfb77bdd110b22271136b33ce5049fb3ca05107787abc" + ) + + test( + id = 3, + EC = ECP_SWei_Proj[Fp[BN254_Snarks]], + Px = "39bc19c41835082f86ca046b71875b051575072e4d6a4aeedac31eee34b07df", + Py = "1fdbf42fc20421e1e775fd93ed1888d614f7e39067e7443f21b6a4817481c346", + scalar = "29e140c33f706c0111443699b0b8396d8ead339a3d6f3c212b08749cf2a16f6b", + Qx = "83895d1c7a2b15a5dfe9371983196591415182978e8ff0e83262e32d768c712", + Qy = "2ed8b88e1cd08814ce1d1929d0e4bba6fb5897f915b3525cf12349256da95499" + ) + + test( + id = 4, + EC = ECP_SWei_Proj[Fp[BN254_Snarks]], + Px = "157a3e1ff9dabccced9746e19855a9438098be6d734f07d1c069aa1bd05b8d87", + Py = "1c96bf3e48bc1a6635d93d4f1302a0eba39bd907c5d861f2a9d0c714ee60f04d", + scalar = "29b05bd55963e262e0fa458c76297fb5be3ec1421fdb1354789f68fdce81dc2c", + Qx = "196aeca74447934eeaba0f2263177fcb7eb239985814f8ef2d7bf08677108c9", + Qy = "1f5aa4c7df4a9855113c63d8fd55c512c7e919b8ae0352e280bdb1009299c3b2" + ) + + test( + id = 5, + EC = ECP_SWei_Proj[Fp[BN254_Snarks]], + Px = "2f260967d4cd5d15f98c0a0a9d5abaae0c70d3b8d83e1e884586cd6ece395fe7", + Py = "2a102c7aebdfaa999d5a99984148ada142f72f5d4158c10368a2e13dded886f6", + scalar = "1796de74c1edac90d102e7c33f3fad94304eaff4a67a018cae678774d377f6cd", + Qx = "28c73e276807863ecf4ae60b1353790f10f176ca8c55b3db774e33c569ef39d5", + Qy = "c386e24828cead255ec7657698559b23a26fc9bd5db70a1fe20b48ecfbd6db9" + ) + + test( + id = 6, + EC = ECP_SWei_Proj[Fp[BN254_Snarks]], + Px = "1b4ccef57f4411360a02b8228e4251896c9492ff93a69ba3720da0cd46a04e83", + Py = "1fabcb215bd7c06ead2e6b0167497efc2cdd3dbacf69bcb0244142fd63c1e405", + scalar = "116741cd19dac61c5e77877fc6fef40f363b164b501dfbdbc09e17ea51d6beb0", + Qx = "192ca2e120b0f5296baf7cc47bfebbbc74748c8847bbdbe485bcb796de2622aa", + Qy = "8bc6b1aa4532c727be8fd21a8176d55bc721c727af327f601f7a8dff655b0b9" + ) + + test( + id = 7, + EC = ECP_SWei_Proj[Fp[BN254_Snarks]], + Px = "2807c88d6759280d6bd83a54d349a533d1a66dc32f72cab8114ab707f10e829b", + Py = "dbf0d486aeed3d303880f324faa2605aa0219e35661bc88150470c7df1c0b61", + scalar = "2a5976268563870739ced3e6efd8cf53887e8e4426803377095708509dd156ca", + Qx = "2841f67de361436f64e582a134fe36ab7196334c758a07e732e1cf1ccb35a476", + Qy = "21fb9b8311e53832044be5ff024f737aee474bc504c7c158fe760cc999da8612" + ) + + test( + id = 8, + EC = ECP_SWei_Proj[Fp[BN254_Snarks]], + Px = "2754a174a33a55f2a31573767e9bf5381b47dca1cbebc8b68dd4df58b3f1cc2", + Py = "f222f59c8893ad87c581dacb3f8b6e7c20e7a13bc5fb6e24262a3436d663b1", + scalar = "25d596bf6caf4565fbfd22d81f9cef40c8f89b1e5939f20caa1b28056e0e4f58", + Qx = "2b48dd3ace8e403c2905f00cdf13814f0dbecb0c0465e6455fe390cc9730f5a", + Qy = "fe65f0cd4ae0d2e459daa4163f32deed1250b5c384eb5aeb933162a41793d25" + ) + + test( + id = 9, + EC = ECP_SWei_Proj[Fp[BN254_Snarks]], + Px = "273bf6c679d8e880034590d16c007bbabc6c65ed870a263b5d1ce7375c18fd7", + Py = "2904086cb9e33657999229b082558a74c19b2b619a0499afb2e21d804d8598ee", + scalar = "67a499a389129f3902ba6140660c431a56811b53de01d043e924711bd341e53", + Qx = "1d827e4569f17f068457ffc52f1c6ed7e2ec89b8b520efae48eff41827f79128", + Qy = "be8c488bb9587bcb0faba916277974afe12511e54fbd749e27d3d7efd998713" + ) + + test( + id = 10, + EC = ECP_SWei_Proj[Fp[BN254_Snarks]], + Px = "ec892c09a5f1c68c1bfec7780a1ebd279739383f2698eeefbba745b3e717fd5", + Py = "23d273a1b9750fe1d4ebd4b7c25f4a8d7d94f6662c436305cca8ff2cdbd3f736", + scalar = "d2f09ceaa2638b7ac3d7d4aa9eff7a12e93dc85db0f9676e5f19fb86d6273e9", + Qx = "305d7692b141962a4a92038adfacc0d2691e5589ed097a1c661cc48c84e2b64e", + Qy = "bafa230a0f5cc2fa3cf07fa46312cb724fc944b097890fa60f2cf42a1be7963" + ) From e9e84abf09af3f3cd0dbd84df8ee49c0a32860e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mamy=20Andr=C3=A9-Ratsimbazafy?= Date: Sun, 14 Jun 2020 14:00:05 +0200 Subject: [PATCH 18/19] Implement endomorphism acceleration for BLS12-381 (needed cofactor clearing of the point) --- ...ch_ec_swei_proj_g1.nim => bench_ec_g1.nim} | 8 +- benchmarks/bench_elliptic_template.nim | 53 +++-- constantine.nimble | 14 +- constantine/config/curves_declaration.nim | 2 +- .../elliptic/ec_endomorphism_accel.nim | 105 +++------ .../elliptic/ec_endomorphism_params.nim | 135 ++++++++++++ constantine/elliptic/ec_scalar_mul.nim | 5 +- sage/curve_family_bls12.sage | 8 +- sage/lattice_decomposition_bls12_381_g1.sage | 202 ++++++++++++++++++ ...attice_decomposition_bn254_snarks_g1.sage} | 9 +- sage/testgen_bls12_381.sage | 6 +- tests/test_ec_bls12_381.nim | 89 ++++---- tests/test_ec_bn254.nim | 2 +- 13 files changed, 478 insertions(+), 160 deletions(-) rename benchmarks/{bench_ec_swei_proj_g1.nim => bench_ec_g1.nim} (90%) create mode 100644 constantine/elliptic/ec_endomorphism_params.nim create mode 100644 sage/lattice_decomposition_bls12_381_g1.sage rename sage/{bn254_lattice_decomposition.sage => lattice_decomposition_bn254_snarks_g1.sage} (96%) diff --git a/benchmarks/bench_ec_swei_proj_g1.nim b/benchmarks/bench_ec_g1.nim similarity index 90% rename from benchmarks/bench_ec_swei_proj_g1.nim rename to benchmarks/bench_ec_g1.nim index e079ba238..175e22655 100644 --- a/benchmarks/bench_ec_swei_proj_g1.nim +++ b/benchmarks/bench_ec_g1.nim @@ -10,7 +10,7 @@ import # Internals ../constantine/config/curves, ../constantine/arithmetic, - ../constantine/elliptic/[ec_weierstrass_projective, ec_scalar_mul], + ../constantine/elliptic/ec_weierstrass_projective, # Helpers ../helpers/static_for, ./bench_elliptic_template, @@ -51,14 +51,16 @@ proc main() = separator() doublingBench(ECP_SWei_Proj[Fp[curve]], Iters) separator() + scalarMulUnsafeDoubleAddBench(ECP_SWei_Proj[Fp[curve]], MulIters) + separator() scalarMulGenericBench(ECP_SWei_Proj[Fp[curve]], scratchSpaceSize = 1 shl 2, MulIters) separator() scalarMulGenericBench(ECP_SWei_Proj[Fp[curve]], scratchSpaceSize = 1 shl 3, MulIters) separator() scalarMulGenericBench(ECP_SWei_Proj[Fp[curve]], scratchSpaceSize = 1 shl 4, MulIters) separator() - # scalarMulUnsafeDoubleAddBench(ECP_SWei_Proj[Fp[curve]], MulIters) - # separator() + scalarMulGLV(ECP_SWei_Proj[Fp[curve]], MulIters) + separator() separator() main() diff --git a/benchmarks/bench_elliptic_template.nim b/benchmarks/bench_elliptic_template.nim index b372805a4..a654b5bbb 100644 --- a/benchmarks/bench_elliptic_template.nim +++ b/benchmarks/bench_elliptic_template.nim @@ -17,11 +17,14 @@ import ../constantine/config/curves, ../constantine/arithmetic, ../constantine/io/io_bigints, + ../constantine/elliptic/[ec_weierstrass_projective, ec_scalar_mul, ec_endomorphism_accel], # Helpers ../helpers/[prng_unsafe, static_for], ./platforms, # Standard library - std/[monotimes, times, strformat, strutils, macros] + std/[monotimes, times, strformat, strutils, macros], + # Reference unsafe scalar multiplication + ../tests/support/ec_reference_scalar_mult var rng: RngState let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32 @@ -71,15 +74,15 @@ when SupportsGetTicks: echo "\n=================================================================================================================\n" proc separator*() = - echo "-".repeat(157) + echo "-".repeat(177) proc report(op, elliptic: string, start, stop: MonoTime, startClk, stopClk: int64, iters: int) = let ns = inNanoseconds((stop-start) div iters) let throughput = 1e9 / float64(ns) when SupportsGetTicks: - echo &"{op:<40} {elliptic:<40} {throughput:>15.3f} ops/s {ns:>9} ns/op {(stopClk - startClk) div iters:>9} CPU cycles (approx)" + echo &"{op:<60} {elliptic:<40} {throughput:>15.3f} ops/s {ns:>9} ns/op {(stopClk - startClk) div iters:>9} CPU cycles (approx)" else: - echo &"{op:<40} {elliptic:<40} {throughput:>15.3f} ops/s {ns:>9} ns/op" + echo &"{op:<60} {elliptic:<40} {throughput:>15.3f} ops/s {ns:>9} ns/op" macro fixEllipticDisplay(T: typedesc): untyped = # At compile-time, enums are integers and their display is buggy @@ -124,7 +127,7 @@ proc scalarMulGenericBench*(T: typedesc, scratchSpaceSize: static int, iters: in const bits = T.F.C.getCurveOrderBitwidth() var r {.noInit.}: T - let P = rng.random_unsafe(T) + let P = rng.random_unsafe(T) # TODO: clear cofactor let exponent = rng.random_unsafe(BigInt[bits]) var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte] @@ -136,18 +139,28 @@ proc scalarMulGenericBench*(T: typedesc, scratchSpaceSize: static int, iters: in r = P r.scalarMulGeneric(exponentCanonical, scratchSpace) -# import ../tests/support/ec_reference_scalar_mult -# -# proc scalarMulUnsafeDoubleAddBench*(T: typedesc, iters: int) = -# const bits = T.F.C.getCurveOrderBitwidth() -# -# var r {.noInit.}: T -# let P = rng.random_unsafe(T) -# -# let exponent = rng.random_unsafe(BigInt[bits]) -# var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte] -# exponentCanonical.exportRawUint(exponent, bigEndian) -# -# bench("EC ScalarMul G1 (unsafe DoubleAdd)", T, iters): -# r = P -# r.unsafe_ECmul_double_add(exponentCanonical) +proc scalarMulGLV*(T: typedesc, iters: int) = + const bits = T.F.C.getCurveOrderBitwidth() + + var r {.noInit.}: T + let P = rng.random_unsafe(T) # TODO: clear cofactor + + let exponent = rng.random_unsafe(BigInt[bits]) + + bench("EC ScalarMul G1 (GLV endomorphism accelerated)", T, iters): + r = P + r.scalarMulGLV(exponent) + +proc scalarMulUnsafeDoubleAddBench*(T: typedesc, iters: int) = + const bits = T.F.C.getCurveOrderBitwidth() + + var r {.noInit.}: T + let P = rng.random_unsafe(T) # TODO: clear cofactor + + let exponent = rng.random_unsafe(BigInt[bits]) + var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte] + exponentCanonical.exportRawUint(exponent, bigEndian) + + bench("EC ScalarMul G1 (unsafe reference DoubleAdd)", T, iters): + r = P + r.unsafe_ECmul_double_add(exponentCanonical) diff --git a/constantine.nimble b/constantine.nimble index 2fc633987..94d787adc 100644 --- a/constantine.nimble +++ b/constantine.nimble @@ -124,7 +124,7 @@ task test, "Run all tests": runBench("bench_fp2") runBench("bench_fp6") runBench("bench_fp12") - runBench("bench_ec_swei_proj_g1") + runBench("bench_ec_g1") task test_no_gmp, "Run tests that don't require GMP": # -d:testingCurves is configured in a *.nim.cfg for convenience @@ -320,11 +320,11 @@ task bench_fp12_gcc, "Run benchmark 𝔽p12 with gcc": task bench_fp12_clang, "Run benchmark 𝔽p12 with clang": runBench("bench_fp12", "clang") -task bench_ec_swei_proj_g1, "Run benchmark on Elliptic Curve group 𝔾1 - Short Weierstrass with Projective Coordinates - GCC": - runBench("bench_ec_swei_proj_g1") +task bench_ec_g1, "Run benchmark on Elliptic Curve group 𝔾1 - Short Weierstrass with Projective Coordinates - GCC": + runBench("bench_ec_g1") -task bench_ec_swei_proj_g1_gcc, "Run benchmark on Elliptic Curve group 𝔾1 - Short Weierstrass with Projective Coordinates - GCC": - runBench("bench_ec_swei_proj_g1", "gcc") +task bench_ec_gcc, "Run benchmark on Elliptic Curve group 𝔾1 - Short Weierstrass with Projective Coordinates - GCC": + runBench("bench_ec_g1", "gcc") -task bench_ec_swei_proj_g1_clang, "Run benchmark on Elliptic Curve group 𝔾1 - Short Weierstrass with Projective Coordinates - Clang": - runBench("bench_ec_swei_proj_g1", "clang") +task bench_ec_g1_clang, "Run benchmark on Elliptic Curve group 𝔾1 - Short Weierstrass with Projective Coordinates - Clang": + runBench("bench_ec_g1", "clang") diff --git a/constantine/config/curves_declaration.nim b/constantine/config/curves_declaration.nim index 62309962d..3efdfc7c9 100644 --- a/constantine/config/curves_declaration.nim +++ b/constantine/config/curves_declaration.nim @@ -144,7 +144,7 @@ declareCurves: modulus: "0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" family: BarretoLynnScott # u: -(2^63 + 2^62 + 2^60 + 2^57 + 2^48 + 2^16) - cubicRootOfUnity_mod_p: "0x5f19672fdf76ce51ba69c6076a0f77eaddb3a93be6f89688de17d813620a00022e01fffffffefffe" + cubicRootOfUnity_mod_p: "0x1a0111ea397fe699ec02408663d4de85aa0d857d89759ad4897d29650fb85f9b409427eb4f49fffd8bfd00000000aaac" # G1 Equation: y² = x³ + 4 # G2 Equation: y² = x³ + 4 (1+i) diff --git a/constantine/elliptic/ec_endomorphism_accel.nim b/constantine/elliptic/ec_endomorphism_accel.nim index 164244388..eece9040b 100644 --- a/constantine/elliptic/ec_endomorphism_accel.nim +++ b/constantine/elliptic/ec_endomorphism_accel.nim @@ -17,7 +17,8 @@ import ../io/io_bigints, ../towers, ./ec_weierstrass_affine, - ./ec_weierstrass_projective + ./ec_weierstrass_projective, + ./ec_endomorphism_params # ############################################################ # @@ -71,9 +72,6 @@ type ## ## Digit-Endianness is bigEndian - MultiScalar[M, LengthInBits: static int] = array[M, BigInt[LengthInBits]] - ## Decomposition of a secret scalar in multiple scalars - const BitSize = 2 Shift = 2 # log2(4) - we can store 4 digit per byte @@ -149,9 +147,9 @@ proc `[]=`(recoding: var Recoded, slot[] = slot[] or shifted -func nDimMultiScalarRecoding[M, LengthInBits, LengthInDigits: static int]( - dst: var GLV_SAC[M, LengthInDigits], - src: MultiScalar[M, LengthInBits] +func nDimMultiScalarRecoding[M, L: static int]( + dst: var GLV_SAC[M, L], + src: MultiScalar[M, L] ) = ## This recodes N scalar for GLV multi-scalar multiplication ## with side-channel resistance. @@ -203,19 +201,17 @@ func nDimMultiScalarRecoding[M, LengthInBits, LengthInDigits: static int]( # For that floored division, bji may be negative!!! # In particular floored division of -1 is -1 not 0. # This means that arithmetic right shift must be used instead of logical right shift - static: doAssert LengthInDigits == LengthInBits+1, - "Length in digits: " & $LengthInDigits & " Length in bits: " & $LengthInBits - # " # VScode broken highlight + # assert src[0].isOdd - Only happen on implementation error, we don't want to leak a single bit var k = src # Keep the source multiscalar in registers template b: untyped {.dirty.} = dst - b[0][LengthInDigits-1] = 1 - for i in 0 .. LengthInDigits-2: + b[0][L-1] = 1 + for i in 0 .. L-2: b[0][i] = 2 * k[0].bit(i+1).int8 - 1 for j in 1 .. M-1: - for i in 0 .. LengthInDigits-1: + for i in 0 .. L-1: let bji = b[0][i] * k[j].bit0.int8 b[j][i] = bji # In the following equation @@ -276,61 +272,6 @@ func buildLookupTable[M: static int, F]( lut[u].sum(lut[u.clearBit(msb)], endomorphisms[msb]) # } # highlight bug, ... -# Chapter 6.3.1 - Guide to Pairing-based Cryptography -const Lattice_BN254_Snarks_G1: array[2, array[2, tuple[b: BigInt[127], isNeg: bool]]] = [ - # Curve of order 254 -> mini scalars of size 127 - # u = 0x44E992B44A6909F1 - [(BigInt[127].fromHex"0x89d3256894d213e3", false), # 2u + 1 - (BigInt[127].fromHex"0x6f4d8248eeb859fd0be4e1541221250b", false)], # 6u² + 4u + 1 - [(BigInt[127].fromHex"0x6f4d8248eeb859fc8211bbeb7d4f1128", false), # 6u² + 2u - (BigInt[127].fromHex"0x89d3256894d213e3", true)] # -2u - 1 -] - -const Babai_BN254_Snarks_G1 = [ - # Vector for Babai rounding - BigInt[127].fromHex"0x89d3256894d213e3", # 2u + 1 - BigInt[127].fromHex"0x6f4d8248eeb859fd0be4e1541221250b" # 6u² + 4u + 1 -] - -func decomposeScalar_BN254_Snarks_G1[M, scalBits, miniBits: static int]( - scalar: BigInt[scalBits], - miniScalars: var MultiScalar[M, miniBits] - ) = - ## Decompose a secret scalar into mini-scalar exploiting - ## BN254_Snarks specificities. - ## - ## TODO: Generalize to all BN curves - ## - needs a Lattice type - ## - needs to better support negative bigints, (extra bit for sign?) - - static: doAssert miniBits == (scalBits + M - 1) div M - # 𝛼0 = (0x2d91d232ec7e0b3d7 * s) >> 256 - # 𝛼1 = (0x24ccef014a773d2d25398fd0300ff6565 * s) >> 256 - const - w = BN254_Snarks.getCurveOrderBitwidth().wordsRequired() - alphaHats = (BigInt[66].fromHex"0x2d91d232ec7e0b3d7", - BigInt[130].fromHex"0x24ccef014a773d2d25398fd0300ff6565") - - var alphas{.noInit.}: array[M, BigInt[scalBits]] # TODO size 66+254 and 130+254 - - staticFor i, 0, M: - alphas[i].prod_high_words(alphaHats[i], scalar, w) - - # We have k0 = s - 𝛼0 b00 - 𝛼1 b10 - # and kj = 0 - 𝛼j b0j - 𝛼1 b1j - var k: array[M, BigInt[scalBits]] - k[0] = scalar - for miniScalarIdx in 0 ..< M: - for basisIdx in 0 ..< M: - var alphaB {.noInit.}: BigInt[scalBits] - alphaB.prod(alphas[basisIdx], Lattice_BN254_Snarks_G1[basisIdx][miniScalarIdx].b) # TODO small lattice size - if Lattice_BN254_Snarks_G1[basisIdx][miniScalarIdx].isNeg: - k[miniScalarIdx] += alphaB - else: - k[miniScalarIdx] -= alphaB - - miniScalars[miniScalarIdx].copyTruncatedFrom(k[miniScalarIdx]) - func tableIndex(glv: GLV_SAC, bit: int): SecretWord = ## Compose the secret table index from ## the GLV-SAC representation and the "bit" accessed @@ -353,9 +294,9 @@ func secretLookup[T](dst: var T, table: openArray[T], index: SecretWord) = let selector = SecretWord(i) == index dst.ccopy(table[i], selector) -func scalarMulGLV_BN254*( +func scalarMulGLV*[scalBits]( P: var ECP_SWei_Proj, - scalar: BigInt[BN254_Snarks.getCurveOrderBitwidth()] + scalar: BigInt[scalBits] ) = ## Elliptic Curve Scalar Multiplication ## @@ -363,19 +304,29 @@ func scalarMulGLV_BN254*( ## ## This is a scalar multiplication accelerated by an endomorphism ## via the GLV (Gallant-lambert-Vanstone) decomposition. - const M = 2 + const C = P.F.C # curve + static: doAssert: scalBits == C.getCurveOrderBitwidth() + when P.F is Fp: + const M = 2 # 1. Compute endomorphisms var endomorphisms: array[M-1, typeof(P)] # TODO: zero-init not required endomorphisms[0] = P - endomorphisms[0].x *= BN254_Snarks.getCubicRootOfUnity_mod_p() + endomorphisms[0].x *= C.getCubicRootOfUnity_mod_p() # 2. Decompose scalar into mini-scalars - const L = (BN254_Snarks.getCurveOrderBitwidth() + M - 1) div M + 1 - var miniScalars: array[M, BigInt[L-1]] # TODO: zero-init not required - scalar.decomposeScalar_BN254_Snarks_G1( - miniScalars - ) + const L = (C.getCurveOrderBitwidth() + M - 1) div M + 1 + var miniScalars: array[M, BigInt[L]] # TODO: zero-init not required + when C == BN254_Snarks: + scalar.decomposeScalar_BN254_Snarks_G1( + miniScalars + ) + elif C == BLS12_381: + scalar.decomposeScalar_BLS12_381_G1( + miniScalars + ) + else: + {.error: "Unsupported curve for GLV acceleration".} # 3. TODO: handle negative mini-scalars # Either negate the associated base and the scalar (in the `endomorphisms` array) diff --git a/constantine/elliptic/ec_endomorphism_params.nim b/constantine/elliptic/ec_endomorphism_params.nim new file mode 100644 index 000000000..1f00a75ba --- /dev/null +++ b/constantine/elliptic/ec_endomorphism_params.nim @@ -0,0 +1,135 @@ +# 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 + ../../helpers/static_for, + ../primitives, + ../config/[common, curves, type_bigint], + ../arithmetic, + ../io/io_bigints, + ../towers, + ./ec_weierstrass_projective + +# Parameters for GLV endomorphisms acceleration +# ---------------------------------------------------------------------------------------- +# TODO: cleanup, those should be derived in the config folder +# and stored in a constant + +type + MultiScalar*[M, LengthInBits: static int] = array[M, BigInt[LengthInBits]] + ## Decomposition of a secret scalar in multiple scalars + +# Chapter 6.3.1 - Guide to Pairing-based Cryptography +const Lattice_BN254_Snarks_G1: array[2, array[2, tuple[b: BigInt[127], isNeg: bool]]] = [ + # Curve of order 254 -> mini scalars of size 127 + # u = 0x44E992B44A6909F1 + [(BigInt[127].fromHex"0x89d3256894d213e3", false), # 2u + 1 + (BigInt[127].fromHex"0x6f4d8248eeb859fd0be4e1541221250b", false)], # 6u² + 4u + 1 + [(BigInt[127].fromHex"0x6f4d8248eeb859fc8211bbeb7d4f1128", false), # 6u² + 2u + (BigInt[127].fromHex"0x89d3256894d213e3", true)] # -2u - 1 +] + +const Babai_BN254_Snarks_G1 = [ + # Vector for Babai rounding + BigInt[127].fromHex"0x89d3256894d213e3", # 2u + 1 + BigInt[127].fromHex"0x6f4d8248eeb859fd0be4e1541221250b" # 6u² + 4u + 1 +] + +func decomposeScalar_BN254_Snarks_G1*[M, scalBits, L: static int]( + scalar: BigInt[scalBits], + miniScalars: var MultiScalar[M, L] + ) = + ## Decompose a secret scalar into mini-scalar exploiting + ## BN254_Snarks specificities. + ## + ## TODO: Generalize to all BN curves + ## - needs a Lattice type + ## - needs to better support negative bigints, (extra bit for sign?) + + static: doAssert L == (scalBits + M - 1) div M + 1 + # 𝛼0 = (0x2d91d232ec7e0b3d7 * s) >> 256 + # 𝛼1 = (0x24ccef014a773d2d25398fd0300ff6565 * s) >> 256 + const + w = BN254_Snarks.getCurveOrderBitwidth().wordsRequired() + alphaHats = (BigInt[66].fromHex"0x2d91d232ec7e0b3d7", + BigInt[130].fromHex"0x24ccef014a773d2d25398fd0300ff6565") + + var alphas{.noInit.}: array[M, BigInt[scalBits]] # TODO size 66+254 and 130+254 + + staticFor i, 0, M: + alphas[i].prod_high_words(alphaHats[i], scalar, w) + + # We have k0 = s - 𝛼0 b00 - 𝛼1 b10 + # and kj = 0 - 𝛼j b0j - 𝛼1 b1j + var k: array[M, BigInt[scalBits]] + k[0] = scalar + for miniScalarIdx in 0 ..< M: + for basisIdx in 0 ..< M: + var alphaB {.noInit.}: BigInt[scalBits] + alphaB.prod(alphas[basisIdx], Lattice_BN254_Snarks_G1[basisIdx][miniScalarIdx].b) # TODO small lattice size + if Lattice_BN254_Snarks_G1[basisIdx][miniScalarIdx].isNeg: + k[miniScalarIdx] += alphaB + else: + k[miniScalarIdx] -= alphaB + + miniScalars[miniScalarIdx].copyTruncatedFrom(k[miniScalarIdx]) + +const Lattice_BLS12_381_G1: array[2, array[2, tuple[b: BigInt[128], isNeg: bool]]] = [ + # Curve of order 254 -> mini scalars of size 127 + # u = 0x44E992B44A6909F1 + [(BigInt[128].fromHex"0xac45a4010001a40200000000ffffffff", false), # u² - 1 + (BigInt[128].fromHex"0x1", true)], # -1 + [(BigInt[128].fromHex"0x1", false), # 1 + (BigInt[128].fromHex"0xac45a4010001a4020000000100000000", false)] # u² +] + +const Babai_BLS12_381_G1 = [ + # Vector for Babai rounding + BigInt[128].fromHex"0xac45a4010001a4020000000100000000", + BigInt[128].fromHex"0x1" +] + +func decomposeScalar_BLS12_381_G1*[M, scalBits, L: static int]( + scalar: BigInt[scalBits], + miniScalars: var MultiScalar[M, L] + ) = + ## Decompose a secret scalar into mini-scalar exploiting + ## BLS12_381 specificities. + ## + ## TODO: Generalize to all BLS curves + ## - needs a Lattice type + ## - needs to better support negative bigints, (extra bit for sign?) + + static: doAssert L == (scalBits + M - 1) div M + 1 + # 𝛼0 = (0x2d91d232ec7e0b3d7 * s) >> 256 + # 𝛼1 = (0x24ccef014a773d2d25398fd0300ff6565 * s) >> 256 + const + w = BLS12_381.getCurveOrderBitwidth().wordsRequired() + alphaHats = (BigInt[129].fromHex"0x17c6becf1e01faadd63f6e522f6cfee30", + BigInt[2].fromHex"0x2") + + var alphas{.noInit.}: array[M, BigInt[scalBits]] # TODO size 256+255 and 132+255 + + staticFor i, 0, M: + alphas[i].prod_high_words(alphaHats[i], scalar, w) + + # We have k0 = s - 𝛼0 b00 - 𝛼1 b10 + # and kj = 0 - 𝛼j b0j - 𝛼1 b1j + var k: array[M, BigInt[scalBits]] + k[0] = scalar + for miniScalarIdx in 0 ..< M: + for basisIdx in 0 ..< M: + var alphaB {.noInit.}: BigInt[scalBits] + alphaB.prod(alphas[basisIdx], Lattice_BLS12_381_G1[basisIdx][miniScalarIdx].b) # TODO small lattice size + if Lattice_BLS12_381_G1[basisIdx][miniScalarIdx].isNeg: + k[miniScalarIdx] += alphaB + else: + k[miniScalarIdx] -= alphaB + + miniScalars[miniScalarIdx].copyTruncatedFrom(k[miniScalarIdx]) diff --git a/constantine/elliptic/ec_scalar_mul.nim b/constantine/elliptic/ec_scalar_mul.nim index bff92476b..3fc8cfd30 100644 --- a/constantine/elliptic/ec_scalar_mul.nim +++ b/constantine/elliptic/ec_scalar_mul.nim @@ -218,8 +218,9 @@ func scalarMul*( ## P <- [k] P # This calls endomorphism accelerated scalar mul if available # or the generic scalar mul otherwise - when ECP_SWei_Proj.F.C == BN254_Snarks: - scalarMulGLV_BN254(P, scalar) + when ECP_SWei_Proj.F.C in {BN254_Snarks, BLS12_381}: + # ⚠️ This requires the cofactor to be cleared + scalarMulGLV(P, scalar) else: var scratchSpace: array[1 shl 4, ECP_SWei_Proj] diff --git a/sage/curve_family_bls12.sage b/sage/curve_family_bls12.sage index adace597e..bda356685 100644 --- a/sage/curve_family_bls12.sage +++ b/sage/curve_family_bls12.sage @@ -55,8 +55,8 @@ def compute_curve_characteristic(u_str): print(f' Lattice b2: ' + str(['0x' + b.hex() for b in [1, u^2]])) # Babai rounding - ahat1 = 2*u+1 - ahat2 = 6*u^2+4*u+1 + ahat1 = u^2 + ahat2 = 1 # We want a1 = ahat1 * s/r with m = 2 (for a 2-dim decomposition) and r the curve order # To handle rounding errors we instead multiply by # 𝜈 = (2^WordBitWidth)^w (i.e. the same as the R magic constant for Montgomery arithmetic) @@ -70,8 +70,8 @@ def compute_curve_characteristic(u_str): print(f' 𝛼\u03021: ' + '0x' + ahat1.hex()) print(f' 𝛼\u03022: ' + '0x' + ahat2.hex()) print(f' Handle rounding errors') - print(f' 𝛼1 = 𝛼\u03021 * s / r with 𝛼1 = (𝛼\u03021 * 𝜈/r) * s/𝜈') - print(f' 𝛼2 = 𝛼\u03022 * s / r with 𝛼2 = (𝛼\u03022 * 𝜈/r) * s/𝜈') + print(f' 𝛼\u03051 = 𝛼\u03021 * s / r with 𝛼1 = (𝛼\u03021 * 𝜈/r) * s/𝜈') + print(f' 𝛼\u03052 = 𝛼\u03022 * s / r with 𝛼2 = (𝛼\u03022 * 𝜈/r) * s/𝜈') print(f' -----------------------------------------------------') l1 = Integer(ahat1 << v) // r l2 = Integer(ahat2 << v) // r diff --git a/sage/lattice_decomposition_bls12_381_g1.sage b/sage/lattice_decomposition_bls12_381_g1.sage new file mode 100644 index 000000000..3d082c87b --- /dev/null +++ b/sage/lattice_decomposition_bls12_381_g1.sage @@ -0,0 +1,202 @@ +# 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 GLV Endomorphism +# Lattice Decomposition +# +# ############################################################ + +# Parameters +u = -(2^63 + 2^62 + 2^60 + 2^57 + 2^48 + 2^16) +p = (u - 1)^2 * (u^4 - u^2 + 1)//3 + u +r = u^4 - u^2 + 1 +cofactor = Integer('0x396c8c005555e1568c00aaab0000aaab') +print('p : ' + p.hex()) +print('r : ' + r.hex()) + +# Cube root of unity (mod r) formula for any BLS12 curves +lambda1_r = u^2 - 1 +assert lambda1_r^3 % r == 1 +print('λᵩ1 : ' + lambda1_r.hex()) +print('λᵩ1+r: ' + (lambda1_r+r).hex()) + +lambda2_r = u^4 +assert lambda2_r^3 % r == 1 +print('λᵩ2 : ' + lambda2_r.hex()) + +# Finite fields +F = GF(p) +# K2. = PolynomialRing(F) +# F2. = F.extension(u^2+9) +# K6. = PolynomialRing(F2) +# F6. = F2.extension(v^3-beta) +# K12. = PolynomialRing(F6) +# K12. = F6.extension(w^2-eta) + +# Curves +b = 4 +G1 = EllipticCurve(F, [0, b]) +# G2 = EllipticCurve(F2, [0, b*beta]) + +(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 + +# Test generator +set_random_seed(1337) + +# Check +def checkEndo(): + Prand = G1.random_point() + assert Prand != G1([0, 1, 0]) # Infinity + + # Clear cofactor + P = Prand * cofactor + + (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 == Qendo2 + assert Q2 == Qendo2 + + print('Endomorphism OK with 𝜑2') + +checkEndo() + +# Lattice +b = [ + [u^2-1, -1], + [1, u^2] +] +# Babai rounding +ahat = [u^2, 1] +v = int(r).bit_length() +v = int(((v + 64 - 1) // 64) * 64) # round to next multiple of 64 + +l = [Integer(a << v) // r for a in ahat] +print('𝛼\u03051: ' + l[0].hex()) +print('𝛼\u03052: ' + l[1].hex()) + +def getGLV2_decomp(scalar): + + a0 = (l[0] * scalar) >> v + a1 = (l[1] * scalar) >> v + + k0 = scalar - a0 * b[0][0] - a1 * b[1][0] + k1 = 0 - a0 * b[0][1] - a1 * b[1][1] + + assert int(k0).bit_length() <= (int(r).bit_length() + 1) // 2 + assert int(k1).bit_length() <= (int(r).bit_length() + 1) // 2 + + assert scalar == (k0 + k1 * (lambda1_r % r)) % r + assert scalar == (k0 + k1 * (lambda2_r % r)) % r + + return k0, k1 + +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] = 1 + for i in range(0, L-1): # l-2 inclusive + b[0][i] = 2 * ((k[0] >> (i+1)) & 1) - 1 + for j in range(1, m): + for i in range(0, L): + b[j][i] = b[0][i] * (k[j] & 1) + k[j] = (k[j]//2) - (b[j][i] // 2) + + 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 scalarMulGLV(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) + print('k0: ' + k0.hex()) + print('k1: ' + k1.hex()) + + P1 = (lambda1_r % r) * P0 + (Px, Py, Pz) = P0 + P1_endo = G1([Px*phi2 % p, Py, Pz]) + assert P1 == P1_endo + + expected = scalar * P0 + decomp = k0*P0 + k1*P1 + assert expected == decomp + + print('------ recode scalar -----------') + even = k0 & 1 == 1 + 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 ---------------') + print('b0 L-1: ' + str(b[0][L-1])) + Q = b[0][L-1] * lut[b[1][L-1] & 1] + for i in range(L-2, -1, -1): + Q *= 2 + Q += b[0][i] * lut[b[1][i] & 1] + + if even: + Q += P0 + + print('final Q: ' + pointToString(Q)) + print('expected: ' + pointToString(expected)) + assert Q == expected # TODO debug + +# 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() + scalar = Integer('0xf7e60a832eb77ac47374bc93251360d6c81c21add62767ff816caf11a20d8db') + P = G1([ + Integer('0xf9679bb02ee7f352fff6a6467a5e563ec8dd38c86a48abd9e8f7f241f1cdd29d54bc3ddea3a33b62e0d7ce22f3d244a'), + Integer('0x50189b992cf856846b30e52205ff9ef72dc081e9680726586231cbc29a81a162120082585f401e00382d5c86fb1083f'), + Integer(1) + ]) + scalarMulGLV(scalar, P) diff --git a/sage/bn254_lattice_decomposition.sage b/sage/lattice_decomposition_bn254_snarks_g1.sage similarity index 96% rename from sage/bn254_lattice_decomposition.sage rename to sage/lattice_decomposition_bn254_snarks_g1.sage index 1478cf485..ab2fb491c 100644 --- a/sage/bn254_lattice_decomposition.sage +++ b/sage/lattice_decomposition_bn254_snarks_g1.sage @@ -24,7 +24,6 @@ lambda1_r = (-(36*u^3+18*u^2+6*u+2)) assert lambda1_r^3 % r == 1 print('λᵩ1 : ' + lambda1_r.hex()) print('λᵩ1+r: ' + (lambda1_r+r).hex()) -print('λᵩ1+r: ' + (lambda1_r+r).hex()) lambda2_r = (36*u^4-1) assert lambda2_r^3 % r == 1 @@ -48,9 +47,14 @@ G1 = EllipticCurve(F, [0, b]) print('𝜑1 :' + Integer(phi1).hex()) print('𝜑2 :' + Integer(phi2).hex()) +# Test generator +set_random_seed(1337) + # Check def checkEndo(): P = G1.random_point() + 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]) @@ -61,6 +65,9 @@ def checkEndo(): 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 diff --git a/sage/testgen_bls12_381.sage b/sage/testgen_bls12_381.sage index fb8151927..d681f6476 100644 --- a/sage/testgen_bls12_381.sage +++ b/sage/testgen_bls12_381.sage @@ -37,7 +37,11 @@ set_random_seed(1337) for i in range(10): print('---------------------------------------') - P = G1.random_point() + Prand = G1.random_point() + + # Clear cofactor + P = Prand * cofactor + (Px, Py, Pz) = P print('Px: ' + Integer(Px).hex()) print('Py: ' + Integer(Py).hex()) diff --git a/tests/test_ec_bls12_381.nim b/tests/test_ec_bls12_381.nim index f8f51ce8d..ffe874061 100644 --- a/tests/test_ec_bls12_381.nim +++ b/tests/test_ec_bls12_381.nim @@ -13,7 +13,7 @@ import ../constantine/config/[common, curves], ../constantine/arithmetic, ../constantine/io/[io_bigints, io_ec], - ../constantine/elliptic/[ec_weierstrass_projective, ec_scalar_mul], + ../constantine/elliptic/[ec_weierstrass_projective, ec_scalar_mul, ec_endomorphism_accel], # Test utilities ./support/ec_reference_scalar_mult @@ -33,119 +33,122 @@ proc test( var Q: EC let qOK = Q.fromHex(Qx, Qy) - let exponent = EC.F.C.matchingBigInt.fromHex(scalar) + let exponent = BigInt[EC.F.C.getCurveOrderBitwidth()].fromHex(scalar) var exponentCanonical: array[(exponent.bits+7) div 8, byte] exponentCanonical.exportRawUint(exponent, bigEndian) var impl = P reference = P + endo = P scratchSpace: array[1 shl 4, EC] impl.scalarMulGeneric(exponentCanonical, scratchSpace) reference.unsafe_ECmul_double_add(exponentCanonical) + endo.scalarMulGLV(exponent) doAssert: bool(Q == reference) doAssert: bool(Q == impl) + doAssert: bool(Q == endo) -suite "Scalar Multiplication: BLS12_381 implementation (and unsafe reference impl) vs SageMath": +suite "Scalar Multiplication (cofactor cleared): BLS12_381 implementation (and unsafe reference impl) vs SageMath": # Generated via sage sage/testgen_bls12_381.sage test( id = 1, EC = ECP_SWei_Proj[Fp[BLS12_381]], - Px = "f21eda282230f72b855d48055e68ab3825da87831fa5147a64fa071bade4c26bddd45e8b602e62df4d907414a6ec1b4", - Py = "531b38866cb35c19951f4a1ac62242f11fa714a1b99c6116a630fa75e7f4407fcd1ae9770a821c5899a777d341c915a", + Px = "f9679bb02ee7f352fff6a6467a5e563ec8dd38c86a48abd9e8f7f241f1cdd29d54bc3ddea3a33b62e0d7ce22f3d244a", + Py = "50189b992cf856846b30e52205ff9ef72dc081e9680726586231cbc29a81a162120082585f401e00382d5c86fb1083f", scalar = "f7e60a832eb77ac47374bc93251360d6c81c21add62767ff816caf11a20d8db", - Qx = "18d7ca3fb93d7300a0484233f3bac9bca00b45595a4b9caf66aa0b2237f6fd51559a24a634f3876451332c5f754438b2", - Qy = "edbb203999303fc99ef04368412da4b3555f999c703b425dedff3fdc799317c292751c46275b27990c53d933de2db63" + Qx = "c344f3bcc86df380186311fa502b7943a436a629380f8ee1960515522eedc58fe67ddd47615487668bcf12842c524d8", + Qy = "189e0c154f2631ad26e24ca73d84fb60a21d385fe205df04cf9f2f6fc0c3aa72afe9fbea71a930fa71d9bbfddb2fa571" ) test( id = 2, EC = ECP_SWei_Proj[Fp[BLS12_381]], - Px = "9ca8e33d8a330b04b052af6cf44bf2ed08cc93d83a4eb48cbb0cabfe02ffb2ef910df44862b271354352f15b70e45b5", - Py = "102f6d07ef45f51de9a4ecef5ec34eae16833f4761c2ddfbe2b414173c3580721135e5bbb74269ab85ba83cb03020d9b", + Px = "17d71835ff84f150fabf5c77ac90bf7f6249143abd1f5d8a46a76f243d424d82e1e258fc7983ba8af97a2462adebe090", + Py = "d3e108ee1332067cbe4f4193eae10381acb69f493b40e53d9dee59506b49c6564c9056494a7f987982eb4069512c1c6", scalar = "5f10367bdae7aa872d90b5ac209321ce5a15181ce22848d032a8d452055cbfd0", - Qx = "a50d49e3d8757f994aae312dedd55205687c432bc9d97efbe69e87bef4256b87af1b665a669d06657cda6ff01ee42df", - Qy = "160d50aaa21f9d5b4faada77e4f91d8d4f152a0fcca4d30d271d74b20c1bba8638128f99f52d9603d4a24f8e27219bcd" + Qx = "21073bee733a07b15d83afcd4e6ee11b01e6137fd5ad4589c5045e12d79a9a9490a3ebc59f30633a60fc3635a3c1e51", + Qy = "eb7a97a9d3dfff1667b8fa559bdcdf37c7767e6afb8ca93ad9dd44feb93761e10aa2c4c1a79728a21cd4a6f705398b5" ) test( id = 3, EC = ECP_SWei_Proj[Fp[BLS12_381]], - Px = "173c28687d23de83c950131e548485e8e09d4053d32b814d13b618ee4159e8b61bf6320148ddabcedf2b04d3c9787cd4", - Py = "277f935b4e0a90155915960c617f395dcadead1c7297cf92916add07308fc3f0493aa6dabf31d1f15953f56ac37d3d9", + Px = "f92c9572692e8f3d450483a7a9bb4694e3b54c9cd09441a4dd7f579b0a6984e47f8090c31c172b33d87f3de186d6b58", + Py = "286ede4cb2ae19ead4932d5550c5d3ec8ce3a3ada5e1ed6d202e93dd1b16d3513f0f9b62adc6323f18e272a426ee955", scalar = "4c321d72220c098fc0fd52306de98f8be9446bf854cf1e4d8dbae62375d18faf", - Qx = "16259e878b5921bbe1e5672cccea0f29fedbb93b8ce1bae4d4b602b6dd5708c6d4e5d82ff92868828c46fd333aadf82d", - Qy = "16d09713f4fe5705f2e3491aa9a1d5827fb3b280f5a1fdde0b01a2b75f5803d528d5f5603cc0e9da29d6a07b8e14df7c" + Qx = "4bb385e937582ae32aa7ba89632fcef2eace3f7b57309d979cf35298a430de9ef4d9ac5ba2335c1a4b6e7e5c38d0036", + Qy = "1801154d3a7b0daea772345b7f72a4c88c9677743f267da63490dad4dece2ecc9ec02d4d4d063086ee5d356aa2db914e" ) test( id = 4, EC = ECP_SWei_Proj[Fp[BLS12_381]], - Px = "177d32dfa6e97daf5ea8aee520bc5c33b7bee37cba114fda52dd948030ad81abdffbdda402c930791e1048ad531b2c80", - Py = "14e9f915698dadd2220586a8bcc921b1f855273c3c0e41a88569e5a9fd2a4e886eeff9a7a11b02ec286987c5a52d55ce", + Px = "ec23ff3435b8ebd5e8e0a879d432e11eb974161664b1341fd28f1ffc4c228bf6ada2ae4a565f18c9b66f67a7573502d", + Py = "10c4b647be08db0b49b75320ae891f9f9c5d7bb7c798947e800d681d205d1b24b12e4dfa993d1bd16851b00356627cc1", scalar = "1738857afb76c55f615c2a20b44ca90dcb3267d804ec23fddea431dbee4eb37f", - Qx = "a4bfcfc65eb16562752f5c164349ef673477e19fe020de84eddbc2958f6d40bbbba39fc67ee8c8fdf007922fec97f79", - Qy = "106ccd382d15773e6097f8ea6f012cbec15184d6f4ea08bac2842ed419f0e555f1a43f7434b2e017f9e02971d07eb59d" + Qx = "dc7ae7801152918ee3c13590407b4242a80d0b855a0bf585d3dc30719601d2d5d9e01e99ae735003ecb7c20ef48265", + Qy = "142c01a6aa390426a4ce2f36df43f86442732c35d4e05e5b67f3623832944f0ea5a29138624cb939330652a3cfb282b5" ) test( id = 5, EC = ECP_SWei_Proj[Fp[BLS12_381]], - Px = "1bc38e70e62770489063d3b7a3bebbc6735ab8dc389576ff03272c2883045aa051d74f8407920530b4c719b140cda81", - Py = "bd24e4fb09ed4098d61e3d2cb456f03d7818ded79dfba9cfe7956829797b12e10f1766c46c1a2e1cf2957295124c782", + Px = "df127083c2a5ef2388b02af913c0e4002a52a82db9e5ecbf23ee4f557d3b61c91ebcfe9d4973070b46bc5ea6897bca1", + Py = "318960aeea262ec23ffdd42ec1ba72ae6fa2186a1e2a0fc2659073fb7b5adfb50d581a4d998a94d1accf78b1b3a0163", scalar = "19c47811813444020c999a2b263940b5054cf45bb8ad8e086ff126bfcd5507e1", - Qx = "b310d4688f2c9f8cd4c030b62ed27341f4c71341fe9c56858a949a2d51670eb6ebe1339163bdb833e692b0ee0cf4e92", - Qy = "c92300561e1acb1e1ae6a1b75f83b9d2d2cb5f07c3f8ea945990ceb75e7ea12c4aec115227c13a05be92f5caed9268e" + Qx = "5f93c42fd76a29063efa2ee92607e0b3ae7edc4e419b3914661e5162d6beaeb96a34d2007ff817bc102651f61dca8d1", + Qy = "18dde8666bb1d0a379719d7d1b1512de809b70e49d9553303274ea872e56f7f39da551d6bcb7c57ae88ec7dc1fb354a4" ) test( id = 6, EC = ECP_SWei_Proj[Fp[BLS12_381]], - Px = "48cddafaca93d33caf910a7a6f74dc3489d53da9fa2f940b70b6dcf538cc08da1a369809ab86a8ee49cead0ed6bfef6", - Py = "173f8dfb384aea011bed89aaca625085dc2940d0775e5f2647fc7574ce822643d0d7b1b39e9a51b9f5a0dca7486bddd0", + Px = "101123de23c0f240c583c2368c4118dc942db219c55f58cf54acd500c1fcfa06f651ad75319ebf840cbdb6bddea7fde4", + Py = "5268587d4b844b0708e0336d1bbf48da185aaf5b948eccc3b565d00a856dd55882b9bb31c52af0e275b168cb35eb7b0", scalar = "43ffcda71e45a3e90b7502d92b30a0b06c54c95a91aa21e0438677b1c2714ecb", - Qx = "ef1e4967a3eb19318a66d092eada9810bebf301c168cea7c73fad9d98f7d4c2bde1071fd142c3da90830509f22a82b5", - Qy = "da537922dcb6bf79e4d09237c1a3c5804e3a83b6f18ccb26991d50d77c81bef76139fa73d39c684c7c1616151b1058b" + Qx = "f9871b682c1c76c7f4f0a7ca57ad876c10dc108b65b76987264873278d9f54db95101c173aed06d07062efc7d47ca0c", + Qy = "20d9628d611e72a4251a1f2357d4f53e68e4915383b6a0d126273d216b1a8c5e2cb7b2688ad702ef1682f4c5228fcd9" ) test( id = 7, EC = ECP_SWei_Proj[Fp[BLS12_381]], - Px = "9d56cb273bdeef945078066192b74d2f3077f00f5bd1a50b338c44f7c640005a614f9c6fc89cb4678140b2a721c69a8", - Py = "107b42b9a0c22b9e9cd2191b90fede2ab280532ea26806338a5b28533cf9431bde1a8010677a5078c63482953d4f2451", + Px = "1457ba1bae6eb3afae3261941c65c93e3ae7d784907d15b8d559100da5e13fd29e4a4d6e3103b781a95237b7b2d80a8e", + Py = "6a869a47cb48d01e7d29660932afd7617720262b55de5f430b8aa3d74f9fd2b9d3a07ce192425da58014764fc9532cd", scalar = "64ad0d6c36dba5368e71f0010aebf860288f54611e5aaf18082bae7a404ebfd8", - Qx = "e0c78d1e1ed993fdeb14e4872965bc90014aa39c728c457a720bf3123ebcdcb17ac553a619b9b7073ada436565d4bb4", - Qy = "c2d9ba441ed90bae4f1597da90e434f1668fda320e4fa04cddcdce0eacb3bc54185d5f7cde826f5bd0e3d59b2424906" + Qx = "93e540e26190e161038d985d40f2ab897cbc2346be7d8f2b201a689b59d4020a8740e252606f2f79ba0e121ccc9976d", + Qy = "10568d68f1b993aa1eded3869eda14e509f1cb4d8553bdf97feee175467cea4c0c1316fdb4e5a68440ad04b96b2d3bfc" ) test( id = 8, EC = ECP_SWei_Proj[Fp[BLS12_381]], - Px = "150a83a868fa6a74dbc5658445ea99ec47009572f303ce1d3c76370804c5a8c26d40c8b4b35a6585612d704c5fb090cb", - Py = "31e73ed0aedebcf0b58d60c16f2e5ddd2d4eb2a6e34177939efcca0767cde241966b5950c3333c62ccddee51de26fe6", + Px = "2615f843e8fe68d4c337bcf83b2cf13cbae638edd0740f1eac520dc2146afa3b8d36c540878c1d207ef913634b1e593", + Py = "1787d6eeeceb6e7793073f0bbe7bae522529c126b650c43d5d41e732c581a57df1bfb818061b7b4e6c9145da5df2c43e", scalar = "b0ac3d0e685583075aa46c03a00859dfbec24ccb36e2cae3806d82275adcc03", - Qx = "9c5e69fbd492a64e5811af7cc69e42bc14d8626f6d384d3f479d8e06c20ec5f460a1e3839f33899b4a9e0ada876ac6e", - Qy = "16990d7d308897c74b87368f847df3ac0bb6609091c8d39b22d5778a4229f0bb92fea385d27db41e237dcfb0d05bd0e7" + Qx = "d95ed29c2e15fd2205d83a71478341d6022deb93af4d49f704437678a72ce141d2f6043aa0e34e26f60d17e16b97053", + Qy = "b37cbded112c84116b74ff311b10d148f3e203cb88d4a011b096c74cd2bfdb27255727de4aa8299ae10b32d661d48a7" ) test( id = 9, EC = ECP_SWei_Proj[Fp[BLS12_381]], - Px = "69498486a06c18f836a8e9ed507bbb563d6d03545e03e08f628e8fbd2e5d098e58950071d516ffd044d92b3a8b07184", - Py = "18a169f06fc94f40cd131bdcd23e48e95b1276c0c8daacf56c3a5e278e89ee1094c94aa516113aa4a2455da149f0f989", + Px = "10bc0c4e1ed87246a9d4d7d38546369f275a245f6e1d3b882e8c9a7f05bc6ee8ff97a96a54084c2bef15ed8bfefb1465", + Py = "1782377e5f588576b5ab42fea224e88873dda957202f0c6d72ce8728c2d58dc654be77226fbda385d5f269354e4a176a", scalar = "23941bb3c3659423d6fdafb7cff52e0e02de0ac91e64c537c6203d64905b63d0", - Qx = "482e085550f5e514dd98f2d9b119c284ac165514d228c8f7a179f2b442968984873223af2255a499dc931c63543c0ba", - Qy = "151ce80ca51dd09243d2b1a7937096d6b7494e89190da5ab7604cd913dc4105c871e48c815fefadee2906b8b401e7e71" + Qx = "83f1e7e8bd963c1ccd837dae7bc9336531aaf0aee717537a9a7e2712e220f74cdb73a99f331c0eb6b377be3dafc211f", + Qy = "cd87773d072b1305dfc85c2983aecae2ab316e5e8f31306c32d58d6ce2e431b12685d18c58b6a35ad2113c5b689eeb" ) test( id = 10, EC = ECP_SWei_Proj[Fp[BLS12_381]], - Px = "98cc20aa561769b7ee569304503a94752e236bba52938fed7f3093d5867f65361dc8b48c83bd7db490c26736196e20e", - Py = "10a68394358903122bd649bd30b473f4d3b4f0830bfe7da1c48ae87d9429d8fd26f5b4be8d8fd8e4214017044696da29", + Px = "be4f9f721d98a761a5562bd80ea06f369e9cbb7d33bbb2f0191d4b77d0fd2a10c4083b54157b525f36c522ca3a6ca09", + Py = "166c315ecdd20acb3c5efcc7e038b17d0b37a06ffbf77873f15fc0cd091a1e4102a8b8bf5507919453759e744391b04d", scalar = "4203156dcf70582ea8cbd0388104f47fd5a18ae336b2fed8458e1e4e74d7baf5", - Qx = "18ff1dfd96799b7d0bffaa7480121c3a719047815ae41419f1bd1fdd593288bed8827b3d9e45a3a1e01bf7d603b5ba0", - Qy = "49b95ca2c0f75dfb15fc07e5692d23f8eb38cb1cc9c48cd0e93a80adbff135a3945cc7a5d53d2b7510d6ee7cf97308d" + Qx = "c72bc7087cd22993b7f6d2e49026abfde678a384073ed373b95df722b1ab658eb5ae42211e5528af606e38b59511bc6", + Qy = "96d80593b42fe44e64793e490b1257af0aa26b36773aac93c3686fdb14975917cf60a1a19e32623218d0722dbb88a85" ) diff --git a/tests/test_ec_bn254.nim b/tests/test_ec_bn254.nim index cbadb7bec..60444777b 100644 --- a/tests/test_ec_bn254.nim +++ b/tests/test_ec_bn254.nim @@ -45,7 +45,7 @@ proc test( impl.scalarMulGeneric(exponentCanonical, scratchSpace) reference.unsafe_ECmul_double_add(exponentCanonical) - endo.scalarMulGLV_BN254(exponent) + endo.scalarMulGLV(exponent) doAssert: bool(Q == reference) doAssert: bool(Q == impl) From 1ad1d4f1544dda6adbbf97fc823b62a2b4b81f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mamy=20Andr=C3=A9-Ratsimbazafy?= Date: Sun, 14 Jun 2020 15:08:46 +0200 Subject: [PATCH 19/19] fix nimble test script after bench rename --- constantine.nimble | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/constantine.nimble b/constantine.nimble index 94d787adc..d21c12fc5 100644 --- a/constantine.nimble +++ b/constantine.nimble @@ -194,7 +194,7 @@ task test_no_gmp, "Run tests that don't require GMP": runBench("bench_fp2") runBench("bench_fp6") runBench("bench_fp12") - runBench("bench_ec_swei_proj_g1") + runBench("bench_ec_g1") task test_parallel, "Run all tests in parallel (via GNU parallel)": # -d:testingCurves is configured in a *.nim.cfg for convenience @@ -282,7 +282,7 @@ task test_parallel, "Run all tests in parallel (via GNU parallel)": runBench("bench_fp2") runBench("bench_fp6") runBench("bench_fp12") - runBench("bench_ec_swei_proj_g1") + runBench("bench_ec_g1") task bench_fp, "Run benchmark 𝔽p with your default compiler": runBench("bench_fp")