BLS12-377 (#91)

* add Sage for constant time tonelli shanks

* Fused sqrt and invsqrt via Tonelli Shanks

* isolate sqrt in their own folder

* Implement constant-time Tonelli Shanks for any prime

* Implement Fp2 sqrt for any non-residue

* Add tests for BLS12_377

* Lattice decomposition script for BLS12_377 G1

* BLS12-377 G1 GLV ok, G2 GLV issue

* Proper endomorphism acceleration support for BLS12-377

* Add naive pairing support for BLS12-377

* Activate more bench for BLS12-377

* Fix MSB computation

* Optimize final exponentiation + add benches
This commit is contained in:
Mamy Ratsimbazafy 2020-09-27 09:15:14 +02:00 committed by GitHub
parent ac37b55aa1
commit 0e4dbfe400
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
50 changed files with 2315 additions and 263 deletions

View File

@ -35,7 +35,7 @@ const AvailableCurves = [
# Curve25519,
# P256,
# Secp256k1,
# BLS12_377,
BLS12_377,
BLS12_381,
# BN446,
# FKM12_447,
@ -48,23 +48,17 @@ proc main() =
staticFor i, 0, AvailableCurves.len:
const curve = AvailableCurves[i]
addBench(ECP_SWei_Proj[Fp[curve]], Iters)
separator()
mixedAddBench(ECP_SWei_Proj[Fp[curve]], Iters)
separator()
doublingBench(ECP_SWei_Proj[Fp[curve]], Iters)
separator()
scalarMulUnsafeDoubleAddBench(ECP_SWei_Proj[Fp[curve]], MulIters)
separator()
scalarMulGenericBench(ECP_SWei_Proj[Fp[curve]], window = 2, MulIters)
separator()
scalarMulGenericBench(ECP_SWei_Proj[Fp[curve]], window = 3, MulIters)
separator()
scalarMulGenericBench(ECP_SWei_Proj[Fp[curve]], window = 4, MulIters)
separator()
scalarMulGenericBench(ECP_SWei_Proj[Fp[curve]], window = 5, MulIters)
separator()
scalarMulEndo(ECP_SWei_Proj[Fp[curve]], MulIters)
separator()
scalarMulEndoWindow(ECP_SWei_Proj[Fp[curve]], MulIters)
separator()
separator()

View File

@ -36,7 +36,7 @@ const AvailableCurves = [
# Curve25519,
# P256,
# Secp256k1,
# BLS12_377,
BLS12_377,
BLS12_381,
# BN446,
# FKM12_447,
@ -49,23 +49,19 @@ proc main() =
staticFor i, 0, AvailableCurves.len:
const curve = AvailableCurves[i]
addBench(ECP_SWei_Proj[Fp2[curve]], Iters)
separator()
mixedAddBench(ECP_SWei_Proj[Fp2[curve]], Iters)
separator()
doublingBench(ECP_SWei_Proj[Fp2[curve]], Iters)
separator()
scalarMulUnsafeDoubleAddBench(ECP_SWei_Proj[Fp2[curve]], MulIters)
separator()
scalarMulGenericBench(ECP_SWei_Proj[Fp2[curve]], window = 2, MulIters)
separator()
scalarMulGenericBench(ECP_SWei_Proj[Fp2[curve]], window = 3, MulIters)
separator()
scalarMulGenericBench(ECP_SWei_Proj[Fp2[curve]], window = 4, MulIters)
separator()
scalarMulGenericBench(ECP_SWei_Proj[Fp2[curve]], window = 5, MulIters)
separator()
scalarMulEndo(ECP_SWei_Proj[Fp2[curve]], MulIters)
separator()
separator()
separator()
main()

View File

@ -28,12 +28,12 @@ const Iters = 1_000_000
const ExponentIters = 1000
const AvailableCurves = [
# P224,
# BN254_Nogami,
BN254_Nogami,
BN254_Snarks,
# Curve25519,
# P256,
# Secp256k1,
# BLS12_377,
BLS12_377,
BLS12_381,
# BN446,
# FKM12_447,
@ -52,7 +52,8 @@ proc main() =
sqrBench(Fp[curve], Iters)
invEuclidBench(Fp[curve], ExponentIters)
invPowFermatBench(Fp[curve], ExponentIters)
invAddChainBench(Fp[curve], ExponentIters)
when curve in {BN254_Snarks, BLS12_381}:
invAddChainBench(Fp[curve], ExponentIters)
sqrtBench(Fp[curve], ExponentIters)
# Exponentiation by a "secret" of size ~the curve order
powBench(Fp[curve], ExponentIters)

View File

@ -27,7 +27,7 @@ const Iters = 10_000
const InvIters = 1000
const AvailableCurves = [
# Pairing-Friendly curves
# BN254_Nogami,
BN254_Nogami,
BN254_Snarks,
BLS12_377,
BLS12_381

View File

@ -27,9 +27,9 @@ const Iters = 1_000_000
const InvIters = 1000
const AvailableCurves = [
# Pairing-Friendly curves
# BN254_Nogami,
BN254_Nogami,
BN254_Snarks,
# BLS12_377,
BLS12_377,
BLS12_381
# BN446,
# FKM12_447,

View File

@ -27,7 +27,7 @@ const Iters = 100_000
const InvIters = 1000
const AvailableCurves = [
# Pairing-Friendly curves
# BN254_Nogami,
BN254_Nogami,
BN254_Snarks,
BLS12_377,
BLS12_381

View File

@ -0,0 +1,51 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
# Internals
../constantine/config/curves,
../constantine/arithmetic,
../constantine/towers,
# Helpers
../helpers/static_for,
./bench_pairing_template,
# Standard library
std/strutils
# ############################################################
#
# Benchmark of pairings
# for BLS12-381
#
# ############################################################
const Iters = 50
const AvailableCurves = [
BLS12_377,
]
proc main() =
separator()
staticFor i, 0, AvailableCurves.len:
const curve = AvailableCurves[i]
lineDoubleBench(curve, Iters)
lineAddBench(curve, Iters)
mulFp12byLine_xyz000_Bench(curve, Iters)
separator()
finalExpEasyBench(curve, Iters)
finalExpHardBLS12Bench(curve, Iters)
separator()
millerLoopBLS12Bench(curve, Iters)
finalExpBLS12Bench(curve, Iters)
separator()
pairingBLS12Bench(curve, Iters)
separator()
main()
notes()

View File

@ -65,9 +65,14 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[
("tests/t_ec_wstrass_prj_g2_mul_sanity_bls12_381.nim", false),
("tests/t_ec_wstrass_prj_g2_mul_distri_bls12_381.nim", false),
("tests/t_ec_wstrass_prj_g2_mul_vs_ref_bls12_381.nim", false),
("tests/t_ec_wstrass_prj_g2_add_double_bls12_377.nim", false),
("tests/t_ec_wstrass_prj_g2_mul_sanity_bls12_377.nim", false),
("tests/t_ec_wstrass_prj_g2_mul_distri_bls12_377.nim", false),
("tests/t_ec_wstrass_prj_g2_mul_vs_ref_bls12_377.nim", false),
# Elliptic curve arithmetic vs Sagemath
("tests/t_ec_frobenius.nim", false),
("tests/t_ec_sage_bn254.nim", false),
("tests/t_ec_sage_bls12_377.nim", false),
("tests/t_ec_sage_bls12_381.nim", false),
# Edge cases highlighted by past bugs
("tests/t_ec_wstrass_prj_edge_cases.nim", false),
@ -76,7 +81,8 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[
("tests/t_pairing_cyclotomic_fp12.nim", false),
("tests/t_pairing_bn254_nogami_optate.nim", false),
("tests/t_pairing_bn254_snarks_optate.nim", false),
("tests/t_pairing_bls12_381_optate.nim", false)
("tests/t_pairing_bls12_377_optate.nim", false),
("tests/t_pairing_bls12_381_optate.nim", false),
]
# For temporary (hopefully) investigation that can only be reproduced in CI
@ -152,11 +158,16 @@ task test, "Run all tests":
# Ensure benchmarks stay relevant. Ignore Windows 32-bit at the moment
if not defined(windows) or not (existsEnv"UCPU" or getEnv"UCPU" == "i686"):
runBench("bench_fp")
runBench("bench_fp_double_width")
runBench("bench_fp2")
runBench("bench_fp6")
runBench("bench_fp12")
runBench("bench_ec_g1")
runBench("bench_ec_g2")
runBench("bench_pairing_bls12_377")
runBench("bench_pairing_bls12_381")
runBench("bench_pairing_bn254_nogami")
runBench("bench_pairing_bn254_snarks")
task test_no_gmp, "Run tests that don't require GMP":
# -d:testingCurves is configured in a *.nim.cfg for convenience
@ -449,6 +460,21 @@ task bench_ec_g2_gcc_noasm, "Run benchmark on Elliptic Curve group 𝔾2 - Short
task bench_ec_g2_clang_noasm, "Run benchmark on Elliptic Curve group 𝔾2 - Short Weierstrass with Projective Coordinates - Clang no Assembly":
runBench("bench_ec_g2", "clang", useAsm = false)
task bench_pairing_bls12_377, "Run pairings benchmarks for BLS12-377 - Default compiler":
runBench("bench_pairing_bls12_377")
task bench_pairing_bls12_377_gcc, "Run pairings benchmarks for BLS12-377 - GCC":
runBench("bench_pairing_bls12_377", "gcc")
task bench_pairing_bls12_377_clang, "Run pairings benchmarks for BLS12-377 - Clang":
runBench("bench_pairing_bls12_377", "clang")
task bench_pairing_bls12_377_gcc_noasm, "Run pairings benchmarks for BLS12-377 - GCC no Assembly":
runBench("bench_pairing_bls12_377", "gcc", useAsm = false)
task bench_pairing_bls12_377_clang_noasm, "Run pairings benchmarks for BLS12-377 - Clang no Assembly":
runBench("bench_pairing_bls12_377", "clang", useAsm = false)
task bench_pairing_bls12_381, "Run pairings benchmarks for BLS12-381 - Default compiler":
runBench("bench_pairing_bls12_381")

View File

@ -8,8 +8,16 @@
import
arithmetic/bigints,
arithmetic/[finite_fields, finite_fields_inversion, finite_fields_double_width]
arithmetic/[
finite_fields,
finite_fields_inversion,
finite_fields_square_root,
finite_fields_double_width
]
export
bigints,
finite_fields, finite_fields_inversion, finite_fields_double_width
finite_fields,
finite_fields_inversion,
finite_fields_square_root,
finite_fields_double_width

View File

@ -130,6 +130,10 @@ func isOdd*(a: BigInt): SecretBool =
## Returns true if a is odd
a.limbs.isOdd
func isEven*(a: BigInt): SecretBool =
## Returns true if a is even
a.limbs.isEven
func isMsbSet*(a: BigInt): SecretBool =
## Returns true if MSB is set
## i.e. if a BigInt is interpreted
@ -138,9 +142,9 @@ func isMsbSet*(a: BigInt): SecretBool =
## This is equivalent to checking
## if the number is negative
# MSB is at announced bits - (wordsRequired - 1)
const msb_pos = BigInt.bits-1 - (BigInt.bits.wordsRequired - 1)
SecretBool((BaseType(a.limbs[a.limbs.len-1]) shr msb_pos) and 1)
# MSB is at announced bits - (wordsRequired-1)*WordBitWidth - 1
const msb_in_msw = BigInt.bits - (BigInt.bits.wordsRequired-1)*WordBitWidth - 1
SecretBool((BaseType(a.limbs[a.limbs.len-1]) shr msb_in_msw) and 1)
func eq*(a: BigInt, n: SecretWord): SecretBool =
## Returns true if ``a`` is equal
@ -546,6 +550,9 @@ func montyPowUnsafeExponent*[mBits: static int](
var scratchSpace {.noInit.}: array[scratchLen, Limbs[mBits.wordsRequired]]
montyPowUnsafeExponent(a.limbs, exponent, M.limbs, one.limbs, negInvModWord, scratchSpace, canUseNoCarryMontyMul, canUseNoCarryMontySquare)
from ../io/io_bigints import exportRawUint
# Workaround recursive dependencies
func montyPow*[mBits, eBits: static int](
a: var BigInt[mBits], exponent: BigInt[eBits],
M, one: BigInt[mBits], negInvModWord: static BaseType, windowSize: static int,
@ -560,8 +567,6 @@ func montyPow*[mBits, eBits: static int](
##
## This is constant-time: the window optimization does
## not reveal the exponent bits or hamming weight
mixin exportRawUint # exported in io_bigints which depends on this module ...
var expBE {.noInit.}: array[(ebits + 7) div 8, byte]
expBE.exportRawUint(exponent, bigEndian)
@ -585,8 +590,6 @@ func montyPowUnsafeExponent*[mBits, eBits: static int](
##
## This uses fixed window optimization
## A window size in the range [1, 5] must be chosen
mixin exportRawUint # exported in io_bigints which depends on this module ...
var expBE {.noInit.}: array[(ebits + 7) div 8, byte]
expBE.exportRawUint(exponent, bigEndian)

View File

@ -27,6 +27,7 @@
import
../primitives,
../config/[common, type_fp, curves],
../io/io_bigints,
./bigints, ./limbs_montgomery
when UseASM_X86_64:
@ -307,153 +308,6 @@ func powUnsafeExponent*(a: var Fp, exponent: openarray[byte]) {.inline.} =
Fp.C.canUseNoCarryMontySquare()
)
# ############################################################
#
# Field arithmetic square roots
#
# ############################################################
func isSquare*[C](a: Fp[C]): SecretBool {.inline.} =
## Returns true if ``a`` is a square (quadratic residue) in 𝔽p
##
## Assumes that the prime modulus ``p`` is public.
# Implementation: we use exponentiation by (p-1)/2 (Euler's criterion)
# as it can reuse the exponentiation implementation
# Note that we don't care about leaking the bits of p
# as we assume that
var xi {.noInit.} = a # TODO: is noInit necessary? see https://github.com/mratsim/constantine/issues/21
xi.powUnsafeExponent(C.getPrimeMinus1div2_BE())
result = not(xi.mres == C.getMontyPrimeMinus1())
# xi can be:
# - 1 if a square
# - 0 if 0
# - -1 if a quadratic non-residue
debug:
doAssert: bool(
xi.isZero or
xi.isOne or
xi.mres == C.getMontyPrimeMinus1()
)
func sqrt_p3mod4[C](a: var Fp[C]) {.inline.} =
## Compute the square root of ``a``
##
## This requires ``a`` to be a square
## and the prime field modulus ``p``: p ≡ 3 (mod 4)
##
## The result is undefined otherwise
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
static: doAssert BaseType(C.Mod.limbs[0]) mod 4 == 3
a.powUnsafeExponent(C.getPrimePlus1div4_BE())
func sqrt_invsqrt_p3mod4[C](sqrt, invsqrt: var Fp[C], a: Fp[C]) {.inline.} =
## If ``a`` is a square, compute the square root of ``a`` in sqrt
## and the inverse square root of a in invsqrt
##
## This assumes that the prime field modulus ``p``: p ≡ 3 (mod 4)
# TODO: deterministic sign
#
# Algorithm
#
#
# From Euler's criterion: a^((p-1)/2)) ≡ 1 (mod p) if square
# a^((p-1)/2)) * a^-1 ≡ 1/a (mod p)
# a^((p-3)/2)) ≡ 1/a (mod p)
# a^((p-3)/4)) ≡ 1/√a (mod p) # Requires p ≡ 3 (mod 4)
static: doAssert BaseType(C.Mod.limbs[0]) mod 4 == 3
invsqrt = a
invsqrt.powUnsafeExponent(C.getPrimeMinus3div4_BE())
# √a ≡ a * 1/√a ≡ a^((p+1)/4) (mod p)
sqrt.prod(invsqrt, a)
func sqrt_invsqrt_if_square_p3mod4[C](sqrt, invsqrt: var Fp[C], a: Fp[C]): SecretBool {.inline.} =
## If ``a`` is a square, compute the square root of ``a`` in sqrt
## and the inverse square root of a in invsqrt
##
## If a is not square, sqrt and invsqrt are undefined
##
## This assumes that the prime field modulus ``p``: p ≡ 3 (mod 4)
sqrt_invsqrt_p3mod4(sqrt, invsqrt, a)
var euler {.noInit.}: Fp[C]
euler.prod(sqrt, invsqrt)
result = not(euler.mres == C.getMontyPrimeMinus1())
func sqrt_if_square_p3mod4[C](a: var Fp[C]): SecretBool {.inline.} =
## If ``a`` is a square, compute the square root of ``a``
## if not, ``a`` is unmodified.
##
## This assumes that the prime field modulus ``p``: p ≡ 3 (mod 4)
##
## The result is undefined otherwise
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
var sqrt {.noInit.}, invsqrt {.noInit.}: Fp[C]
result = sqrt_invsqrt_if_square_p3mod4(sqrt, invsqrt, a)
a.ccopy(sqrt, result)
func sqrt*[C](a: var Fp[C]) {.inline.} =
## Compute the square root of ``a``
##
## This requires ``a`` to be a square
##
## The result is undefined otherwise
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
when BaseType(C.Mod.limbs[0]) mod 4 == 3:
sqrt_p3mod4(a)
else:
{.error: "Square root is only implemented for p ≡ 3 (mod 4)".}
func sqrt_if_square*[C](a: var Fp[C]): SecretBool {.inline.} =
## If ``a`` is a square, compute the square root of ``a``
## if not, ``a`` is unmodified.
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
when BaseType(C.Mod.limbs[0]) mod 4 == 3:
result = sqrt_if_square_p3mod4(a)
else:
{.error: "Square root is only implemented for p ≡ 3 (mod 4)".}
func sqrt_invsqrt*[C](sqrt, invsqrt: var Fp[C], a: Fp[C]) {.inline.} =
## Compute the square root and inverse square root of ``a``
##
## This requires ``a`` to be a square
##
## The result is undefined otherwise
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
when BaseType(C.Mod.limbs[0]) mod 4 == 3:
sqrt_invsqrt_p3mod4(sqrt, invsqrt, a)
else:
{.error: "Square root is only implemented for p ≡ 3 (mod 4)".}
func sqrt_invsqrt_if_square*[C](sqrt, invsqrt: var Fp[C], a: Fp[C]): SecretBool {.inline.} =
## Compute the square root and ivnerse square root of ``a``
##
## This returns true if ``a`` is square and sqrt/invsqrt contains the square root/inverse square root
##
## The result is undefined otherwise
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
when BaseType(C.Mod.limbs[0]) mod 4 == 3:
result = sqrt_invsqrt_if_square_p3mod4(sqrt, invsqrt, a)
else:
{.error: "Square root is only implemented for p ≡ 3 (mod 4)".}
# ############################################################
#
# Field arithmetic ergonomic primitives

View File

@ -0,0 +1,269 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
std/macros,
../primitives,
../config/[common, type_fp, curves],
../io/[io_bigints, io_fields],
./bigints, ./finite_fields, ./limbs_montgomery
# ############################################################
#
# Field arithmetic square roots
#
# ############################################################
# Legendre symbol / Euler's Criterion / Kronecker's symbol
# ------------------------------------------------------------
func isSquare*[C](a: Fp[C]): SecretBool {.inline.} =
## Returns true if ``a`` is a square (quadratic residue) in 𝔽p
##
## Assumes that the prime modulus ``p`` is public.
# Implementation: we use exponentiation by (p-1)/2 (Euler's criterion)
# as it can reuse the exponentiation implementation
# Note that we don't care about leaking the bits of p
# as we assume that
var xi {.noInit.} = a # TODO: is noInit necessary? see https://github.com/mratsim/constantine/issues/21
xi.powUnsafeExponent(C.getPrimeMinus1div2_BE())
result = not(xi.mres == C.getMontyPrimeMinus1())
# xi can be:
# - 1 if a square
# - 0 if 0
# - -1 if a quadratic non-residue
debug:
doAssert: bool(
xi.isZero or
xi.isOne or
xi.mres == C.getMontyPrimeMinus1()
)
# Specialized routine for p ≡ 3 (mod 4)
# ------------------------------------------------------------
func sqrt_p3mod4[C](a: var Fp[C]) {.inline.} =
## Compute the square root of ``a``
##
## This requires ``a`` to be a square
## and the prime field modulus ``p``: p ≡ 3 (mod 4)
##
## The result is undefined otherwise
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
static: doAssert BaseType(C.Mod.limbs[0]) mod 4 == 3
a.powUnsafeExponent(C.getPrimePlus1div4_BE())
func sqrt_invsqrt_p3mod4[C](sqrt, invsqrt: var Fp[C], a: Fp[C]) {.inline.} =
## If ``a`` is a square, compute the square root of ``a`` in sqrt
## and the inverse square root of a in invsqrt
##
## This assumes that the prime field modulus ``p``: p ≡ 3 (mod 4)
# TODO: deterministic sign
#
# Algorithm
#
#
# From Euler's criterion: a^((p-1)/2)) ≡ 1 (mod p) if square
# a^((p-1)/2)) * a^-1 ≡ 1/a (mod p)
# a^((p-3)/2)) ≡ 1/a (mod p)
# a^((p-3)/4)) ≡ 1/√a (mod p) # Requires p ≡ 3 (mod 4)
static: doAssert BaseType(C.Mod.limbs[0]) mod 4 == 3
invsqrt = a
invsqrt.powUnsafeExponent(C.getPrimeMinus3div4_BE())
# √a ≡ a * 1/√a ≡ a^((p+1)/4) (mod p)
sqrt.prod(invsqrt, a)
func sqrt_invsqrt_if_square_p3mod4[C](sqrt, invsqrt: var Fp[C], a: Fp[C]): SecretBool {.inline.} =
## If ``a`` is a square, compute the square root of ``a`` in sqrt
## and the inverse square root of a in invsqrt
##
## If a is not square, sqrt and invsqrt are undefined
##
## This assumes that the prime field modulus ``p``: p ≡ 3 (mod 4)
sqrt_invsqrt_p3mod4(sqrt, invsqrt, a)
var euler {.noInit.}: Fp[C]
euler.prod(sqrt, invsqrt)
result = not(euler.mres == C.getMontyPrimeMinus1())
func sqrt_if_square_p3mod4[C](a: var Fp[C]): SecretBool {.inline.} =
## If ``a`` is a square, compute the square root of ``a``
## if not, ``a`` is unmodified.
##
## This assumes that the prime field modulus ``p``: p ≡ 3 (mod 4)
##
## The result is undefined otherwise
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
var sqrt {.noInit.}, invsqrt {.noInit.}: Fp[C]
result = sqrt_invsqrt_if_square_p3mod4(sqrt, invsqrt, a)
a.ccopy(sqrt, result)
# Tonelli Shanks for any prime
# ------------------------------------------------------------
const
# with e = 2adicity
# p == s * 2^e + 1
# root_of_unity = smallest_quadratic_nonresidue^s
# exponent = (p-1-2^e)/2^e / 2
TonelliShanks_exponent_BLS12_377 = BigInt[330].fromHex"0x35c748c2f8a21d58c760b80d94292763445b3e601ea271e3de6c45f741290002e16ba88600000010a11"
TonelliShanks_twoAdicity_BLS12_377 = 46
TonelliShanks_root_of_unity_BLS12_377 = Fp[BLS12_377].fromHex"0x382d3d99cdbc5d8fe9dee6aa914b0ad14fcaca7022110ec6eaa2bc56228ac41ea03d28cc795186ba6b5ef26b00bbe8"
{.experimental: "dynamicBindSym".}
macro tsGet(C: static Curve, value: untyped): untyped =
return bindSym("TonelliShanks_" & $value & "_" & $C)
func precompute_tonelli_shanks[C](
a_pre_exp: var Fp[C],
a: Fp[C]) =
a_pre_exp = a
a_pre_exp.powUnsafeExponent(C.tsGet(exponent))
func isSquare_tonelli_shanks[C](
a, a_pre_exp: Fp[C]): SecretBool =
## Returns if `a` is a quadratic residue
## This uses common precomputation for
## Tonelli-Shanks based square root and inverse square root
##
## a^((p-1-2^e)/(2*2^e))
const e = C.tsGet(twoAdicity)
var r {.noInit.}: Fp[C]
r.square(a_pre_exp) # a^(2(q-1-2^e)/(2*2^e)) = a^((q-1)/2^e - 1)
r *= a # a^((q-1)/2^e)
for _ in 0 ..< e-1:
r.square() # a^((q-1)/2)
result = not(r.mres == C.getMontyPrimeMinus1())
# r can be:
# - 1 if a square
# - 0 if 0
# - -1 if a quadratic non-residue
debug:
doAssert: bool(
r.isZero or
r.isOne or
r.mres == C.getMontyPrimeMinus1()
)
func sqrt_invsqrt_tonelli_shanks[C](
sqrt, invsqrt: var Fp[C],
a, a_pre_exp: Fp[C]) =
## Compute the square_root and inverse_square_root
## of `a` via constant-time Tonelli-Shanks
##
## a_pre_exp is a precomputation a^((p-1-2^e)/(2*2^e))
## ThItat is shared with the simultaneous isSquare routine
template z: untyped = a_pre_exp
template r: untyped = invsqrt
var t {.noInit.}: Fp[C]
const e = C.tsGet(twoAdicity)
t.square(z)
t *= a
r = z
var b = t
var root = C.tsGet(root_of_unity)
var buf {.noInit.}: Fp[C]
for i in countdown(e, 2, 1):
for j in 1 .. i-2:
b.square()
let bNotOne = not b.isOne()
buf.prod(r, root)
r.ccopy(buf, bNotOne)
root.square()
buf.prod(t, root)
t.ccopy(buf, bNotOne)
b = t
sqrt.prod(invsqrt, a)
# Public routines
# ------------------------------------------------------------
func sqrt*[C](a: var Fp[C]) {.inline.} =
## Compute the square root of ``a``
##
## This requires ``a`` to be a square
##
## The result is undefined otherwise
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
## This procedure is constant-time
when (BaseType(C.Mod.limbs[0]) and 3) == 3:
sqrt_p3mod4(a)
else:
var a_pre_exp{.noInit.}, sqrt{.noInit.}, invsqrt{.noInit.}: Fp[C]
a_pre_exp.precompute_tonelli_shanks(a)
sqrt_invsqrt_tonelli_shanks(sqrt, invsqrt, a, a_pre_exp)
a = sqrt
func sqrt_if_square*[C](a: var Fp[C]): SecretBool {.inline.} =
## If ``a`` is a square, compute the square root of ``a``
## if not, ``a`` is unmodified.
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
## This procedure is constant-time
when (BaseType(C.Mod.limbs[0]) and 3) == 3:
result = sqrt_if_square_p3mod4(a)
else:
var a_pre_exp{.noInit.}, sqrt{.noInit.}, invsqrt{.noInit.}: Fp[C]
a_pre_exp.precompute_tonelli_shanks(a)
result = isSquare_tonelli_shanks(a, a_pre_exp)
sqrt_invsqrt_tonelli_shanks(sqrt, invsqrt, a, a_pre_exp)
a = sqrt
func sqrt_invsqrt*[C](sqrt, invsqrt: var Fp[C], a: Fp[C]) {.inline.} =
## Compute the square root and inverse square root of ``a``
##
## This requires ``a`` to be a square
##
## The result is undefined otherwise
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
when (BaseType(C.Mod.limbs[0]) and 3) == 3:
sqrt_invsqrt_p3mod4(sqrt, invsqrt, a)
else:
var a_pre_exp{.noInit.}: Fp[C]
a_pre_exp.precompute_tonelli_shanks(a)
sqrt_invsqrt_tonelli_shanks(sqrt, invsqrt, a, a_pre_exp)
func sqrt_invsqrt_if_square*[C](sqrt, invsqrt: var Fp[C], a: Fp[C]): SecretBool {.inline.} =
## Compute the square root and ivnerse square root of ``a``
##
## This returns true if ``a`` is square and sqrt/invsqrt contains the square root/inverse square root
##
## The result is undefined otherwise
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
when (BaseType(C.Mod.limbs[0]) and 3) == 3:
result = sqrt_invsqrt_if_square_p3mod4(sqrt, invsqrt, a)
else:
var a_pre_exp{.noInit.}: Fp[C]
a_pre_exp.precompute_tonelli_shanks(a)
result = isSquare_tonelli_shanks(a, a_pre_exp)
sqrt_invsqrt_tonelli_shanks(sqrt, invsqrt, a, a_pre_exp)
a = sqrt

View File

@ -146,6 +146,10 @@ func isOdd*(a: Limbs): SecretBool =
## Returns true if a is odd
SecretBool(a[0] and SecretWord(1))
func isEven*(a: Limbs): SecretBool =
## Returns true if a is even
not SecretBool(a[0] and SecretWord(1))
# Bit manipulation
# ------------------------------------------------------------

View File

@ -140,9 +140,12 @@ declareCurves:
family: BarretoLynnScott
# u: 3 * 2^46 * (7 * 13 * 499) + 1
# u: 0x8508c00000000001
cubicRootOfUnity_mod_p: "0x9b3af05dd14f6ec619aaf7d34594aabc5ed1347970dec00452217cc900000008508c00000000001"
# G1 Equation: y² = x³ + 1
# G2 Equation: y² = x³ + 1/ with 𝑗 = √-5
order: "0x12ab655e9a2ca55660b44d1e5c37b00159aa76fed00000010a11800000000001"
orderBitwidth: 253
eq_form: ShortWeierstrass
coef_a: 0
coef_b: 1

View File

@ -244,9 +244,15 @@ func scalarMulEndo*[scalBits](
# and negate it as well.
#
# However solution 1 seems to cause issues (TODO)
# with some of the BLS12-381 test cases (6 and 9)
# with some of the BLS12-381 G2 test cases (6 and 9) as one of the miniscalars is 65 bits
# instead of the expected maximum of 64.
# - 0x5668a2332db27199dcfb7cbdfca6317c2ff128db26d7df68483e0a095ec8e88f
# - 0x644dc62869683f0c93f38eaef2ba6912569dc91ec2806e46b4a3dd6a4421dad1
#
# Furthermore solution 2 isn't enough on BLS12-377 G2 as test fails when miniScalars[0] is negative
let isNeg0 = miniscalars[0].isMsbSet()
miniscalars[0].cneg(isNeg0)
P.cneg(isNeg0)
# 4. Precompute lookup table
var lut {.noInit.}: array[1 shl (M-1), ECP_SWei_Proj]

View File

@ -52,6 +52,24 @@ const Babai_BN254_Snarks_G1 = (
(BigInt[130].fromHex"0x24ccef014a773d2d25398fd0300ff6565", false) # (6u² + 4u + 1) << 2^256 // r
)
# BLS12-377 G1
# ----------------------------------------------------------------------------------------
const Lattice_BLS12_377_G1 = (
# (BigInt, isNeg)
((BigInt[127].fromHex"0x452217cc900000010a11800000000000", false), # u² - 1
(BigInt[1].fromHex"0x1", true)), # -1
((BigInt[1].fromHex"0x1", false), # 1
(BigInt[127].fromHex"0x452217cc900000010a11800000000001", false)) # u²
)
const Babai_BLS12_377_G1 = (
# Vector for Babai rounding
# (BigInt, isNeg)
(BigInt[130].fromHex"0x3b3f7aa969fd371607f72ed32af90182c", false),
(BigInt[4].fromHex"0xd", false)
)
# BLS12-381 G1
# ----------------------------------------------------------------------------------------
@ -107,6 +125,43 @@ const Babai_BN254_Snarks_G2 = (
(BigInt[128].fromhex"0xc444fab18d269b9af7ae23ce89afae7d", true) # -2x²-x << 2^256 // r
)
# BLS12-377 G2
# ----------------------------------------------------------------------------------------
const Lattice_BLS12_377_G2 = (
# Curve of order 254 -> mini scalars of size 65
# x = -0xd201000000010000
# Value, isNeg
((BigInt[64].fromHex"0x8508c00000000001", true), # -x
(BigInt[1].fromHex"0x1", false), # 1
(BigInt[1].fromHex"0x0", false), # 0
(BigInt[1].fromHex"0x0", false)), # 0
((BigInt[1].fromHex"0x0", false), # 0
(BigInt[64].fromHex"0x8508c00000000001", true), # -x
(BigInt[1].fromHex"0x1", false), # 1
(BigInt[1].fromHex"0x0", false)), # 0
((BigInt[1].fromHex"0x0", false), # 0
(BigInt[1].fromHex"0x0", false), # 0
(BigInt[64].fromHex"0x8508c00000000001", true), # -x
(BigInt[1].fromHex"0x1", false)), # 1
((BigInt[1].fromHex"0x1", false), # 1
(BigInt[1].fromHex"0x0", false), # 0
(BigInt[1].fromHex"0x1", true), # -1
(BigInt[64].fromHex"0x8508c00000000001", true)) # -x
)
const Babai_BLS12_377_G2 = (
# Vector for Babai rounding
# Value, isNeg
(BigInt[193].fromHex"0x1eca0125755aed064f63abaff9084ce152979759b442f60d1", true),
(BigInt[130].fromHex"0x3b3f7aa969fd371607f72ed32af90181f", true),
(BigInt[67].fromhex"0x72030ba8ee9c06415", true),
(BigInt[1].fromhex"0x0", false)
)
# BLS12-381 G2
# ----------------------------------------------------------------------------------------

View File

@ -241,7 +241,7 @@ func scalarMul*(
## - 0 <= scalar < curve order
## this will not automatically
when BigInt.bits <= ECP_SWei_Proj.F.C.getCurveOrderBitwidth() and
ECP_SWei_Proj.F.C in {BN254_Snarks, BLS12_381}:
ECP_SWei_Proj.F.C in {BN254_Snarks, BLS12_377, BLS12_381}:
when ECP_SWei_Proj.F is Fp:
P.scalarMulGLV_m2w2(scalar)
elif ECP_SWei_Proj.F is Fp2:

View File

@ -32,6 +32,12 @@ const Cofactor_Eff_BN254_Snarks_G1 = BigInt[1].fromHex"0x1"
const Cofactor_Eff_BN254_Snarks_G2 = BigInt[254].fromHex"0x30644e72e131a029b85045b68181585e06ceecda572a2489345f2299c0f9fa8d"
## G2.order // r
# TODO effective cofactors as per H2C draft like BLS12-381 curve
const Cofactor_Eff_BLS12_377_G1 = BigInt[125].fromHex"0x170b5d44300000000000000000000000"
## P -> (1 - x) P
const Cofactor_Eff_BLS12_377_G2 = BigInt[502].fromHex"0x26ba558ae9562addd88d99a6f6a829fbb36b00e1dcc40c8c505634fae2e189d693e8c36676bd09a0f3622fba094800452217cc900000000000000000000001"
## P -> (x^2 - x - 1) P + (x - 1) psi(P) + psi(psi(2P))
# https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-09#section-8.8
const Cofactor_Eff_BLS12_381_G1 = BigInt[64].fromHex"0xd201000000010001"
## P -> (1 - x) P
@ -58,9 +64,17 @@ func clearCofactorReference*(P: var ECP_SWei_Proj[Fp2[BN254_Snarks]]) {.inline.}
# Endomorphism acceleration cannot be used if cofactor is not cleared
P.scalarMulGeneric(Cofactor_Eff_BN254_Snarks_G2)
func clearCofactorReference*(P: var ECP_SWei_Proj[Fp[BLS12_377]]) {.inline.} =
## Clear the cofactor of BLS12_377 G1
P.scalarMulGeneric(Cofactor_Eff_BLS12_377_G1)
func clearCofactorReference*(P: var ECP_SWei_Proj[Fp2[BLS12_377]]) {.inline.} =
## Clear the cofactor of BLS12_377 G2
# Endomorphism acceleration cannot be used if cofactor is not cleared
P.scalarMulGeneric(Cofactor_Eff_BLS12_377_G2)
func clearCofactorReference*(P: var ECP_SWei_Proj[Fp[BLS12_381]]) {.inline.} =
## Clear the cofactor of BLS12_381 G1
## BN curve have a G1 cofactor of 1 so this is a no-op
P.scalarMulGeneric(Cofactor_Eff_BLS12_381_G1)
func clearCofactorReference*(P: var ECP_SWei_Proj[Fp2[BLS12_381]]) {.inline.} =

View File

@ -64,7 +64,11 @@ func toHex*(f: Fp, order: static Endianness = bigEndian): string =
## - no leaks
result.appendHex(f, order)
func fromHex*(dst: var Fp, s: string) {.raises: [ValueError].}=
func fromHex*(dst: var Fp, hexString: string) {.raises: [ValueError].}=
## Convert a hex string to a element of Fp
let raw {.noinit.} = fromHex(dst.mres.typeof, s)
let raw {.noinit.} = fromHex(dst.mres.typeof, hexString)
dst.fromBig(raw)
func fromHex*(T: type Fp, hexString: string): T {.noInit, raises: [ValueError].}=
## Convert a hex string to a element of Fp
result.fromHex(hexString)

View File

@ -66,6 +66,86 @@ template mulCheckSparse[Fp2](a: var Fp2, b: Fp2) =
# Frobenius map - on extension fields
# -----------------------------------------------------------------
# c = (SNR^((p-1)/6)^coef).
# Then for frobenius(2): c * conjugate(c)
# And for frobenius(3): c² * conjugate(c)
const FrobMapConst_BLS12_377 = [
# frobenius(1)
[Fp2[BLS12_377].fromHex( # SNR^((p-1)/6)^0
"0x1",
"0x0"
),
Fp2[BLS12_377].fromHex( # SNR^((p-1)/6)^1
"0x9a9975399c019633c1e30682567f915c8a45e0f94ebc8ec681bf34a3aa559db57668e558eb0188e938a9d1104f2031",
"0x0"
),
Fp2[BLS12_377].fromHex( # SNR^((p-1)/6)^2 = SNR^((p-1)/3)
"0x9b3af05dd14f6ec619aaf7d34594aabc5ed1347970dec00452217cc900000008508c00000000002",
"0x0"
),
Fp2[BLS12_377].fromHex( # SNR^((p-1)/6)^3 = SNR^((p-1)/2)
"0x1680a40796537cac0c534db1a79beb1400398f50ad1dec1bce649cf436b0f6299588459bff27d8e6e76d5ecf1391c63",
"0x0"
),
Fp2[BLS12_377].fromHex( # SNR^((p-1)/6)^4 = SNR^(2(p-1)/3)
"0x9b3af05dd14f6ec619aaf7d34594aabc5ed1347970dec00452217cc900000008508c00000000001",
"0x0"
),
Fp2[BLS12_377].fromHex( # SNR^((p-1)/6)^5
"0xcd70cb3fc936348d0351d498233f1fe379531411832232f6648a9a9fc0b9c4e3e21b7467077c05853e2c1be0e9fc32",
"0x0"
)],
# frobenius(2)
[Fp2[BLS12_377].fromHex( # norm(SNR)^((p-1)/6)^1
"0x1",
"0x0"
),
Fp2[BLS12_377].fromHex( # norm(SNR)^((p-1)/6)^2
"0x9b3af05dd14f6ec619aaf7d34594aabc5ed1347970dec00452217cc900000008508c00000000002",
"0x0"
),
Fp2[BLS12_377].fromHex(
"0x9b3af05dd14f6ec619aaf7d34594aabc5ed1347970dec00452217cc900000008508c00000000001",
"0x0"
),
Fp2[BLS12_377].fromHex(
"0x1ae3a4617c510eac63b05c06ca1493b1a22d9f300f5138f1ef3622fba094800170b5d44300000008508c00000000000",
"0x0"
),
Fp2[BLS12_377].fromHex(
"0x1ae3a4617c510eabc8756ba8f8c524eb8882a75cc9bc8e359064ee822fb5bffd1e945779fffffffffffffffffffffff",
"0x0"
),
Fp2[BLS12_377].fromHex(
"0x1ae3a4617c510eabc8756ba8f8c524eb8882a75cc9bc8e359064ee822fb5bffd1e94577a00000000000000000000000",
"0x0"
)],
# frobenius(3)
[Fp2[BLS12_377].fromHex(
"0x1",
"0x0"
),
Fp2[BLS12_377].fromHex(
"0x1680a40796537cac0c534db1a79beb1400398f50ad1dec1bce649cf436b0f6299588459bff27d8e6e76d5ecf1391c63",
"0x0"
),
Fp2[BLS12_377].fromHex(
"0x1ae3a4617c510eac63b05c06ca1493b1a22d9f300f5138f1ef3622fba094800170b5d44300000008508c00000000000",
"0x0"
),
Fp2[BLS12_377].fromHex(
"0x4630059e5fd9200575d0e552278a89da1f40fdf62334cd620d1860769e389d7db2d8ea700d82721691ea130ec6e39e",
"0x0"
),
Fp2[BLS12_377].fromHex(
"0x1",
"0x0"
),
Fp2[BLS12_377].fromHex(
"0x1680a40796537cac0c534db1a79beb1400398f50ad1dec1bce649cf436b0f6299588459bff27d8e6e76d5ecf1391c63",
"0x0"
)]]
# c = (SNR^((p-1)/6)^coef).
# Then for frobenius(2): c * conjugate(c)
# And for frobenius(3): c² * conjugate(c)

View File

@ -7,6 +7,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
std/macros,
../primitives,
../config/[common, curves],
../arithmetic,
@ -45,21 +46,39 @@ import
# https://eprint.iacr.org/2009/615.pdf
# TODO: should be part of curve parameters
const BLS12_381_param = block:
# The bit count must be exact for the Miller loop
const BLS12_377_ate_param = block:
# BLS Miller loop is parametrized by u
BigInt[64+1].fromHex("0x8508c00000000001") # +1 so that we can take *3 and NAF encode it
const BLS12_377_ate_param_isNeg = false
const BLS12_381_ate_param = block:
# BLS Miller loop is parametrized by u
BigInt[64+2].fromHex("0xd201000000010000") # +2 so that we can take *3 and NAF encode it
const BLS12_381_param_isNeg = true
const BLS12_381_ate_param_isNeg = true
# Generic slow pairing implementation
# ----------------------------------------------------------------
const BLS12_377_finalexponent = block:
# (p^12 - 1) / r
# BigInt[4269].fromHex"0x1b2ff68c1abdc48ab4f04ed12cc8f9b2f161b41c7eb8865b9ad3c9bb0571dd94c6bde66548dc13624d9d741024ceb315f46a89cc2482605eb6afc6d8977e5e2ccbec348dd362d59ec2b5bc62a1b467ae44572215548abc98bb4193886ed89cceaedd0221aba84fb33e5584ac29619a87a00c315178155496857c995eab4a8a9af95f4015db27955ae408d6927d0ab37d52f3917c4ddec88f8159f7bcba7eb65f1aae4eeb4e70cb20227159c08a7fdfea9b62bb308918eac3202569dd1bcdd86b431e3646356fc3fb79f89b30775e006993adb629586b6c874b7688f86f11ef7ad94a40eb020da3c532b317232fa56dc564637b331a8e8832eab84269f00b506602c8594b7f7da5a5d8d851fff6ab1d38a354fc8e0b8958e2a9e5ce2d7e50ec36d761d9505fe5e1f317257e2df2952fcd4c93b85278c20488b4ccaee94db3fec1ce8283473e4b493843fa73abe99af8bafce29170b2b863b9513b5a47312991f60c5a4f6872b5d574212bf00d797c0bea3c0f7dfd748e63679fda9b1c50f2df74de38f38e004ae0df997a10db31d209cacbf58ba0678bfe7cd0985bc43258d72d8d5106c21635ae1e527eb01fca3032d50d97756ec9ee756eaba7f21652a808a4e2539e838ef7ec4b178b29e3b976c46bd0ecdd32c1fb75e6e0aef2d8b5661f595a98023f3520381aba8da6cce785dbb0a0bba025478d75ee749619cdb7c42a21098ece86a00c6c2046c1e00000063c69000000000000"
# (p^12 - 1) / r * 3
BigInt[4271].fromHex"0x518fe3a450394da01ed0ec73865aed18d4251c557c299312d07b5d31105598be5439b32fda943a26e8d85c306e6c1941dd3f9d646d87211c240f5489c67b1a8663c49da97a2880dc48213527e51d370acd05663ffda035ca31c4ba994c89d66c0c97066502f8ef19bb008e047c24cf96e02493f4683ffdc39075cc1c01df9fd0ec1dc0419176c010ac1a83b777201a77f8dab474e99c59ae840de7362f7c231d500aecc1eb52616067540d419f7f9fbfd22831919b4ac04960703d9753698941c95aa2d2a04f4bf26de9d191661a013cbb09227c09424595e2639ae94d35ce708bdec2c10628eb4f981945698ef049502d2a71994fab9898c028c73dd021f13208590be27e78f0f18a88f5ffe40157a9e9fef5aa229c0aa7fdb16a887af2c4a486258bf11fb1a5d945707a89d7bf8f67e5bb28f76a460d9a1e660cbbe91bfc456b8789d5bae1dba8cbef5b03bcd0ea30f6a7b45218292b2bf3b20ed5937cb5e2250eee395821805c6383d0286c7423beb42e79f85dab2a36df8fd154f2d89e5e9aaadaaa00e0a29ecc6e329195761d6063e0a2e136a3fb7671c9134c970a8588a7f3144642a10a5af77c105f5e90987f28c6604c5dcb604c02f7d642f7f819eea6fadb8aace7c4e146a17dab2c644d4372c6979845f261b4a20cd88a20325e0c0fc806bd9f60a8502fa8f466b6919311e232e06fd6a861cb5dc24d69274c7e631cac6b93e0254460d445a0000012b53b000000000000"
const BLS12_381_finalexponent = block:
# (p^12 - 1) / r
# BigInt[4314].fromHex"0x2ee1db5dcc825b7e1bda9c0496a1c0a89ee0193d4977b3f7d4507d07363baa13f8d14a917848517badc3a43d1073776ab353f2c30698e8cc7deada9c0aadff5e9cfee9a074e43b9a660835cc872ee83ff3a0f0f1c0ad0d6106feaf4e347aa68ad49466fa927e7bb9375331807a0dce2630d9aa4b113f414386b0e8819328148978e2b0dd39099b86e1ab656d2670d93e4d7acdd350da5359bc73ab61a0c5bf24c374693c49f570bcd2b01f3077ffb10bf24dde41064837f27611212596bc293c8d4c01f25118790f4684d0b9c40a68eb74bb22a40ee7169cdc1041296532fef459f12438dfc8e2886ef965e61a474c5c85b0129127a1b5ad0463434724538411d1676a53b5a62eb34c05739334f46c02c3f0bd0c55d3109cd15948d0a1fad20044ce6ad4c6bec3ec03ef19592004cedd556952c6d8823b19dadd7c2498345c6e5308f1c511291097db60b1749bf9b71a9f9e0100418a3ef0bc627751bbd81367066bca6a4c1b6dcfc5cceb73fc56947a403577dfa9e13c24ea820b09c1d9f7c31759c3635de3f7a3639991708e88adce88177456c49637fd7961be1a4c7e79fb02faa732e2f3ec2bea83d196283313492caa9d4aff1c910e9622d2a73f62537f2701aaef6539314043f7bbce5b78c7869aeb2181a67e49eeed2161daf3f881bd88592d767f67c4717489119226c2f011d4cab803e9d71650a6f80698e2f8491d12191a04406fbc8fbd5f48925f98630e68bfb24c0bcb9b55df57510"
# (p^12 - 1) / r * 3
BigInt[4316].fromHex"0x8ca592196587127a538fd40dc3e541f9dca04bb7dc671be77cf17715a2b2fe3bea73dfb468d8f473094aecb7315a664019fbd84913caba6579c08fd42009fe1bd6fcbce15eacb2cf3218a165958cb8bfdae2d2d54207282314fc0dea9d6ff3a07dbd34efb77b732ba5f994816e296a72928cfee133bdc3ca9412b984b9783d9c6aa81297ab1cd294a502304773528bbae8706979f28efa0d355b0224e2513d6e4a5d3bb4dde0523678105d9167ff1323d6e99ac312d8a7d762336370c4347bb5a7e405d6f3496b2dd38e722d4c1f3ac25e3167ec2cb543d69430c37c2f98fcdd0dd36caa9f5aa7994cec31b24ed5e515911037b376e521070d29c9d56cfa8c3574363efb20f28c19e4105ab99edd44084bd23725017931d6740bda71e5f07600ce6b407e543c4bc40bcd4c0b600e6c98003bf8548986b14d9098746dc89d154af91ad54f337b31c79222145dd3ed254fdeda0300c49ebcd2352765f533883a3513435f3ee452496f5166c25bf503bd6ec0a0679efda3b46ebf86211d458de749460d4a2a19abe6ea2accb451ab9a096b98465d044dc2a7f86c253a4ee57b6df108eff598a8dbc483bf8b74c2789939db85ffd7e0fd55b32bc26877f5be26fa7d750500ce2fab93c0cbe7336b126a5693d0c16484f37addccc7642590dbe98538990b88637e374d545d9b34b67448d0357e60280bbd8542f1f4e813caa8e8db57364b4e0cc14f35af381dd9b71ec9292b3a3f16e42362d2019e05f30"
{.experimental: "dynamicBindSym".}
macro get(C: static Curve, value: untyped): untyped =
return bindSym($C & "_" & $value)
func millerLoopGenericBLS12*[C: static Curve](
f: var Fp12[C],
P: ECP_SWei_Aff[Fp[C]],
@ -95,9 +114,6 @@ func millerLoopGenericBLS12*[C: static Curve](
# or we special case line addition of T and -T (it's a vertical line)
# or we ensure the loop is done for a number of iterations strictly less
# than the curve order which is the case for BLS12 curves
static: doAssert C == BLS12_381, "Only BLS12-381 is supported at the moment"
var
T {.noInit.}: ECP_SWei_Proj[Fp2[C]]
line {.noInit.}: Line[Fp2[C], C.getSexticTwist()]
@ -107,22 +123,29 @@ func millerLoopGenericBLS12*[C: static Curve](
nQ.neg(Q)
f.setOne()
template u: untyped = BLS12_381_param
let u3 = 3*BLS12_381_param
template mul(f, line): untyped =
when C.getSexticTwist() == D_Twist:
f.mul_sparse_by_line_xyz000(line)
else:
f.mul_sparse_by_line_xy000z(line)
template u: untyped = C.get(ate_param)
let u3 = 3*C.get(ate_param)
for i in countdown(u3.bits - 2, 1):
f.square()
line.line_double(T, P)
f.mul_sparse_by_line_xy000z(line)
f.mul(line)
let naf = u3.bit(i).int8 - u.bit(i).int8 # This can throw exception
if naf == 1:
line.line_add(T, Q, P)
f.mul_sparse_by_line_xy000z(line)
f.mul(line)
elif naf == -1:
line.line_add(T, nQ, P)
f.mul_sparse_by_line_xy000z(line)
f.mul(line)
when BLS12_381_param_isNeg: # TODO generic
when C.get(ate_param_isNeg):
# In GT, x^-1 == conjugate(x)
# Remark 7.1, chapter 7.1.1 of Guide to Pairing-Based Cryptography, El Mrabet, 2017
f.conj()
@ -130,8 +153,7 @@ func millerLoopGenericBLS12*[C: static Curve](
func finalExpGeneric[C: static Curve](f: var Fp12[C]) =
## A generic and slow implementation of final exponentiation
## for sanity checks purposes.
static: doAssert C == BLS12_381, "Only BLS12-381 is supported at the moment"
f.powUnsafeExponent(BLS12_381_finalexponent, window = 3)
f.powUnsafeExponent(C.get(finalexponent), window = 3)
func pairing_bls12_reference*[C](gt: var Fp12[C], P: ECP_SWei_Proj[Fp[C]], Q: ECP_SWei_Proj[Fp2[C]]) =
## Compute the optimal Ate Pairing for BLS12 curves
@ -154,7 +176,7 @@ func cycl_sqr_repeated(f: var Fp12, num: int) =
for _ in 0 ..< num:
f.cyclotomic_square()
func pow_xdiv2(r: var Fp12[BLS12_381], a: Fp12[BLS12_381], invert = BLS12_381_param_isNeg) =
func pow_xdiv2(r: var Fp12[BLS12_381], a: Fp12[BLS12_381], invert = BLS12_381_ate_param_isNeg) =
## f^(x/2) with x the curve parameter
## For BLS12_381 f^-0xd201000000010000
@ -173,12 +195,41 @@ func pow_xdiv2(r: var Fp12[BLS12_381], a: Fp12[BLS12_381], invert = BLS12_381_pa
if invert:
r.cyclotomic_inv()
func pow_x(r: var Fp12[BLS12_381], a: Fp12[BLS12_381], invert = BLS12_381_param_isNeg) =
func pow_x(r: var Fp12[BLS12_381], a: Fp12[BLS12_381], invert = BLS12_381_ate_param_isNeg) =
## f^x with x the curve parameter
## For BLS12_381 f^-0xd201000000010000
r.pow_xdiv2(a, invert)
r.cyclotomic_square()
func pow_x(r: var Fp12[BLS12_377], a: Fp12[BLS12_377], invert = BLS12_377_ate_param_isNeg) =
## f^x with x the curve parameter
## For BLS12_377 f^-0x8508c00000000001
## Warning: The parameter is odd and needs a correction
r.cyclotomic_square(a)
r *= a
r.cyclotomic_square()
r *= a
let t111 = r
r.cycl_sqr_repeated(2)
let t111000 = r
r *= t111
let t100011 = r
r.cyclotomic_square()
r *= t100011
r *= t111000
r.cycl_sqr_repeated(10)
r *= t100011
r.cycl_sqr_repeated(46)
r *= a
if invert:
r.cyclotomic_inv()
func finalExpHard_BLS12*[C: static Curve](f: var Fp12[C]) =
## Hard part of the final exponentiation
## Specialized for BLS12 curves
@ -208,7 +259,10 @@ func finalExpHard_BLS12*[C: static Curve](f: var Fp12[C]) =
v2.cyclotomic_square(f) # v2 = f²
# (x1)²
v0.pow_xdiv2(v2) # v0 = (f²)^(x/2) = f^x
when C.get(ate_param).isEven.bool:
v0.pow_xdiv2(v2) # v0 = (f²)^(x/2) = f^x
else:
v0.pow_x(f)
v1.cyclotomic_inv(f) # v1 = f^-1
v0 *= v1 # v0 = f^(x-1)
v1.pow_x(v0) # v1 = (f^(x-1))^x

View File

@ -43,15 +43,16 @@ import
# https://eprint.iacr.org/2009/615.pdf
# TODO: should be part of curve parameters
# The bit count must be exact for the Miller loop
const BN254_Snarks_ate_param = block:
# BN Miller loop is parametrized by 6u+2
BigInt[67].fromHex"0x19d797039be763ba8"
BigInt[65+2].fromHex"0x19d797039be763ba8"
const BN254_Snarks_ate_param_isNeg = false
const BN254_Nogami_ate_param = block:
# BN Miller loop is parametrized by 6u+2
BigInt[67].fromHex"0x18300000000000004" # 65+2 bit for NAF x3 encoding
BigInt[65+2].fromHex"0x18300000000000004" # 65+2 bit for NAF x3 encoding
const BN254_Nogami_ate_param_isNeg = true
@ -116,22 +117,28 @@ func millerLoopGenericBN*[C: static Curve](
nQ.neg(Q)
f.setOne()
template mul(f, line): untyped =
when C.getSexticTwist() == D_Twist:
f.mul_sparse_by_line_xyz000(line)
else:
f.mul_sparse_by_line_xy000z(line)
template u: untyped = C.get(ate_param)
let u3 = 3*C.get(ate_param)
for i in countdown(u3.bits - 2, 1):
f.square()
line.line_double(T, P)
f.mul_sparse_by_line_xyz000(line)
f.mul(line)
let naf = u3.bit(i).int8 - u.bit(i).int8 # This can throw exception
if naf == 1:
line.line_add(T, Q, P)
f.mul_sparse_by_line_xyz000(line)
f.mul(line)
elif naf == -1:
line.line_add(T, nQ, P)
f.mul_sparse_by_line_xyz000(line)
f.mul(line)
when C.get(ate_param_isNeg): # TODO generic
when C.get(ate_param_isNeg):
# In GT, x^-1 == conjugate(x)
# Remark 7.1, chapter 7.1.1 of Guide to Pairing-Based Cryptography, El Mrabet, 2017
f.conj()

View File

@ -221,16 +221,18 @@ func isSquare*(a: QuadraticExt): SecretBool =
#
# https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-08#appendix-G.5
# https://eprint.iacr.org/2012/685
mixin fromComplexExtension # TODO: relax this
static: doAssert a.fromComplexExtension()
mixin fromComplexExtension
var tv1{.noInit.}, tv2{.noInit.}: typeof(a.c0)
tv1.square(a.c0) # a0²
tv2.square(a.c1) # - β a1² with β = 𝑖² in a complex extension field
when a.fromComplexExtension():
tv1 += tv2 # a0 - (-1) a1²
else:
tv2 *= NonResidue
tv1 -= tv2
tv1 += tv2 # a0 - (-1) a1²
result = tv1.isSquare()
func sqrt_if_square*(a: var QuadraticExt): SecretBool =
@ -243,16 +245,18 @@ func sqrt_if_square*(a: var QuadraticExt): SecretBool =
#
# Implementation via the complex method (which confusingly does not require a complex field)
# We make it constant-time via conditional copies
mixin fromComplexExtension # TODO: relax this
static: doAssert a.fromComplexExtension()
mixin fromComplexExtension
var t1{.noInit.}, t2{.noInit.}, t3{.noInit.}: typeof(a.c0)
t1.square(a.c0) # a0²
t2.square(a.c1) # - β a1² with β = 𝑖² in a complex extension field
when a.fromComplexExtension():
t1 += t2 # a0 - (-1) a1²
else:
t2 *= NonResidue
t1 -= t2
t1 += t2 # a0 - (-1) a1²
result = t1.sqrt_if_square()
t2.sum(a.c0, t1)

View File

@ -92,7 +92,10 @@ def compute_curve_characteristic(x_str):
if __name__ == "__main__":
# Usage
# BLS12-381
# sage sage/curve_family_bls12.sage '-(2^63 + 2^62 + 2^60 + 2^57 + 2^48 + 2^16)'
# BLS12-377
# sage sage/curve_family_bls12.sage '3 * 2^46 * (7 * 13 * 499) + 1'
from argparse import ArgumentParser

View File

@ -41,9 +41,27 @@ G2 = EllipticCurve(Fp2, [0, b/SNR])
# Utilities
def fp2_to_hex(a):
v = vector(a)
return Integer(v[0]).hex() + ' + β * ' + Integer(v[1]).hex()
return '0x' + Integer(v[0]).hex() + ' + β * ' + '0x' + Integer(v[1]).hex()
# Frobenius constants (D type: use SNR, M type use 1/SNR)
# Frobenius map constants
print('\nFrobenius extension field constants')
FrobConst_map = SNR^((p-1)/6)
FrobConst_map_list = []
cur = Fp2([1, 0])
for i in range(6):
FrobConst_map_list.append(cur)
print(f'FrobConst_map_{i} : {fp2_to_hex(cur)}')
cur *= FrobConst_map
print('')
for i in range(6):
print(f'FrobConst_map_{i}_pow2 : {fp2_to_hex(FrobConst_map_list[i]*conjugate(FrobConst_map_list[i]))}')
print('')
for i in range(6):
print(f'FrobConst_map_{i}_pow3 : {fp2_to_hex(FrobConst_map_list[i]**2 * conjugate(FrobConst_map_list[i]))}')
# Frobenius psi constants (D type: use SNR, M type use 1/SNR)
print('\nψ (Psi) - Untwist-Frobenius-Twist constants')
FrobConst_psi = SNR^((p-1)/6)
FrobConst_psi_2 = FrobConst_psi * FrobConst_psi
FrobConst_psi_3 = FrobConst_psi_2 * FrobConst_psi

View File

@ -0,0 +1,223 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
# ############################################################
#
# BLS12-381 GLS Endomorphism
# Lattice Decomposition
#
# ############################################################
# Parameters
x = 3 * 2^46 * (7 * 13 * 499) + 1
p = (x - 1)^2 * (x^4 - x^2 + 1)//3 + x
r = x^4 - x^2 + 1
print('p : ' + p.hex())
print('r : ' + r.hex())
# Cube root of unity (mod r) formula for any BLS12 curves
lambda1_r = x^2 - 1
assert lambda1_r^3 % r == 1
print('λᵩ1 : ' + lambda1_r.hex())
print('λᵩ1+r: ' + (lambda1_r+r).hex())
lambda2_r = x^4
assert lambda2_r^3 % r == 1
print('λᵩ2 : ' + lambda2_r.hex())
# Finite fields
F = GF(p)
# Curves
b = 1
G1 = EllipticCurve(F, [0, b])
cofactorG1 = G1.order() // r
print('')
print('cofactor G1: ' + cofactorG1.hex())
print('')
(phi1, phi2) = (root for root in GF(p)(1).nth_root(3, all=True) if root != 1)
print('𝜑1 :' + Integer(phi1).hex())
print('𝜑2 :' + Integer(phi2).hex())
assert phi1^3 % p == 1
assert phi2^3 % p == 1
def clearCofactorG1(P):
return cofactorG1 * P
# Test generator
set_random_seed(1337)
# Check
def checkEndo():
Prand = G1.random_point()
P = clearCofactorG1(Prand)
assert P != G1([0, 1, 0]) # Infinity
(Px, Py, Pz) = P
Qendo1 = G1([Px*phi1 % p, Py, Pz])
Qendo2 = G1([Px*phi2 % p, Py, Pz])
Q1 = lambda1_r * P
Q2 = lambda2_r * P
assert P != Q1
assert P != Q2
assert (F(Px)*F(phi1))^3 == F(Px)^3
assert (F(Px)*F(phi2))^3 == F(Px)^3
assert Q1 == Qendo1
assert Q2 == Qendo1
print('Endomorphism OK with 𝜑1')
checkEndo()
# Decomposition generated by LLL-algorithm and Babai rounding
# to solve the Shortest (Basis) Vector Problem
# Lattice from Guide to Pairing-Based Cryptography
Lat = [
[x^2-1, -1],
[1, x^2]
]
ahat = [x^2, 1]
n = int(r).bit_length()
n = int(((n + 64 - 1) // 64) * 64) # round to next multiple of 64
v = [Integer(a << n) // r for a in ahat]
def pretty_print_lattice(Lat):
latHex = [['0x' + x.hex() if x >= 0 else '-0x' + (-x).hex() for x in vec] for vec in Lat]
maxlen = max([len(cell) for row in latHex for cell in row])
for row in latHex:
row = ' '.join(cell.rjust(maxlen + 2) for cell in row)
print(row)
print('\nLattice')
pretty_print_lattice(Lat)
print('\nbasis:')
print(' 𝛼\u03050: 0x' + v[0].hex())
print(' 𝛼\u03051: 0x' + v[1].hex())
print('')
maxInfNorm = abs(x^2 + 1)
print('\nmax infinity norm:')
print(' ||(a0 , a1)||∞ ≤ 0x' + str(maxInfNorm.hex()))
print(' infinity norm bitlength: ' + str(int(maxInfNorm).bit_length()))
# Contrary to Faz2013 paper, we use the max infinity norm
# to properly dimension our recoding instead of ⌈log2 r/m⌉ + 1
# which fails for some inputs
#
# +1 for signed column
# Optional +1 for handling negative miniscalars
L = int(maxInfNorm).bit_length() + 1
L += 1
def getGLV1_decomp(scalar):
maxLen = (int(r).bit_length() + 1) // 2 + 1
a0 = (v[0] * scalar) >> n
a1 = (v[1] * scalar) >> n
k0 = scalar - a0 * Lat[0][0] - a1 * Lat[1][0]
k1 = 0 - a0 * Lat[0][1] - a1 * Lat[1][1]
assert int(k0).bit_length() <= maxLen
assert int(k1).bit_length() <= maxLen
assert scalar == (k0 + k1 * (lambda1_r % r)) % r
assert scalar == (k0 + k1 * (lambda2_r % r)) % r
return k0, k1
def recodeScalars(k):
m = 2
b = [[0] * L, [0] * L]
b[0][L-1] = 0
for i in range(0, L-1): # l-2 inclusive
b[0][i] = 1 - ((k[0] >> (i+1)) & 1)
for j in range(1, m):
for i in range(0, L):
b[j][i] = k[j] & 1
k[j] = k[j]//2 + (b[j][i] & b[0][i])
return b
def buildLut(P0, P1):
m = 2
lut = [0] * (1 << (m-1))
lut[0] = P0
lut[1] = P0 + P1
return lut
def pointToString(P):
(Px, Py, Pz) = P
return '(x: ' + Integer(Px).hex() + ', y: ' + Integer(Py).hex() + ', z: ' + Integer(Pz).hex() + ')'
def scalarMulEndo(scalar, P0):
m = 2
print('L: ' + str(L))
print('scalar: ' + Integer(scalar).hex())
k0, k1 = getGLV1_decomp(scalar)
print('k0: ' + k0.hex())
print('k1: ' + k1.hex())
P1 = (lambda1_r % r) * P0
(Px, Py, Pz) = P0
P1_endo = G1([Px*phi1 % p, Py, Pz])
assert P1 == P1_endo
expected = scalar * P0
decomp = k0*P0 + k1*P1
assert expected == decomp
print('------ recode scalar -----------')
even = k0 & 1 == 0
if even:
k0 += 1
b = recodeScalars([k0, k1])
print('b0: ' + str(list(reversed(b[0]))))
print('b1: ' + str(list(reversed(b[1]))))
print('------------ lut ---------------')
lut = buildLut(P0, P1)
print('------------ mul ---------------')
# b[0][L-1] is always 0
Q = lut[b[1][L-1]]
for i in range(L-2, -1, -1):
Q *= 2
Q += (1 - 2 * b[0][i]) * lut[b[1][i]]
if even:
Q -= P0
print('final Q: ' + pointToString(Q))
print('expected: ' + pointToString(expected))
assert Q == expected
# Test generator
set_random_seed(1337)
for i in range(1):
print('---------------------------------------')
scalar = randrange(r) # Pick an integer below curve order
P = G1.random_point()
P = clearCofactorG1(P)
scalarMulEndo(scalar, P)

View File

@ -0,0 +1,375 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
# ############################################################
#
# BLS12-381 GLS Endomorphism
# Lattice Decomposition
#
# ############################################################
# Parameters
x = 3 * 2^46 * (7 * 13 * 499) + 1
p = (x - 1)^2 * (x^4 - x^2 + 1)//3 + x
r = x^4 - x^2 + 1
t = x+1
print(' Prime modulus p: 0x' + p.hex())
print(' Curve order r: 0x' + r.hex())
print(' trace t: 0x' + t.hex())
# Finite fields
Fp = GF(p)
K2.<u> = PolynomialRing(Fp)
Fp2.<beta> = Fp.extension(u^2+5)
SNR = Fp2([0, 1]) # Sextic Non-Residue for Sextic Twist
# Curves
b = 1
G1 = EllipticCurve(Fp, [0, b])
G2 = EllipticCurve(Fp2, [0, b/SNR])
# https://crypto.stackexchange.com/questions/64064/order-of-twisted-curve-in-pairings
# https://math.stackexchange.com/questions/144194/how-to-find-the-order-of-elliptic-curve-over-finite-field-extension
cofactorG1 = G1.order() // r
cofactorG2 = G2.order() // r
print('')
print('cofactor G1: ' + cofactorG1.hex())
print('cofactor G2: ' + cofactorG2.hex())
print('')
# Frobenius constants (D type: use SNR, M type use 1/SNR)
FrobConst_psi = SNR^((p-1)/6)
FrobConst_psi_2 = FrobConst_psi * FrobConst_psi
FrobConst_psi_3 = FrobConst_psi_2 * FrobConst_psi
FrobConst_psi2_2 = FrobConst_psi_2 * FrobConst_psi_2^p
FrobConst_psi2_3 = FrobConst_psi_3 * FrobConst_psi_3^p
def psi(P):
(Px, Py, Pz) = P
return G2([
FrobConst_psi_2 * Px.frobenius(1),
FrobConst_psi_3 * Py.frobenius(1)
# Pz.frobenius() - Always 1 after extract
])
def psi2(P):
(Px, Py, Pz) = P
return G2([
FrobConst_psi2_2 * Px.frobenius(2),
FrobConst_psi2_3 * Py.frobenius(2)
# Pz - Always 1 after extract
])
def clearCofactorG2(P):
return cofactorG2 * P
# Test generator
set_random_seed(1337)
# Check
def checkEndo():
P = G2.random_point()
P = clearCofactorG2(P)
(Px, Py, Pz) = P
# Galbraith-Lin-Scott, 2008, Theorem 1
assert psi(psi(P)) - t*psi(P) + p*P == G2([0, 1, 0])
# Galbraith-Scott, 2008, Lemma 1
# k-th cyclotomic polynomial with k = 12
assert psi2(psi2(P)) - psi2(P) + P == G2([0, 1, 0])
assert p % r == (t-1) % r
# assert (p^4 - p^2 + 1) % r == 0
assert ((t-1)^4 - (t-1)^2 + 1) % r == 0
assert (t-1)*P == (p % r)*P
assert (t-1)*P == psi(P)
print('Endomorphism OK')
checkEndo()
def subgroup_check(P):
ppP = psi2(P)
assert x * psi(ppP) - ppP + P == G2([0,1,0])
# Decomposition generated by LLL-algorithm and Babai rounding
# to solve the Shortest (Basis) Vector Problem
#
# TODO: This lattice is generating wrong result
# Lattice from Guide to Pairing-Based Cryptography
# Lat = [
# [ x, 1, 0, 0],
# [ 0, x, 1, 0],
# [ 0, 0, x, 1],
# [ 1, 0,-1, x]
# ]
# ahat = [x*(x^2+1), -(x^2+1), x, -1]
# Lattice from my own LLL+Babai rounding routines
Lat = Matrix([
[-x, 1, 0, 0],
[ 0,-x, 1, 0],
[ 0, 0,-x, 1],
[ 1, 0,-1,-x]
])
# print('Lat: ' + str(Lat))
ahat = vector([r, 0, 0, 0]) * Lat.inverse()
# print('ahat: ' + str(ahat))
n = int(r).bit_length()
n = int(((n + 64 - 1) // 64) * 64) # round to next multiple of 64
v = [Integer(int(a) << n) // r for a in ahat]
def pretty_print_lattice(Lat):
latHex = [['0x' + x.hex() if x >= 0 else '-0x' + (-x).hex() for x in vec] for vec in Lat]
maxlen = max([len(cell) for row in latHex for cell in row])
for row in latHex:
row = ' '.join(cell.rjust(maxlen + 2) for cell in row)
print(row)
print('\nLattice')
pretty_print_lattice(Lat)
print('\nbasis:')
print(' 𝛼\u03050: 0x' + v[0].hex())
print(' 𝛼\u03051: 0x' + v[1].hex())
print(' 𝛼\u03052: 0x' + v[2].hex())
print(' 𝛼\u03053: 0x' + v[3].hex())
print('')
maxInfNorm = abs(x + 2)
print('\nmax infinity norm:')
print(' ||(a0, a1, a2, a3)||∞ ≤ 0x' + str(maxInfNorm.hex()))
print(' infinity norm bitlength: ' + str(int(maxInfNorm).bit_length()))
# Contrary to Faz2013 paper, we use the max infinity norm
# to properly dimension our recoding instead of ⌈log2 r/m⌉ + 1
# which fails for some inputs
# +1 for signed column
# Optional +1 for handling negative miniscalars
L = int(maxInfNorm).bit_length() + 1
L += 1
lambda1 = (t-1) % r
lambda2 = lambda1^2 % r
lambda3 = lambda1^3 % r
def getGLV2_decomp(scalar):
maxLen = (int(r).bit_length() + 3) // 4 + 1
maxLen += 1 # Deal with negative miniscalars
a0 = (v[0] * scalar) >> n
a1 = (v[1] * scalar) >> n
a2 = (v[2] * scalar) >> n
a3 = (v[3] * scalar) >> n
k0 = scalar - a0 * Lat[0][0] - a1 * Lat[1][0] - a2 * Lat[2][0] - a3 * Lat[3][0]
k1 = 0 - a0 * Lat[0][1] - a1 * Lat[1][1] - a2 * Lat[2][1] - a3 * Lat[3][1]
k2 = 0 - a0 * Lat[0][2] - a1 * Lat[1][2] - a2 * Lat[2][2] - a3 * Lat[3][2]
k3 = 0 - a0 * Lat[0][3] - a1 * Lat[1][3] - a2 * Lat[2][3] - a3 * Lat[3][3]
print("k0.bitlength(): " + str(int(k0).bit_length()))
print("k1.bitlength(): " + str(int(k1).bit_length()))
print("k2.bitlength(): " + str(int(k2).bit_length()))
print("k3.bitlength(): " + str(int(k3).bit_length()))
print('k0: ' + k0.hex())
print('k1: ' + k1.hex())
print('k2: ' + k2.hex())
print('k3: ' + k3.hex())
assert scalar == (k0 + k1*lambda1 + k2*lambda2 + k3*lambda3) % r
assert int(k0).bit_length() <= maxLen
assert int(k1).bit_length() <= maxLen
assert int(k2).bit_length() <= maxLen
assert int(k3).bit_length() <= maxLen
return k0, k1, k2, k3
def recodeScalars(k):
m = 4
b = [[0] * L, [0] * L, [0] * L, [0] * L]
b[0][L-1] = 0
for i in range(0, L-1): # l-2 inclusive
b[0][i] = 1 - ((k[0] >> (i+1)) & 1)
for j in range(1, m):
for i in range(0, L):
b[j][i] = k[j] & 1
k[j] = k[j]//2 + (b[j][i] & b[0][i])
return b
def clearBit(v, bit):
return v & ~int(1 << bit)
def buildLut(P0, P_endos):
m = 4
assert len(P_endos) == m-1
lut = [0] * (1 << (m-1))
lut[0] = P0
lutS = [''] * (1 << (m-1))
lutS[0] = 'P0'
endoS = ['P1', 'P2', 'P3']
for u in range(1, 1 << (m-1)):
msb = u.bit_length() - 1
idx = clearBit(u, msb)
lut[u] = lut[clearBit(u, msb)] + P_endos[msb]
lutS[u] = lutS[clearBit(u, msb)] + ' + ' + endoS[msb]
print('LUT: ' + str(lutS))
return lut
def pointToString(P):
(Px, Py, Pz) = P
vPx = vector(Px)
vPy = vector(Py)
result = 'Point(\n'
result += ' Px: ' + Integer(vPx[0]).hex() + ' + β * ' + Integer(vPx[1]).hex() + '\n'
result += ' Py: ' + Integer(vPy[0]).hex() + ' + β * ' + Integer(vPy[1]).hex() + '\n'
result += ')'
return result
def getIndex(glvRecoding, bit):
m = 4
index = 0
for k in range(1, m):
index |= ((glvRecoding[k][bit] & 1) << (k-1))
return index
def updateFactors(factors, recoded, bit):
index = getIndex(recoded, bit)
if recoded[0][bit] == 0: # Positive
factors[0] += 1
factors[1] += (index >> 0) & 1
factors[2] += (index >> 1) & 1
factors[3] += (index >> 2) & 1
else:
factors[0] -= 1
factors[1] -= (index >> 0) & 1
factors[2] -= (index >> 1) & 1
factors[3] -= (index >> 2) & 1
def doubleFactors(factors):
for i in range(len(factors)):
factors[i] *= 2
def printFactors(factors):
print('Multiplication done: ')
for i in range(len(factors)):
print(f' f{i}: {factors[i].hex()}')
def scalarMulEndo(scalar, P0):
m = 4
print('L: ' + str(L))
print('scalar: ' + Integer(scalar).hex())
k0, k1, k2, k3 = getGLV2_decomp(scalar)
P1 = psi(P0)
P2 = psi2(P0)
P3 = psi(P2)
expected = scalar * P0
decomp = k0*P0 + k1*P1 + k2*P2 + k3*P3
print('expected: ' + pointToString(expected))
print('decomp: ' + pointToString(decomp))
assert expected == decomp
# Alternative to adding an extra bit
# to deal with miniscalars
# if k0 < 0: k0 = -k0; P0 = -P0
# if k1 < 0: k1 = -k1; P1 = -P1
# if k2 < 0: k2 = -k2; P2 = -P2
# if k3 < 0: k3 = -k3; P3 = -P3
# assert expected == k0*P0 + k1*P1 + k2*P2 + k3*P3
# Somehow the recoding doesn't cope with first scalar being negative
if k0 < 0:
k0 = -k0
P0 = -P0
print('------ recode scalar -----------')
even = k0 & 1 == 0
print('was even: ' + str(even))
if even:
k0 += 1
b = recodeScalars([k0, k1, k2, k3])
print('b0: ' + str(list(reversed(b[0]))))
print('b1: ' + str(list(reversed(b[1]))))
print('b2: ' + str(list(reversed(b[2]))))
print('b3: ' + str(list(reversed(b[3]))))
print('------------ lut ---------------')
lut = buildLut(P0, [P1, P2, P3])
print('------------ mul ---------------')
# b[0][L-1] is always 0
print(f'L-1: {getIndex(b, L-1)}')
print(f'L-2: {getIndex(b, L-2)}')
print(f'L-3: {getIndex(b, L-3)}')
print(f'L-4: {getIndex(b, L-4)}')
print(f'L-5: {getIndex(b, L-5)}')
print(f'L-6: {getIndex(b, L-6)}')
factors = [0, 0, 0, 0] # Track the decomposed scalar applied (debugging)
updateFactors(factors, b, L-1)
Q = lut[getIndex(b, L-1)]
for bit in range(L-2, -1, -1):
Q *= 2
Q += (1 - 2 * b[0][bit]) * lut[getIndex(b, bit)]
doubleFactors(factors)
updateFactors(factors, b, bit)
if even:
Q -= P0
print('----')
print('final Q: ' + pointToString(Q))
print('expected: ' + pointToString(expected))
print('----')
printFactors(factors)
print('Mul expected:')
print(' k0: ' + k0.hex())
print(' k1: ' + k1.hex())
print(' k2: ' + k2.hex())
print(' k3: ' + k3.hex())
assert Q == expected
# Test generator
set_random_seed(1337)
for i in range(1):
print('---------------------------------------')
scalar = randrange(r) # Pick an integer below curve order
# P = G2.random_point()
# P = clearCofactorG2(P)
scalar = Integer('0x9d432eb58ec68bbc09d10961451d99c7796fb2f795eca603d6feaf3e2a1634b')
P = G2([
Fp2([Integer('0x267401f3ef554fe74ae131d56a10edf14ae40192654901b4618d2bf7af22e77c2a9b79e407348dbd4aad13ca73b33a'),
Integer('0x12dcca838f46a3e0418e5dd8b978362757a16bfd78f0b77f4a1916ace353938389ae3ea228d0eb5020a0aaa58884aec')]),
Fp2([Integer('0x11799118d2e054aabd9f74c0843fecbdc1c0d56f61c61c5854c2507ae2416e48a6b2cd3bc8bf7495a4d3d8270eafe2b'),
Integer('0x823b9f8fb9f8297734a14359fa2c2a0de275e7e638197eaaaa7cff28f9cb3101bdabb570016672455f1ecae625e294')])
])
subgroup_check(P)
scalarMulEndo(scalar, P)

View File

@ -108,7 +108,20 @@ print(' 𝛼\u03050: 0x' + v[0].hex())
print(' 𝛼\u03051: 0x' + v[1].hex())
print('')
def getGLV2_decomp(scalar):
maxInfNorm = abs(x + 2)
print('\nmax infinity norm:')
print(' ||(a0, a1)||∞ ≤ 0x' + str(maxInfNorm.hex()))
print(' infinity norm bitlength: ' + str(int(maxInfNorm).bit_length()))
# Contrary to Faz2013 paper, we use the max infinity norm
# to properly dimension our recoding instead of ⌈log2 r/m⌉ + 1
# which fails for some inputs
# +1 for signed column
# Optional +1 for handling negative miniscalars
L = int(maxInfNorm).bit_length() + 1
L += 1
def getGLV1_decomp(scalar):
maxLen = (int(r).bit_length() + 1) // 2 + 1
@ -128,7 +141,6 @@ def getGLV2_decomp(scalar):
def recodeScalars(k):
m = 2
L = ((int(r).bit_length() + m-1) // m) + 1 # l = ⌈log2 r/m⌉ + 1
b = [[0] * L, [0] * L]
b[0][L-1] = 0
@ -154,13 +166,11 @@ def pointToString(P):
def scalarMulEndo(scalar, P0):
m = 2
L = ((int(r).bit_length() + m-1) // m) + 1 # l = ⌈log2 r/m⌉ + 1
print('L: ' + str(L))
print('scalar: ' + Integer(scalar).hex())
k0, k1 = getGLV2_decomp(scalar)
k0, k1 = getGLV1_decomp(scalar)
print('k0: ' + k0.hex())
print('k1: ' + k1.hex())

View File

@ -96,6 +96,10 @@ def checkEndo():
checkEndo()
def subgroup_check(P):
ppP = psi2(P)
assert x * psi(ppP) - ppP + P == G2([0,1,0])
# Decomposition generated by LLL-algorithm and Babai rounding
# to solve the Shortest (Basis) Vector Problem
#
@ -141,6 +145,19 @@ print(' 𝛼\u03052: 0x' + v[2].hex())
print(' 𝛼\u03053: 0x' + v[3].hex())
print('')
maxInfNorm = abs(x + 2)
print('\nmax infinity norm:')
print(' ||(a0 , a1 , a2 , a3)||∞ ≤ 0x' + str(maxInfNorm.hex()))
print(' infinity norm bitlength: ' + str(int(maxInfNorm).bit_length()))
# Contrary to Faz2013 paper, we use the max infinity norm
# to properly dimension our recoding instead of ⌈log2 r/m⌉ + 1
# which fails for some inputs
# +1 for signed column
# Optional +1 for handling negative miniscalars
L = int(maxInfNorm).bit_length() + 1
L += 1
lambda1 = (t-1) % r
lambda2 = lambda1^2 % r
lambda3 = lambda1^3 % r
@ -181,8 +198,6 @@ def getGLV2_decomp(scalar):
def recodeScalars(k):
m = 4
L = ((int(r).bit_length() + m-1) // m) + 1 # l = ⌈log2 r/m⌉ + 1
L += 1 # Deal with negative miniscalars
b = [[0] * L, [0] * L, [0] * L, [0] * L]
b[0][L-1] = 0
@ -259,9 +274,8 @@ def printFactors(factors):
def scalarMulEndo(scalar, P0):
m = 4
L = ((int(r).bit_length() + m-1) // m) + 1 # l = ⌈log2 r/m⌉ + 1
L += 1 # Deal with negative miniscalars
print('r bits: ' + str(int(r).bit_length()))
print('L: ' + str(L))
print('scalar: ' + Integer(scalar).hex())
@ -278,6 +292,16 @@ def scalarMulEndo(scalar, P0):
print('decomp: ' + pointToString(decomp))
assert expected == decomp
# Alternative to adding an extra bit
# to deal with miniscalars, unfortunately broken
# for some input
# for example 0x5668a2332db27199dcfb7cbdfca6317c2ff128db26d7df68483e0a095ec8e88f
# which is missing bits for b[2]
# if k0 < 0: k0 = -k0; P0 = -P0
# if k1 < 0: k1 = -k1; P1 = -P1
# if k2 < 0: k2 = -k2; P2 = -P2
# if k3 < 0: k3 = -k3; P3 = -P3
print('------ recode scalar -----------')
even = k0 & 1 == 0
print('was even: ' + str(even))
@ -363,13 +387,29 @@ for i in range(1):
# Integer('0xf5d6d74f1dd3d9c07451340b8f6990fe93a28fe5e176564eb920bf17eb02df8b6f1e626eda5542ff415f89d51943001')])
# ])
scalar = Integer('0x5668a2332db27199dcfb7cbdfca6317c2ff128db26d7df68483e0a095ec8e88f')
# The following input fails in Constantine when negating the base point
# but not when adding an extra bit
# scalar = Integer('0x5668a2332db27199dcfb7cbdfca6317c2ff128db26d7df68483e0a095ec8e88f')
# P = G2([
# Fp2([Integer('0xa8c5649d2df1bae84fd9e8bfcde5113937b3acea22d67ddfedaf1fb8de8c1ef4c70591cf505c24c31e54020c2c510c3'),
# Integer('0xa0553f98229a6a067489c3ee204161c11e96f421b3e9c145dc3865b03e9d4ff6cab14c5b5308ecd31173f954463690c')]),
# Fp2([Integer('0xb29d8dfe18dc41b4826c3a102c1bf8f306cb42433cc36ee38080f47a324c02a678f9daed0a2bc577c18b9865de029f0'),
# Integer('0x558cdabf11e37c5c5e8abd668bbdd71bb3f07f320948ccaac8a207359fffe38424bfd9b1ef1d24b28b2fbb9f76faff1')])
# ])
# The following fails when we have both extra bit and negation of the first
# scalar if it is negative.
# it also uses 65 bits instead of teh expected max of 64
# And triggers an off by 1 when negating
scalar = Integer('0x6448f296d9b1a8d81319a0b789df04c587c6165776ccf39f50a354204aabe0da')
P = G2([
Fp2([Integer('0xa8c5649d2df1bae84fd9e8bfcde5113937b3acea22d67ddfedaf1fb8de8c1ef4c70591cf505c24c31e54020c2c510c3'),
Integer('0xa0553f98229a6a067489c3ee204161c11e96f421b3e9c145dc3865b03e9d4ff6cab14c5b5308ecd31173f954463690c')]),
Fp2([Integer('0xb29d8dfe18dc41b4826c3a102c1bf8f306cb42433cc36ee38080f47a324c02a678f9daed0a2bc577c18b9865de029f0'),
Integer('0x558cdabf11e37c5c5e8abd668bbdd71bb3f07f320948ccaac8a207359fffe38424bfd9b1ef1d24b28b2fbb9f76faff1')])
Fp2([Integer('0x5adc112fb04bf4ca642d5a7d7343ccd6b93546442d2fff5b9d32c15e456d54884cba49dd7f94ce4ddaad4018e55d0f2'),
Integer('0x5d1c5bbf5d7a833dc76ba206bfa99c281fc37941be050e18f8c6d267b2376b3634d8ad6eb951e52a6d096315abd17d6')]),
Fp2([Integer('0x15a959e54981fab9ac3c6f5bfd6fb60a50a916bd43d96a09922a54309b84812736581bfa728670cba864b08b9e391bb9'),
Integer('0xf5d6d74f1dd3d9c07451340b8f6990fe93a28fe5e176564eb920bf17eb02df8b6f1e626eda5542ff415f89d51943001')])
])
subgroup_check(P)
scalarMulEndo(scalar, P)

View File

@ -101,6 +101,19 @@ print(' 𝛼\u03050: 0x' + v[0].hex())
print(' 𝛼\u03051: 0x' + v[1].hex())
print('')
maxInfNorm = abs(6*x^2+6*x+2)
print('\nmax infinity norm:')
print(' ||(a0, a1)||∞ ≤ 0x' + str(maxInfNorm.hex()))
print(' infinity norm bitlength: ' + str(int(maxInfNorm).bit_length()))
# Contrary to Faz2013 paper, we use the max infinity norm
# to properly dimension our recoding instead of ⌈log2 r/m⌉ + 1
# which fails for some inputs
# +1 for signed column
# Optional +1 for handling negative miniscalars
L = int(maxInfNorm).bit_length() + 1
L += 1
def getGLV2_decomp(scalar):
maxLen = (int(r).bit_length() + 1) // 2 + 1
@ -121,7 +134,6 @@ def getGLV2_decomp(scalar):
def recodeScalars(k):
m = 2
L = ((int(r).bit_length() + m-1) // m) + 1 # l = ⌈log2 r/m⌉ + 1
b = [[0] * L, [0] * L]
b[0][L-1] = 0
@ -147,8 +159,6 @@ def pointToString(P):
def scalarMulEndo(scalar, P0):
m = 2
L = ((int(r).bit_length() + m-1) // m) + 1 # l = ⌈log2 r/m⌉ + 1
print('L: ' + str(L))
print('scalar: ' + Integer(scalar).hex())

View File

@ -142,6 +142,19 @@ print(' 𝛼\u03052: 0x' + v[2].hex())
print(' 𝛼\u03053: 0x' + v[3].hex())
print('')
maxInfNorm = abs(5*x + 3)
print('\nmax infinity norm:')
print(' ||(a0 , a1 , a2 , a3)||∞ ≤ 0x' + str(maxInfNorm.hex()))
print(' infinity norm bitlength: ' + str(int(maxInfNorm).bit_length()))
# Contrary to Faz2013 paper, we use the max infinity norm
# to properly dimension our recoding instead of ⌈log2 r/m⌉ + 1
# which fails for some inputs
# +1 for signed column
# Optional +1 for handling negative miniscalars
L = int(maxInfNorm).bit_length() + 1
L += 1
lambda1 = (t-1) % r
lambda2 = lambda1^2 % r
lambda3 = lambda1^3 % r
@ -204,7 +217,6 @@ def getGLV2_decomp(scalar):
def recodeScalars(k):
m = 4
L = ((int(r).bit_length() + m-1) // m) + 1 # l = ⌈log2 r/m⌉ + 1
b = [[0] * L, [0] * L, [0] * L, [0] * L]
b[0][L-1] = 0
@ -280,8 +292,6 @@ def printFactors(factors):
def scalarMulEndo(scalar, P0):
m = 4
L = ((int(r).bit_length() + m-1) // m) + 1 # l = ⌈log2 r/m⌉ + 1
print('L: ' + str(L))
print('scalar: ' + Integer(scalar).hex())

View File

@ -0,0 +1,263 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
# ############################################################
#
# BLS12-377
# Constant-time Square Root
#
# ############################################################
# Parameters
x = 3 * 2^46 * (7 * 13 * 499) + 1
p = (x - 1)^2 * (x^4 - x^2 + 1)//3 + x
r = x^4 - x^2 + 1
t = x + 1
print('x : ' + x.hex())
print('p : ' + p.hex())
print('r : ' + r.hex())
print('t : ' + t.hex())
def modCheck(p, pow):
## Find q mod 2^s != 1
q = p^pow
s = 4
while q % s == 1:
s *= 2
if s > q:
raise ValueError('Uh Oh')
if pow == 1:
print(f'Found: p mod {s} = {q % s}')
else:
print(f'Found: p^{pow} mod {s} = {q % s}')
modCheck(p, 1) # Found: p mod 140737488355328 = 70368744177665
modCheck(p, 2) # Found: p^2 mod 281474976710656 = 140737488355329
# On Fp
# a^((p-70368744177665+140737488355328)/140737488355328)
# would lead to a square root but there would be
# log2(140737488355328)-1 candidates
# which must be checked constant time
def precomp_tonelli_shanks(p):
## Precompute constants for
## constant-time Tonelli Shanks algorithm
## with q = p^pow returns:
## 1. c1, the largest integer such that 2^c1 divides q - 1.
## 2. c2 = (q - 1) / (2^c1) in
## 3. c3 = (c2 - 1) / 2 in
## 4. c4, a non-square value in Fq
## 5. c5 = c4^c2 in Fq
q = p
c1 = 0
c2 = q-1
while c2 & 1 == 0:
c2 >>= 1
c1 += 1
c3 = (c2 - 1) // 2
c4 = 1
while kronecker(c4, q) == 1:
c4 += 1
c5 = GF(p)(c4)^c2
return (c1,c2,c3,c4,c5)
def ccopy(a, b, ctl):
## `b` if `ctl` is true, `a` if false
return int(not(bool(ctl)))*a + int(bool(ctl))*b
def sqrt_tonelli_shanks(x, p):
## Returns z = x² (p^pow)
(c1, c2, c3, c4, c5) = precomp_tonelli_shanks(p)
x = GF(p)(x)
z = x^c3
t = z*z*x
z *= x
b = t
c = c5
for i in range(c1, 1, -1): # c1 ... 2
for j in range(1, i-1): # 1 ... i-2
b *= b
z = ccopy(z, z*c, b != 1)
c *= c
t = ccopy(t, t*c, b != 1)
b = t
return z
# for a in range(2, 30):
# if kronecker(a, p) != 1:
# continue
# # print(f'{a}^(p-1)/2 = ' + str(GF(p)(a)^((p-1)/2)))
# print(f'{a} is a quadratic residue mod p')
# b = sqrt_tonelli_shanks(a, p)
# # print(f'{b}² = {a} mod p')
# # print('b*b = ' + str(b*b))
# assert b*b == a
# Optimized Tonelli Shanks
# --------------------------------------------------------
# Finite fields
Fp = GF(p)
K2.<u> = PolynomialRing(Fp)
Fp2.<beta> = Fp.extension(u^2+5)
def precomp_ts(Fq):
## From q = p^m with p the prime characteristic of the field Fp^m
##
## Returns (s, e) such as
## q == s * 2^e + 1
s = Fq.order() - 1
e = 0
while s & 1 == 0:
s >>= 1
e += 1
return s, e
def find_any_qnr(Fq):
## Find a quadratic Non-Residue
## in GF(p^m)
qnr = Fq(Fq.gen())
r = Fq.order()
while qnr.is_square():
qnr += 1
return qnr
def sqrt_exponent_precomp(Fq, e):
## Returns precomputation a^((q-1-2^e)/(2*2^e))
##
## With 2^e the largest power of 2 that divides q-1
##
## For all sqrt related functions
## - legendre symbol
## - SQRT
## - inverse SQRT
r = Fq.order()
precomp = (r - 1) >> e # (q-1) / 2^e
precomp = (precomp - 1) >> 1 # ((q-1) / 2^e) - 1) / 2 = (q-1-2^e)/2^e / 2
return precomp
s, e = precomp_ts(Fp)
qnr = find_any_qnr(Fp)
root_unity = qnr^s
exponent = sqrt_exponent_precomp(Fp, e)
# print('tonelli s: 0x' + Integer(s).hex())
print('tonelli e (2-adicity): ' + str(e))
print('tonelli root: 0x' + Integer(root_unity).hex())
print('tonelli exponent: 0x' + Integer(exponent).hex())
def legendre_symbol_impl(a, e, a_pre_exp):
## Legendre symbol χ(a) = a^(q-1)/2
## -1 if a is non-square
## 0 if a is 0
## 1 if a is square
##
## a_pre_exp = a^((q-1-2^e)/(2*2^e))
## with
## s and e, precomputed values
## such as q == s * 2^e + 1
##
## a_pre_exp is used in square root
## and or inverse square root computation
##
## for fused operations
r = a_pre_exp * a_pre_exp # a^((q-1-2^e)/2^e) = a^((q-1)/2^e - 1)
r *= a # a^((q-1)/2^e)
for i in range(0, e-1):
r *= r # a^((q-1)/2)
return r
def legendre_symbol(a):
a_pre_exp = a^exponent
return legendre_symbol_impl(a, e, a_pre_exp)
for a in range(20):
assert kronecker(a, p) == legendre_symbol(GF(p)(a))
def sqrt_tonelli_shanks_impl(a, a_pre_exp, s, e, root_of_unity):
## Square root for any `a` in a field of prime characteristic p
##
## a_pre_exp = a^((q-1-2^e)/(2*2^e))
## with
## s and e, precomputed values
## such as q == s * 2^e + 1
z = a_pre_exp
t = z*z*a
r = z * a
b = t
root = root_of_unity
for i in range(e, 1, -1): # e .. 2
for j in range(1, i-1): # 1 .. i-2
b *= b
doCopy = b != 1
r = ccopy(r, r * root, doCopy)
root *= root
t = ccopy(t, t * root, doCopy)
b = t
return r
def sqrt_tonelli_shanks_opt(a):
a_pre_exp = a^exponent
return sqrt_tonelli_shanks_impl(a, a_pre_exp, s, e, root_unity)
# for a in range(2, 30):
# if kronecker(a, p) != 1:
# continue
# # print(f'{a}^(p-1)/2 = ' + str(GF(p)(a)^((p-1)/2)))
# print(f'{a} is a quadratic residue mod p')
# b = sqrt_tonelli_shanks_opt(GF(p)(a))
# # print(f'{b}² = {a} mod p')
# # print('b*b = ' + str(b*b))
# assert b*b == a
def sqrt_inv_sqrt_tonelli_shanks_impl(a, a_pre_exp, s, e, root_of_unity):
## Square root and inverse square root for any `a` in a field of prime characteristic p
##
## a_pre_exp = a^((q-1-2^e)/(2*2^e))
## with
## s and e, precomputed values
## such as q == s * 2^e + 1
# Implementation
# 1/√a * a = √a
# Notice that in Tonelli Shanks, the result `r` is bootstrapped by "z*a"
# We bootstrap it instead by just z to get invsqrt for free
z = a_pre_exp
t = z*z*a
r = z
b = t
root = root_of_unity
for i in range(e, 1, -1): # e .. 2
for j in range(1, i-1): # 1 .. i-2
b *= b
doCopy = b != 1
r = ccopy(r, r * root, doCopy)
root *= root
t = ccopy(t, t * root, doCopy)
b = t
return r*a, r
def sqrt_invsqrt_tonelli_shanks_opt(a):
a_pre_exp = a^exponent
return sqrt_inv_sqrt_tonelli_shanks_impl(a, a_pre_exp, s, e, root_unity)
for a in range(2, 30):
if kronecker(a, p) != 1:
continue
# print(f'{a}^(p-1)/2 = ' + str(GF(p)(a)^((p-1)/2)))
print(f'{a} is a quadratic residue mod p')
b, invb = sqrt_invsqrt_tonelli_shanks_opt(GF(p)(a))
# print(f'{b}² = {a} mod p')
# print('b*b = ' + str(b*b))
assert b*b == a
assert invb*a == b

109
sage/testgen_bls12_377.sage Normal file
View File

@ -0,0 +1,109 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
# ############################################################
#
# BN254 test generator
#
# ############################################################
# Parameters
x = 3 * 2^46 * (7 * 13 * 499) + 1
p = (x - 1)^2 * (x^4 - x^2 + 1)//3 + x
r = x^4 - x^2 + 1
# Finite fields
Fp = GF(p)
K2.<u> = PolynomialRing(Fp)
Fp2.<beta> = Fp.extension(u^2+5)
# Curves
b = 1
SNR = Fp2([0, 1])
G1 = EllipticCurve(Fp, [0, b])
G2 = EllipticCurve(Fp2, [0, b/SNR])
# https://crypto.stackexchange.com/questions/64064/order-of-twisted-curve-in-pairings
# https://math.stackexchange.com/questions/144194/how-to-find-the-order-of-elliptic-curve-over-finite-field-extension
cofactorG1 = G1.order() // r
cofactorG2 = G2.order() // r
print('')
print('cofactor G1: ' + cofactorG1.hex())
print('cofactor G2: ' + cofactorG2.hex())
print('')
def clearCofactorG1(P):
return cofactorG1 * P
def clearCofactorG2(P):
return cofactorG2 * P
# Test generator
set_random_seed(1337)
print('=========================================')
print('G1 vectors: ')
for i in range(10):
print(f'--- test {i} ------------------------------')
Prand = G1.random_point()
P = clearCofactorG1(Prand)
(Px, Py, Pz) = P
print('Px: ' + Integer(Px).hex())
print('Py: ' + Integer(Py).hex())
# print('Pz: ' + Integer(Pz).hex())
exponent = randrange(r) # Pick an integer below curve order
print('scalar: ' + Integer(exponent).hex())
Q = exponent * P
(Qx, Qy, Qz) = Q
print('Qx: ' + Integer(Qx).hex())
print('Qy: ' + Integer(Qy).hex())
# print('Qz: ' + Integer(Qz).hex())
print('=========================================')
print('G2 vectors: ')
for i in range(10):
print(f'--- test {i} ------------------------------')
Prand = G2.random_point()
P = clearCofactorG2(Prand)
(Px, Py, Pz) = P
vPx = vector(Px)
vPy = vector(Py)
# Pz = vector(Pz)
print('Px: ' + Integer(vPx[0]).hex() + ' + β * ' + Integer(vPx[1]).hex())
print('Py: ' + Integer(vPy[0]).hex() + ' + β * ' + Integer(vPy[1]).hex())
exponent = randrange(r) # Pick an integer below curve order
print('scalar: ' + Integer(exponent).hex())
Q = exponent * P
(Qx, Qy, Qz) = Q
Qx = vector(Qx)
Qy = vector(Qy)
print('Qx: ' + Integer(Qx[0]).hex() + ' + β * ' + Integer(Qx[1]).hex())
print('Qy: ' + Integer(Qy[0]).hex() + ' + β * ' + Integer(Qy[1]).hex())
print('=========================================')
# CurveOrder sanity check
#
# P = G1.random_point()
# (Px, Py, Pz) = P
# print('Px: ' + Integer(Px).hex())
# print('Py: ' + Integer(Py).hex())
# print('Pz: ' + Integer(Pz).hex())
#
# print('order: ' + Integer(r).hex())
#
# Q = (r * cofactor) * P
# (Qx, Qy, Qz) = Q
# print('Qx: ' + Integer(Qx).hex())
# print('Qy: ' + Integer(Qy).hex())
# print('Qz: ' + Integer(Qz).hex())

View File

@ -0,0 +1,331 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
# Standard library
std/[unittest, times],
# Internals
../constantine/config/[common, curves],
../constantine/arithmetic,
../constantine/towers,
../constantine/io/[io_bigints, io_ec],
../constantine/elliptic/[ec_weierstrass_projective, ec_scalar_mul, ec_endomorphism_accel],
# Test utilities
./support/ec_reference_scalar_mult
echo "\n------------------------------------------------------\n"
proc test(
id: int,
EC: typedesc[ECP_SWei_Proj],
Px, Py: string,
scalar: string,
Qx, Qy: string
) =
test "test " & $id:
var P: EC
let pOK = P.fromHex(Px, Py)
doAssert pOK
var Q: EC
let qOK = Q.fromHex(Qx, Qy)
let exponent = BigInt[EC.F.C.getCurveOrderBitwidth()].fromHex(scalar)
var
impl = P
reference = P
endo = P
endoW = P
impl.scalarMulGeneric(exponent)
reference.unsafe_ECmul_double_add(exponent)
endo.scalarMulEndo(exponent)
endoW.scalarMulGLV_m2w2(exponent)
doAssert: bool(Q == reference)
doAssert: bool(Q == impl)
doAssert: bool(Q == endo)
doAssert: bool(Q == endoW)
suite "Scalar Multiplication (cofactor cleared): BLS12_377 implementation vs SageMath" & " [" & $WordBitwidth & "-bit mode]":
# Generated via sage sage/testgen_bls12_377.sage
test(
id = 0,
EC = ECP_SWei_Proj[Fp[BLS12_377]],
Px = "4e7e6dfa01ed0ceb6e66708b07cb5c6cd30a42eeb13d7b76f8d103a0a4d491450be6f526fc12f15209b792220c041e",
Py = "c782515159b7e7b9371e0e0caa387951317e993b1625d91869d4346621058a0960ef1b8b6eabb33cd5719694908a05",
scalar = "cf815cb4d44d3d691b7c82a40b4b70caa9b0e8fe9586648abf3f1e2e639ca1b",
Qx = "4a1203db4af8f0efc18c7ceb24999eb6e0dbdfc8f44a9edd5ba2f9eced38e81ecae287ab1c184eea8e8753d1178604",
Qy = "f7509cdce5de473e37f36c69d93ff1b2ab04c3ae25a3b5e41f2c138cdcc69ed5eaf4ac95a7a857eef336ebac19bcd3"
)
test(
id = 1,
EC = ECP_SWei_Proj[Fp[BLS12_377]],
Px = "13d735b28405253dcc0bc60bcdc13633475ffc187d38a9b97655b0d0fa1d56c4548f11ea0a795391ee85c953aaf9b83",
Py = "1693101123fd13a20f9c0569c52c29507ba1c8b6dd412660bc82e7974022f1a10f9137b4ba59d3f0aab67027cefec19",
scalar = "913aa7b9fa2f940b70b6dcf538cc08da1a369809ab86a8ee49cead0ed6bfef6",
Qx = "10e0e8582ec3456f7569473892c23997d004f2542d914fa75db8f1798ed8ce505836e8b7af5cf1503e14d85fadd65ee",
Qy = "d5151ae60e43100e20aad7eaf8659e690e8034910d7717078031520fcbf6f9a00b22c6a9894aec88c9182f13335639"
)
test(
id = 2,
EC = ECP_SWei_Proj[Fp[BLS12_377]],
Px = "6dd2211113cada09d3b96848cbf21ed14f6fc238b581c0afd49aa776980101e4ee279a256ce8f1428d3ed3f70afd85",
Py = "3b406b4433a3f44f8e196012f50c520e876412fbcae2651916f133c1fd3899c79f676e1abba01d84bab7ad100c9295",
scalar = "4cf47669aeb0f30b6c6c5aa02808a87dc787fba22da32875e614a54a50b6a0c",
Qx = "3caf55e1f82d746df2bf47defbee127a3e6f7e79a9575929704b25489ffb801dbce07999acbddfd79352b0633a2708",
Qy = "3fc275c136b07456f0b6ee814508876037add5e95357fae6b4195744ba5ccccaf93581e34feb2babe652a9a704731b"
)
test(
id = 3,
EC = ECP_SWei_Proj[Fp[BLS12_377]],
Px = "18648abbe3261f93cbf679777eac66e419035041a967d1e6a0f0afdb92810d7122e0984f1d6efc8fe518500464ee803",
Py = "7b6f518d4d06309aad4d60d29118310b6c8b17c7bf5db2251f4701b13b89a8c0f04bb5d0386785e55ffbbd7ecc445e",
scalar = "69498486a06c18f836a8e9ed507bbb563d6d03545e03e08f628e8fbd2e5d098",
Qx = "6a871d872673879fd23ec8c150f8d63e8130dc60343fc0c2ec9ff1b02e769e20eeec0288102ccec6ff2f6ac6973b4",
Qy = "cd95a31afa4f0bbcc063a1585fec4a6c51812b2baaee42a04525fd55b3a9db3bdb7cbc77f7cd32f42d2e0eb26ddfa2"
)
test(
id = 4,
EC = ECP_SWei_Proj[Fp[BLS12_377]],
Px = "745e9944549522486f3446676cc62fb666682e10c661a13b8110b42cb9a37676b6f33a46f9495f4fafb342d5809db5",
Py = "bc595854bc42ccec60dd9ec573608d736aa59996cef1c2e8f6c5d424f525a6f3e3d4beeedfac6b959dbd71ced95b13",
scalar = "6e08d8714102a5aa3e9f46e33c70a759c27253c7b0196c3e46c7cb42671197e",
Qx = "18a967a80785de8ec6ac9d98cffa06a8c633b5fa0f36431a32f7bd955946edc3d55f79bfdf5335db405560a6cbe2415",
Qy = "4552ff2eb6ade0c6b33c3603460d9d62099201d842c9883b33b7ed1147cb17268338a77a9417776ddbd774e91a8d2"
)
test(
id = 5,
EC = ECP_SWei_Proj[Fp[BLS12_377]],
Px = "f7b56cf212a8906ed77a758164c0bd05ce1fbd3ee3c4357e7a09b3aedc748a29ace254f1f6df35b8cb74361060337f",
Py = "2640ef641d20fea19b28947833e53faceeafa57a8761b807049f3d707d70c01f1c69a57edd993d301a64517bf47f77",
scalar = "a5c46d7a52938fed7f3093d5867f65361dc8b48c83bd7db490c26736196e20e",
Qx = "434f024c4afd7b9a44375011d663af0ae0fe79442e9caf36518e053bb13d49998ec1d2da4bc1c4a812812119b3221f",
Qy = "32c959e2a678cf93f77e621fdf887454a5f9cb7a67f29065669f48234c25e321ceb5758dfba987431a0c2caec94616"
)
test(
id = 6,
EC = ECP_SWei_Proj[Fp[BLS12_377]],
Px = "43b387c12e4cc91fb1a5593b7671356dceb0fe6e6ac666d5bac94f2a44c8db54976b649a678aae038b21144de04e23",
Py = "1a6366a50f1f9ba64eef73d82bec86177bf184be048a9d66326ccb0122203569ddcb8cf74445cadaff7f47a66d1b1a2",
scalar = "bddd07231bc7fe89ee4a859a00ea1f9d236be9e7fd561303d566904c1b0a07c",
Qx = "8ddb7a959483b51a1471de988146b7d5b166f660734b4d55166c5a23d781e923261927b1012dce73b822bb6e56bfd2",
Qy = "dc7178bf5597ef31209a5b0409fc42c64b81260a0046a562ca08c7a9dc67b444c25e94fb97b9a6bb4c137cadd81021"
)
test(
id = 7,
EC = ECP_SWei_Proj[Fp[BLS12_377]],
Px = "6984c9385a67081f97b3d33444077466cca1d0442a4da8c083a0957578e0b21011435b126a5ab143da9da1cf5b216f",
Py = "18a87c7f5f6c5a8101773e63956b9addd4becf5177acc560d548e5331638121934842fdc9f654b3f456a7df5a2e471a",
scalar = "b72c41a6ffaff0aacb5d62e3dcb16acfec66b6a9639e19d3128fd43c18e7dbe",
Qx = "19279201c3c7a9d50b546aa99d3e1a6625fe2a7bc64a09625b683534638b9e87b9102d4dba6684956b6be7668a658c6",
Qy = "195f15ad1edc05f8b289c7eee7bd8f78116a2d5ba8b83643d9e7cb2cdc6550bcf8c2145008e900ca9cba4d5040e9f4"
)
test(
id = 8,
EC = ECP_SWei_Proj[Fp[BLS12_377]],
Px = "efc34595823e7333616f7768bc82f407268df6f029bf4c94d15f1785dc7ccc08f22e7301dfc48dadc0ea383c8bb3e",
Py = "2459bd9f71977ef122d2102e8bfd07a5737066075058cfa8bcaa9f9690ed065919c844363ceaea6f9bb650906a535f",
scalar = "8f90f6ab0ffa6e4acc601b44c062745f2935b3dc153d0da07977470080d5c18",
Qx = "35d08b33e02579581905941975c8b1cc5be1c9670a7f7ef390daa363b0abd3571a802d8c27f156fba40573094f6c7a",
Qy = "111ba3bdfa4260dc2b636479edb1fcf2fc9478aa722da0118908e1db1551cf7e131c8521b2f3708ef670684dbe8d181"
)
test(
id = 9,
EC = ECP_SWei_Proj[Fp[BLS12_377]],
Px = "3eec93c2a9c5fd03f0de5ede2fdac9e361090fbaea38e4a0f1828745f1d14a057d9fd7c46b9168bd95a45a182a3a62",
Py = "e912dc7e95f90d91e3274ec5639edacb88be1b092c47c13d31a29ecd579885cc09f197f8207d23b2260ab10c94d5f5",
scalar = "203300e949aff816d084a388f07c74b9152bb11b523543afd65c805a389980",
Qx = "8c23ae9c51e7c92c6a59f0ed07a59f148b4d79394fc026931c264612041eedd3782e4f249bbfad1799212788c1a00d",
Qy = "191628b702ebef397bb7c52102ee4522f864ff594b24dc385ece081e8066bcde20aa5c7dfd00fb1f3cea221b3ad4ea2"
)
proc test(
id: int,
EC: typedesc[ECP_SWei_Proj],
Px0, Px1, Py0, Py1: string,
scalar: string,
Qx0, Qx1, Qy0, Qy1: string
) =
test "test " & $id:
var P: EC
let pOK = P.fromHex(Px0, Px1, Py0, Py1)
doAssert pOK
var Q: EC
let qOK = Q.fromHex(Qx0, Qx1, Qy0, Qy1)
let exponent = BigInt[EC.F.C.getCurveOrderBitwidth()].fromHex(scalar)
var
impl = P
reference = P
endo = P
impl.scalarMulGeneric(exponent)
reference.unsafe_ECmul_double_add(exponent)
endo.scalarMulEndo(exponent)
doAssert: bool(Q == reference)
doAssert: bool(Q == impl)
doAssert: bool(Q == endo)
suite "Scalar Multiplication G2: BLS12-381 implementation vs SageMath" & " [" & $WordBitwidth & "-bit mode]":
# Generated via sage sage/testgen_bls12_377.sage
test(
id = 0,
EC = ECP_SWei_Proj[Fp2[BLS12_377]],
Px0 = "267401f3ef554fe74ae131d56a10edf14ae40192654901b4618d2bf7af22e77c2a9b79e407348dbd4aad13ca73b33a",
Px1 = "12dcca838f46a3e0418e5dd8b978362757a16bfd78f0b77f4a1916ace353938389ae3ea228d0eb5020a0aaa58884aec",
Py0 = "11799118d2e054aabd9f74c0843fecbdc1c0d56f61c61c5854c2507ae2416e48a6b2cd3bc8bf7495a4d3d8270eafe2b",
Py1 = "823b9f8fb9f8297734a14359fa2c2a0de275e7e638197eaaaa7cff28f9cb3101bdabb570016672455f1ecae625e294",
scalar = "9d432eb58ec68bbc09d10961451d99c7796fb2f795eca603d6feaf3e2a1634b",
Qx0 = "12cfcb50345d43271d2a20e8208789c8ca82f2b8732fae4e7cccd87eb0883741d5e77166971c38c54170bf7635ca2f3",
Qx1 = "17467c786368f2f6eabd78f1c24aa668eae9cd00f6045bfd86b1b3c40966f023a71927026ae5a1281b432ac980d2b6b",
Qy0 = "6f5f1068b6e6a8fafd96894d8b0a8548acee24319d8e2d8b6e6982b1ced8970d9fe33155e74b33d6c2a7835196ca9",
Qy1 = "dc3c7c91c712f9a2631d2b5e49497d8cdf2ea4b30859b43edd716e84cd6a8b61e63cf708d263a7845913cdfb9c7f3b"
)
test(
id = 1,
EC = ECP_SWei_Proj[Fp2[BLS12_377]],
Px0 = "3a3055d6a46901c1b2227a0e334ffa9f654e62a6d3608f3a672e5816f9e9b04c0e668c3e9f8c807b269422afdc7de9",
Px1 = "25803bd55f37b254865d5fc7ac9843fb306c2eb09d34ee0c4ecb705b5e10f6911f07fd707a2e28681a421f45b1a4d",
Py0 = "15a0594a3c9dddc535472c4827aa443774a06f77bec2d20837c6574aa5fac35a279bac756531fa75f979a7a97f297d6",
Py1 = "15e8c3e013cbc64110f075295f39a4f85c9591a52b8e4903047b1f4b44bb5216b6339788c82fd90e82b1027756e7987",
scalar = "a0067f5b4294fbacd24730fed25c936a08b5f1a77824149ad6c2ce476518d17",
Qx0 = "6280dbf019f8d94acac4a39213e03f221be732849b45437edef7617460e83f972883c20791a86670a8a4c6c0125629",
Qx1 = "ab3a185addbd6c7db907170d75bd2f1997666e93d8cedbef9e10a103110d4ea09d9a889f505d7026e5c498b0355c1",
Qy0 = "1a6dd15b4c2bd6bd97858e5a75ac4365f8becaa885c4e3267d0ae256655cd057061d61b2b9acbd10bdf678a9c1e7f8c",
Qy1 = "16204b5825eec903cb7798cb62ccadb9004032d72bb815958fcd2f613c77e147656449fdaa210994338978676ad6b0e"
)
test(
id = 2,
EC = ECP_SWei_Proj[Fp2[BLS12_377]],
Px0 = "18ebf549cd24a64badff4ec177205be121757b68589020434aba816756d2e6fa95fdb389c8639d4bf77575122e1c04e",
Px1 = "11cd0f0976658a6f2ec113034428ef1605befdaa5642944f5c4e571b24fc166c368c30473e25ab148209be4c0b4e37",
Py0 = "1190d818317495201732feb2cfc8f507adac6273debff46bb6aea4a3f3e3fe7d28d893c90b21a6f28d2fbc72d9fc528",
Py1 = "df5fcb2daa302a5c64aeef96835e0a6b39f5d7bf0e70cc10401f966745a6b3fa682b7e5b45d9295e744e1dd7855fd4",
scalar = "7a49802ba58c87c30b631b2f90a3b876c7143e09b542c9c14706bddf9bd4117",
Qx0 = "6cdff80576d8695a646f915caba5bc6748eee1707bb0d4ebcabaf8d236b780aef6a953a07f48ef4696f02db4906c71",
Qx1 = "6b64ca5f0d46702e7e9e7beb1993b698c9b3e9991f545241d5fd44a27dc692d5f5a4c2fe6871af0653128f960307d4",
Qy0 = "cfad02b664495d42d1c6b598306229c67bcf76cc923abb13ae038c90e959a7611161a98d607729577d55ec18bf1e74",
Qy1 = "e433d9dd7d47d94e9f61a5bc27a688c63efa05408d6fd40a5c4e2711a37d011dc80f5dbaaafd939a07235c770feead"
)
test(
id = 3,
EC = ECP_SWei_Proj[Fp2[BLS12_377]],
Px0 = "52b54c30c1dcadbcb698d0cb7fd65de1eb8f7590c7afe46e019bdc9e0ef8bc4c060339220d3615e4b1f2b12ffa6d83",
Px1 = "dd53b483c2ab1aaa7ed22ef619b5e979237ae95476436f2c51c8b70da39a4e54a989f10f6d12ee098154911aa052f6",
Py0 = "2a8c96662a7c76cb7d8ca6571a5b99abfd2d7343dd668425e5fa4a8c880b313b70f09a15a825fac63ae7065c0c51d",
Py1 = "7bd93bb9d5bfdd15636a34a13385e9abbc1088ecc02c86cbc733122ccbbb657ec8d75e47102ce46d35a9612c01fc3d",
scalar = "e0c564e69ad68343fd4ec3d4dafdf6a92f44c13fc70a9aad95d10b2c96ee747",
Qx0 = "126f6039b7031b73a7f926bd443ace416f62e56f03864d660bbe1f1a2a994672d408f9936293f367d3fb5d8e275ad40",
Qx1 = "1654a2c26479ace1eb68e279eceac6aa10680549b09f42d288767f6296c98ba4299fa6df37a946b88823a5deebc231c",
Qy0 = "23026839059191f71e2abcf582234aa2ce4b4225c3d503e8fc6c119df1168192e894f33ed48bc571e5527365dd92b5",
Qy1 = "11f8fcce53f5e3211290e6c72736eb1c9d2e159dd243f3e493c430f0411864e109af09a3b9379c4b332815e012caa80"
)
test(
id = 4,
EC = ECP_SWei_Proj[Fp2[BLS12_377]],
Px0 = "fb6dd822e327a60c9e55d760c4f5d26783617a06f868835c2f46e902108eaca5d60fd64c0a0c8a6dc12f9352dfef33",
Px1 = "85e97eef3b4e42a93b8421644e9334f4543a25a36ccd41d448c385146baf3b1efbaeac49c202b04cdbf8b50f3fd962",
Py0 = "8db78b315a524f17ac3d69333604e6bc8aa0b2f138f9adf7edb19f49c847eda6b64df9fe2576b7687e7b55cb5f0bd0",
Py1 = "d4a0c7e5e7aaacab0e0e60c6c49624971a161de4f0daa7968f80998fb7b4761b1196964b26fefa9337fd133784e3d9",
scalar = "1abce9e9c079c686864304d479d68087db33f9edb3f7b2e4655fe0c1da4993f",
Qx0 = "1238814ea497bc1b65ba1c0fbfc394427b10b2d8783bf6a04e01505bc165b577cb2366c19c24c555235ff9b16e7233a",
Qx1 = "b156205cad5d2d97d24c45fe218cd412f47248f26d579fb368af5ba2d80b8c95e2a68f4c84540a8b78b2f0a33a48a4",
Qy0 = "12154ce4a569b39ccfe464a7559b8baf03d3fb6462e67dc0c4ae79a3c33b7756e0c558eff64f9558ebd6500086684f8",
Qy1 = "74571a18fc091f4d77cc7d363bf7a34ab4d0dca86c160b261b383e416f68fa553d7b8b8f6317c8e340ce454daffb5f"
)
test(
id = 5,
EC = ECP_SWei_Proj[Fp2[BLS12_377]],
Px0 = "b56e23f2558c3c124f024952e1858067223541f51a8885d161133beb7bf8d64f22163769e604afbf7fbfb02e1fcb43",
Px1 = "e10c4366d667b40488a9ae32daf3e2a5cc8ddf25ed407afe1a38b855f3ac4f7ea20455924e71369eed07114613b633",
Py0 = "f42d3ab4716d10384bcdfec38cba997c2bafb8d8de32a47225a3e2d2835be1ad02a63323d3dd4145db21e3cbf5cc7a",
Py1 = "15f48a901f3dfd3b812a455f1297e8311c4608869ea02d3d16e9aafdf610d7e72f34b02830de8cc0d0f4e909af5827d",
scalar = "401e70df8ccaa504da72b1d649c067f3b752156dcea5b48dcb601710ab0baa3",
Qx0 = "949099ea1ece1be6fa3f3537e8ba39587b40003fb409a4beb831bb1d9d999b7c621b9a1ced3223f710e0bd05f4a018",
Qx1 = "cdfc0d47788bca3672126e0facc05c19a4fb24a43eee32c8e08a6e5152f6af1d7c2efa48907046f90a721ed28fcf7",
Qy0 = "11d608a01cfe1a40806b9951f85a7bb1b6df6e4c4e2c7c50c17e8fad6ed397a430aff9b4742408367d6536fe503692a",
Qy1 = "19c25d8a782b9d8d9f067d345fd03afe50439c82f99a06abca790f2fae944677d8095d2e276fcb7a75cfb2cbccf89d7"
)
test(
id = 6,
EC = ECP_SWei_Proj[Fp2[BLS12_377]],
Px0 = "137001541ab8a479362ee39f818fc38788231e5288ceea5ebe0c08d0bbca3be9519aa415bde98428df26d361b7311ea",
Px1 = "411b1a4f0abb9fd255a7ae26ac39c1f2c88a48f82c7623b6f225aec9755206e27084b23cbc98f31399405a6599dc54",
Py0 = "833ef097986116cab669c4fddff9b831535d100644f732fb0da0a2dce17d69beaeed67230dd66392e840679afbae1e",
Py1 = "f63e809bd55617c31570d686dbc9b94b3cb96c154f9983181cb913a0671c0c16143332087e44a0283ebf01eea6d73b",
scalar = "10697220c755c800861d377c55b22ae48c25dc144e1fa7a1e5bbdf82993aa33c",
Qx0 = "10fc7772bb2bc946e3ff659c01a0452ebf5dd1472bd1aa71e198597fd05361bf0d173817f91fcb8a1b96b46c35d2675",
Qx1 = "d7b00ddf32f9d8e549c05dd605931ef737d8a4bc62795f73044482957b8093b1cdad0f55c7d4edf6f59594a13f9b67",
Qy0 = "148756a592dec0bf2fda41ac219c4a891ef427665ba1cf47b58bed4996cf8937fb07929c041f10e8a55b2238944fd30",
Qy1 = "e4a240f5c32dab76575ace4d035cbd1e2b87d7585fee7bfde9c88e918a7ad2cd8eb4982acfacfac58c33c8a54ddac7"
)
test(
id = 7,
EC = ECP_SWei_Proj[Fp2[BLS12_377]],
Px0 = "1580ffccc21b057dfe4831f950cbb8f1436f999df657404ecec20225a929a5d56920e8662abc426de23402643087308",
Px1 = "8bf8ff20713a9054aa1cce9635eb3d6ac8371fc052b747a8414595708ff9462d64a0a11ff2c1c5121f4ecc5f22df5e",
Py0 = "e7a5f7df0cec03de25da415fdda485ecc1443b95352e47b23e5506b820c9bc9ea9c2d9e99dd48e1f74e1befe58ce80",
Py1 = "12b5638a0f7e508016fddd2dae2d84736efa59c53f2a5d979b9a29fc7c9c406407bb5f5788bcea3529ac1a763c72e8b",
scalar = "bd936c7066316d3f86b077419a0c0fb9867d4612208241fc004b548f1f54fad",
Qx0 = "69e3411725062858ca7e5fc4d648037baf10e34ba6830ff0090e7188932112b9458c4f4b82f2f0f06c6501fcdaa86c",
Qx1 = "127b75c55c90a872c1d749d05609d2499065ac3c9eb96bd6a9816bd7a2315795ddf40944baef1907676395df306860e",
Qy0 = "13fd0e314159909d3091e382017b35ec21ee2004e9082cd24338817046ed2202ffed52e7714cf436b953f95f49f9856",
Qy1 = "5a8ccdf4a1600fafcc86c646d83199c41cbfebf7a1fa2b1cb9c408352be46a33d0f411b175348f42382d170e17ec2b"
)
test(
id = 8,
EC = ECP_SWei_Proj[Fp2[BLS12_377]],
Px0 = "10898a074b6931cc4ada152b64dd1c6b2bb47912e6502b9e88f638e489c144f8aa9b9f915bb428e082ec84676607f40",
Px1 = "18484823648fe1699468e2d265cd2f2e381a0e67f35f8d192259e2a14573692b4e1fbdeed639e9b6eb0731be820b166",
Py0 = "1840bc6fcb224efe00e32f827fa4f9694cd4186493089c66a936e912b50346b75542b6edbe51ba95c88d3b0fcac34ed",
Py1 = "18cb4201510933776a28ff1c44d356ceab065880f5242d8cc7cdf86874568df679b20f34b6a216d0047d2e1c1a16b85",
scalar = "bdb46c0eece9c8bac8a382def90b522b1d5197f09cf9f7cd98b710845ddb7b4",
Qx0 = "102779766b0068b176e118348e4163e89e3171e6eaad0e47f00cad626f8db025ec3ea990363813882c3dcfcbb642240",
Qx1 = "131eb563d2b0145eee52ee7586acd0254f103f02a20de3bf0202d1cffec2c3628501ec20f9f2ae5f461b59604afc04c",
Qy0 = "1463527db149d2c76f30c082786edb3fe1a9c7f4e04f9496e7adb40aab89340d9792f7d75cf3f5f81b0a28f09625175",
Qy1 = "f3190261db082c207ccd88a3ba762d2c0ba330548444afca03c8535ff11c7c90659a867dda3712a83f02ad184eb24a"
)
test(
id = 9,
EC = ECP_SWei_Proj[Fp2[BLS12_377]],
Px0 = "13c3a392307124afb5f219ba0f8062fa9b75654d3fff12bc924592d284af550f039b6ac58880d2c6fea146b3982f03c",
Px1 = "188169bc937fcc20cc9c289adef30580188f64ecb126faadb5b888f31b813727ff7046d1a19b81abeea6609b8b208c6",
Py0 = "2f9bde8fdd43c5f4de30f335a480dca3bf0858464d8368984406f10ddc1ecabb15fcfd11cebd4fef426e7ca9411221",
Py1 = "25505653e199c354f2da8a13ed9de47f9b51a06ad2fa8826c4b8a9c61c6e75268807d7053e06bfc2899d1a3d6deeb4",
scalar = "91c6249ee16ef94d0b905575982419a7cf31d125775e7ede5c0ab4f86defd33",
Qx0 = "1967547943b197311a7d6723dcc37c3e649f173cec1c88ffca27df86518b3392ae0ee13dbca375c928b94d129226852",
Qx1 = "18b793916195f2d7d2a47c3f8243e5bdc239e78a3eb26971dac01b5f4ce14081a06529ab66cda8e8d5537808223fd00",
Qy0 = "a784902a14ad39adcfdc52bc30d8b8711b93dd869af2375a1f408ba8610b5c558bbebfe9f8f9875954780f4b4262c0",
Qy1 = "fb1c766957c89e4d67747549f650070983e6c19d0208b7e65478e4dd4f72fc54fca450d96329182e3f00d16ae0f3a"
)

View File

@ -32,3 +32,9 @@ run_EC_addition_tests(
Iters = Iters,
moduleName = "test_ec_weierstrass_projective_g1_add_double_" & $BLS12_381
)
run_EC_addition_tests(
ec = ECP_SWei_Proj[Fp[BLS12_377]],
Iters = Iters,
moduleName = "test_ec_weierstrass_projective_g1_add_double_" & $BLS12_377
)

View File

@ -28,3 +28,9 @@ run_EC_mixed_add_impl(
Iters = Iters,
moduleName = "test_ec_weierstrass_projective_mixed_add_" & $BLS12_381
)
run_EC_mixed_add_impl(
ec = ECP_SWei_Proj[Fp[BLS12_377]],
Iters = Iters,
moduleName = "test_ec_weierstrass_projective_mixed_add_" & $BLS12_377
)

View File

@ -34,3 +34,9 @@ run_EC_mul_distributive_tests(
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g1_mul_distributive_" & $BLS12_381
)
run_EC_mul_distributive_tests(
ec = ECP_SWei_Proj[Fp[BLS12_377]],
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g1_mul_distributive_" & $BLS12_377
)

View File

@ -78,3 +78,9 @@ run_EC_mul_sanity_tests(
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g1_mul_sanity_" & $BLS12_381
)
run_EC_mul_sanity_tests(
ec = ECP_SWei_Proj[Fp[BLS12_377]],
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g1_mul_sanity_" & $BLS12_377
)

View File

@ -34,3 +34,9 @@ run_EC_mul_vs_ref_impl(
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g1_mul_vs_ref_" & $BLS12_381
)
run_EC_mul_vs_ref_impl(
ec = ECP_SWei_Proj[Fp[BLS12_377]],
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g1_mul_vs_ref_" & $BLS12_377
)

View File

@ -0,0 +1,29 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
# Standard library
std/[unittest, times],
# Internals
../constantine/config/[common, curves],
../constantine/arithmetic,
../constantine/towers,
../constantine/io/io_bigints,
../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective],
# Test utilities
../helpers/prng_unsafe,
./t_ec_template
const
Iters = 8
run_EC_addition_tests(
ec = ECP_SWei_Proj[Fp2[BLS12_377]],
Iters = Iters,
moduleName = "test_ec_weierstrass_projective_g2_add_double_" & $BLS12_377
)

View File

@ -0,0 +1,24 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
# Internals
../constantine/config/curves,
../constantine/elliptic/ec_weierstrass_projective,
../constantine/towers,
# Test utilities
./t_ec_template
const
Iters = 12
run_EC_mixed_add_impl(
ec = ECP_SWei_Proj[Fp2[BLS12_377]],
Iters = Iters,
moduleName = "test_ec_weierstrass_projective_mixed_add_" & $BLS12_377
)

View File

@ -0,0 +1,31 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
# Standard library
std/[unittest, times],
# Internals
../constantine/config/[common, curves],
../constantine/arithmetic,
../constantine/towers,
../constantine/io/io_bigints,
../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective, ec_scalar_mul],
# Test utilities
../helpers/prng_unsafe,
./support/ec_reference_scalar_mult,
./t_ec_template
const
Iters = 12
ItersMul = Iters div 4
run_EC_mul_distributive_tests(
ec = ECP_SWei_Proj[Fp2[BLS12_377]],
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g2_mul_distributive_" & $BLS12_377
)

View File

@ -0,0 +1,62 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
# Standard library
std/[unittest, times],
# Internals
../constantine/config/[common, curves],
../constantine/arithmetic,
../constantine/towers,
../constantine/io/io_bigints,
../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective, ec_scalar_mul],
# Test utilities
../helpers/prng_unsafe,
./support/ec_reference_scalar_mult,
./t_ec_template
const
Iters = 12
ItersMul = Iters div 4
run_EC_mul_sanity_tests(
ec = ECP_SWei_Proj[Fp2[BLS12_377]],
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g2_mul_sanity_" & $BLS12_377
)
# TODO: the order on E'(Fp2) for BLS curves is ??? with r the order on E(Fp)
#
# test "EC mul [Order]P == Inf":
# var rng: RngState
# let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
# rng.seed(seed)
# echo "test_ec_weierstrass_projective_g1_mul_sanity_extra_curve_order_mul_sanity xoshiro512** seed: ", seed
#
# proc test(EC: typedesc, bits: static int, randZ: static bool) =
# for _ in 0 ..< ItersMul:
# when randZ:
# let a = rng.random_unsafe_with_randZ(EC)
# else:
# let a = rng.random_unsafe(EC)
#
# let exponent = F.C.getCurveOrder()
#
# var
# impl = a
# reference = a
#
# impl.scalarMulGeneric(exponent)
# reference.unsafe_ECmul_double_add(exponent)
#
# check:
# bool(impl.isInf())
# bool(reference.isInf())
#
# test(ECP_SWei_Proj[Fp2[BLS12_377]], bits = BLS12_377.getCurveOrderBitwidth(), randZ = false)
# test(ECP_SWei_Proj[Fp2[BLS12_377]], bits = BLS12_377.getCurveOrderBitwidth(), randZ = true)

View File

@ -0,0 +1,31 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
# Standard library
std/[unittest, times],
# Internals
../constantine/config/[common, curves],
../constantine/arithmetic,
../constantine/towers,
../constantine/io/io_bigints,
../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective, ec_scalar_mul],
# Test utilities
../helpers/prng_unsafe,
./support/ec_reference_scalar_mult,
./t_ec_template
const
Iters = 12
ItersMul = Iters div 4
run_EC_mul_vs_ref_impl(
ec = ECP_SWei_Proj[Fp2[BLS12_377]],
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g2_mul_vs_ref_" & $BLS12_377
)

View File

@ -27,8 +27,8 @@ echo "test_finite_fields_sqrt xoshiro512** seed: ", seed
static: doAssert defined(testingCurves), "This modules requires the -d:testingCurves compile option"
proc exhaustiveCheck_p3mod4(C: static Curve, modulus: static int) =
test "Exhaustive square root check for p ≡ 3 (mod 4) on " & $Curve(C):
proc exhaustiveCheck(C: static Curve, modulus: static int) =
test "Exhaustive square root check for " & $Curve(C):
var squares_to_roots: Table[uint16, set[uint16]]
# Create all squares
@ -102,9 +102,9 @@ template testImpl(a: untyped): untyped {.dirty.} =
bool(r == s)
bool(r == a or r == na)
proc randomSqrtCheck_p3mod4(C: static Curve) =
test "Random square root check for p ≡ 3 (mod 4) on " & $Curve(C):
for _ in 0 ..< Iters:
proc randomSqrtCheck(C: static Curve) =
test "Random square root check for " & $Curve(C):
for _ in 0 ..< 1: # Iters:
let a = rng.random_unsafe(Fp[C])
testImpl(a)
@ -118,20 +118,21 @@ proc randomSqrtCheck_p3mod4(C: static Curve) =
proc main() =
suite "Modular square root" & " [" & $WordBitwidth & "-bit mode]":
exhaustiveCheck_p3mod4 Fake103, 103
exhaustiveCheck_p3mod4 Fake10007, 10007
exhaustiveCheck_p3mod4 Fake65519, 65519
randomSqrtCheck_p3mod4 Mersenne61
randomSqrtCheck_p3mod4 Mersenne127
randomSqrtCheck_p3mod4 BN254_Nogami
randomSqrtCheck_p3mod4 BN254_Snarks
randomSqrtCheck_p3mod4 P256
randomSqrtCheck_p3mod4 Secp256k1
randomSqrtCheck_p3mod4 BLS12_381
randomSqrtCheck_p3mod4 BN446
randomSqrtCheck_p3mod4 FKM12_447
randomSqrtCheck_p3mod4 BLS12_461
randomSqrtCheck_p3mod4 BN462
exhaustiveCheck Fake103, 103
exhaustiveCheck Fake10007, 10007
exhaustiveCheck Fake65519, 65519
randomSqrtCheck Mersenne61
randomSqrtCheck Mersenne127
randomSqrtCheck BN254_Nogami
randomSqrtCheck BN254_Snarks
randomSqrtCheck P256
randomSqrtCheck Secp256k1
randomSqrtCheck BLS12_377 # p ≢ 3 (mod 4)
randomSqrtCheck BLS12_381
randomSqrtCheck BN446
randomSqrtCheck FKM12_447
randomSqrtCheck BLS12_461
randomSqrtCheck BN462
suite "Modular square root - 32-bit bugs highlighted by property-based testing " & " [" & $WordBitwidth & "-bit mode]":
test "FKM12_447 - #30":

View File

@ -16,7 +16,7 @@ import
const TestCurves = [
BN254_Nogami,
BN254_Snarks,
# BLS12_377,
BLS12_377,
BLS12_381,
# BN446
# FKM12_447

View File

@ -21,7 +21,9 @@ import
const
Iters = 8
TestCurves = [
BN254_Nogami,
BN254_Snarks,
BLS12_377,
BLS12_381
]
@ -45,7 +47,7 @@ func random_elem(rng: var RngState, F: typedesc, gen: RandomGen): F {.inline, no
else:
result = rng.random_long01Seq(F)
proc randomSqrtCheck_p3mod4(C: static Curve, gen: RandomGen) =
proc randomSqrtCheck(C: static Curve, gen: RandomGen) =
for _ in 0 ..< Iters:
let a = rng.random_elem(Fp2[C], gen)
var na{.noInit.}: Fp2[C]
@ -70,10 +72,10 @@ proc randomSqrtCheck_p3mod4(C: static Curve, gen: RandomGen) =
proc main() =
suite "Modular square root" & " [" & $WordBitwidth & "-bit mode]":
staticFor(curve, TestCurves):
test "[𝔽p2] Random square root check for p ≡ 3 (mod 4) on " & $curve:
randomSqrtCheck_p3mod4(curve, gen = Uniform)
randomSqrtCheck_p3mod4(curve, gen = HighHammingWeight)
randomSqrtCheck_p3mod4(curve, gen = Long01Sequence)
test "[𝔽p2] Random square root check for " & $curve:
randomSqrtCheck(curve, gen = Uniform)
randomSqrtCheck(curve, gen = HighHammingWeight)
randomSqrtCheck(curve, gen = Long01Sequence)
suite "Modular square root - 32-bit bugs highlighted by property-based testing " & " [" & $WordBitwidth & "-bit mode]":
test "sqrt_if_square invalid square BLS12_381 - #64":

View File

@ -0,0 +1,16 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../constantine/config/common,
../constantine/config/curves,
../constantine/pairing/pairing_bls12,
# Test utilities
./t_pairing_template
runPairingTests(4, BLS12_377, pairing_bls12)

View File

@ -23,9 +23,9 @@ import
const
Iters = 4
TestCurves = [
# BN254_Nogami,
# BN254_Snarks,
# BLS12_377,
BN254_Nogami,
BN254_Snarks,
BLS12_377,
BLS12_381
]

View File

@ -27,6 +27,7 @@ const
TestCurves = [
BN254_Nogami,
BN254_Snarks,
BLS12_377,
BLS12_381
]