G2 / Operations on the twisted curve E'(Fp2) (#51)

* Split elliptic curve tests to better use parallel testing

* Add support for printing points on G2

* Implement multiplication and division by optimal sextic non-residue (BLS12-381)

* Implement modular square root in 𝔽p2

* Support EC add and EC double on G2 (for BLS12-381)

* Support G2 divisive twists with non-unit sextic-non-residue like BN254 snarks

* Add EC G2 bench

* cleanup some unused warnings

* Reorg the tests for parallelization and to avoid instantiating huge files
This commit is contained in:
Mamy Ratsimbazafy 2020-06-15 22:58:56 +02:00 committed by GitHub
parent 2613356281
commit d376f08d1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 1916 additions and 713 deletions

View File

@ -59,7 +59,7 @@ proc main() =
separator()
scalarMulGenericBench(ECP_SWei_Proj[Fp[curve]], scratchSpaceSize = 1 shl 4, MulIters)
separator()
scalarMulGLV(ECP_SWei_Proj[Fp[curve]], MulIters)
scalarMulEndo(ECP_SWei_Proj[Fp[curve]], MulIters)
separator()
separator()

View File

@ -0,0 +1,72 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
# Internals
../constantine/config/curves,
../constantine/arithmetic,
../constantine/towers,
../constantine/elliptic/ec_weierstrass_projective,
# Helpers
../helpers/static_for,
./bench_elliptic_template,
# Standard library
std/strutils
# ############################################################
#
# Benchmark of the G1 group of
# Short Weierstrass elliptic curves
# in (homogeneous) projective coordinates
#
# ############################################################
const Iters = 500_000
const MulIters = 500
const AvailableCurves = [
# P224,
# BN254_Nogami,
BN254_Snarks,
# Curve25519,
# P256,
# Secp256k1,
# BLS12_377,
BLS12_381,
# BN446,
# FKM12_447,
# BLS12_461,
# BN462
]
proc main() =
separator()
staticFor i, 0, AvailableCurves.len:
const curve = AvailableCurves[i]
addBench(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]], scratchSpaceSize = 1 shl 2, MulIters)
separator()
scalarMulGenericBench(ECP_SWei_Proj[Fp2[curve]], scratchSpaceSize = 1 shl 3, MulIters)
separator()
scalarMulGenericBench(ECP_SWei_Proj[Fp2[curve]], scratchSpaceSize = 1 shl 4, MulIters)
separator()
# scalarMulEndo(ECP_SWei_Proj[Fp2[curve]], MulIters)
# separator()
separator()
main()
echo "\nNotes:"
echo " - GCC is significantly slower than Clang on multiprecision arithmetic."
echo " - The simplest operations might be optimized away by the compiler."
echo " - Fast Squaring and Fast Multiplication are possible if there are spare bits in the prime representation (i.e. the prime uses 254 bits out of 256 bits)"

View File

@ -111,20 +111,23 @@ template bench(op: string, T: typedesc, iters: int, body: untyped): untyped =
report(op, fixEllipticDisplay(T), start, stop, startClk, stopClk, iters)
proc addBench*(T: typedesc, iters: int) =
const G1_or_G2 = when T.F is Fp: "G1" else: "G2"
var r {.noInit.}: T
let P = rng.random_unsafe(T)
let Q = rng.random_unsafe(T)
bench("EC Add G1", T, iters):
bench("EC Add " & G1_or_G2, T, iters):
r.sum(P, Q)
proc doublingBench*(T: typedesc, iters: int) =
const G1_or_G2 = when T.F is Fp: "G1" else: "G2"
var r {.noInit.}: T
let P = rng.random_unsafe(T)
bench("EC Double G1", T, iters):
bench("EC Double " & G1_or_G2, T, iters):
r.double(P)
proc scalarMulGenericBench*(T: typedesc, scratchSpaceSize: static int, iters: int) =
const bits = T.F.C.getCurveOrderBitwidth()
const G1_or_G2 = when T.F is Fp: "G1" else: "G2"
var r {.noInit.}: T
let P = rng.random_unsafe(T) # TODO: clear cofactor
@ -135,24 +138,29 @@ proc scalarMulGenericBench*(T: typedesc, scratchSpaceSize: static int, iters: in
var scratchSpace{.noInit.}: array[scratchSpaceSize, T]
bench("EC ScalarMul Generic G1 (scratchsize = " & $scratchSpaceSize & ')', T, iters):
bench("EC ScalarMul Generic " & G1_or_G2 & " (scratchsize = " & $scratchSpaceSize & ')', T, iters):
r = P
r.scalarMulGeneric(exponentCanonical, scratchSpace)
proc scalarMulGLV*(T: typedesc, iters: int) =
proc scalarMulEndo*(T: typedesc, iters: int) =
const bits = T.F.C.getCurveOrderBitwidth()
const G1_or_G2 = when T.F is Fp: "G1" else: "G2"
var r {.noInit.}: T
let P = rng.random_unsafe(T) # TODO: clear cofactor
let exponent = rng.random_unsafe(BigInt[bits])
bench("EC ScalarMul G1 (GLV endomorphism accelerated)", T, iters):
bench("EC ScalarMul " & G1_or_G2 & " (endomorphism accelerated)", T, iters):
r = P
r.scalarMulGLV(exponent)
when T.F is Fp:
r.scalarMulGLV(exponent)
else:
{.error: "Not implemented".}
proc scalarMulUnsafeDoubleAddBench*(T: typedesc, iters: int) =
const bits = T.F.C.getCurveOrderBitwidth()
const G1_or_G2 = when T.F is Fp: "G1" else: "G2"
var r {.noInit.}: T
let P = rng.random_unsafe(T) # TODO: clear cofactor
@ -161,6 +169,6 @@ proc scalarMulUnsafeDoubleAddBench*(T: typedesc, iters: int) =
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
bench("EC ScalarMul G1 (unsafe reference DoubleAdd)", T, iters):
bench("EC ScalarMul " & G1_or_G2 & " (unsafe reference DoubleAdd)", T, iters):
r = P
r.unsafe_ECmul_double_add(exponentCanonical)

View File

@ -5,12 +5,67 @@ description = "This library provides constant time big int primitives."
license = "MIT or Apache License 2.0"
srcDir = "src"
### Dependencies
# Dependencies
# ----------------------------------------------------------------
requires "nim >= 1.1.0"
# Test config
# ----------------------------------------------------------------
const buildParallel = "test_parallel.txt"
### Helper functions
const testDesc: seq[tuple[path: string, useGMP: bool]] = @[
# Primitives
("tests/test_primitives.nim", false),
# Big ints
("tests/test_io_bigints.nim", false),
("tests/test_bigints.nim", false),
("tests/test_bigints_multimod.nim", false),
("tests/test_bigints_mod_vs_gmp.nim", true),
("tests/test_bigints_mul_vs_gmp.nim", true),
("tests/test_bigints_mul_high_words_vs_gmp.nim", true),
# Field
("tests/test_io_fields", false),
("tests/test_finite_fields.nim", false),
("tests/test_finite_fields_mulsquare.nim", false),
("tests/test_finite_fields_sqrt.nim", false),
("tests/test_finite_fields_powinv.nim", false),
("tests/test_finite_fields_vs_gmp.nim", true),
# Precompute
("tests/test_precomputed", false),
# Towers of extension fields
("tests/test_fp2.nim", false),
("tests/test_fp2_sqrt.nim", false),
("tests/test_fp6_bn254_snarks.nim", false),
("tests/test_fp6_bls12_377.nim", false),
("tests/test_fp6_bls12_381.nim", false),
("tests/test_fp12_bn254_snarks.nim", false),
("tests/test_fp12_bls12_377.nim", false),
("tests/test_fp12_bls12_381.nim", false),
# Elliptic curve arithmetic G1
("tests/test_ec_weierstrass_projective_g1_add_double.nim", false),
("tests/test_ec_weierstrass_projective_g1_mul_sanity.nim", false),
("tests/test_ec_weierstrass_projective_g1_mul_distributive.nim", false),
("tests/test_ec_weierstrass_projective_g1_mul_vs_ref.nim", false),
# Elliptic curve arithmetic G2
("tests/test_ec_weierstrass_projective_g2_add_double_bn254_snarks.nim", false),
("tests/test_ec_weierstrass_projective_g2_mul_sanity_bn254_snarks.nim", false),
("tests/test_ec_weierstrass_projective_g2_mul_distributive_bn254_snarks.nim", false),
("tests/test_ec_weierstrass_projective_g2_mul_vs_ref_bn254_snarks.nim", false),
("tests/test_ec_weierstrass_projective_g2_add_double_bls12_381.nim", false),
("tests/test_ec_weierstrass_projective_g2_mul_sanity_bls12_381.nim", false),
("tests/test_ec_weierstrass_projective_g2_mul_distributive_bls12_381.nim", false),
("tests/test_ec_weierstrass_projective_g2_mul_vs_ref_bls12_381.nim", false),
# Elliptic curve arithmetic vs Sagemath
("tests/test_ec_sage_bn254.nim", false),
("tests/test_ec_sage_bls12_381.nim", false)
]
# Helper functions
# ----------------------------------------------------------------
proc test(flags, path: string, commandFile = false) =
# commandFile should be a "file" but Nimscript doesn't support IO
# TODO: use a proper runner
@ -28,9 +83,9 @@ proc test(flags, path: string, commandFile = false) =
let command = "nim " & lang & cc & " " & flags & " --verbosity:0 --outdir:build -r --hints:off --warnings:off " & path
if not commandFile:
echo "\n========================================================================================"
echo "\n=============================================================================================="
echo "Running [flags: ", flags, "] ", path
echo "========================================================================================"
echo "=============================================================================================="
exec command
else:
# commandFile.writeLine command
@ -47,74 +102,18 @@ proc runBench(benchName: string, compiler = "") =
" -d:danger --verbosity:0 -o:build/" & benchName & "_" & compiler &
" -r --hints:off --warnings:off benchmarks/" & benchName & ".nim"
### tasks
# Tasks
# ----------------------------------------------------------------
task test, "Run all tests":
# -d:testingCurves is configured in a *.nim.cfg for convenience
# Primitives
test "", "tests/test_primitives.nim"
# Big ints
test "", "tests/test_io_bigints.nim"
test "", "tests/test_bigints.nim"
test "", "tests/test_bigints_multimod.nim"
test "", "tests/test_bigints_mod_vs_gmp.nim"
# Field
test "", "tests/test_io_fields"
test "", "tests/test_finite_fields.nim"
test "", "tests/test_finite_fields_mulsquare.nim"
test "", "tests/test_finite_fields_sqrt.nim"
test "", "tests/test_finite_fields_powinv.nim"
test "", "tests/test_finite_fields_vs_gmp.nim"
# Precompute
test "", "tests/test_precomputed"
# Towers of extension fields
test "", "tests/test_fp2.nim"
test "", "tests/test_fp6.nim"
test "", "tests/test_fp12.nim"
# Elliptic curve arithmetic
test "", "tests/test_ec_weierstrass_projective_g1.nim"
test "", "tests/test_ec_bn254.nim"
test "", "tests/test_ec_bls12_381.nim"
for td in testDesc:
test "", td.path
if sizeof(int) == 8: # 32-bit tests on 64-bit arch
# Primitives
test "-d:Constantine32", "tests/test_primitives.nim"
# Big ints
test "-d:Constantine32", "tests/test_io_bigints.nim"
test "-d:Constantine32", "tests/test_bigints.nim"
test "-d:Constantine32", "tests/test_bigints_multimod.nim"
test "-d:Constantine32", "tests/test_bigints_mod_vs_gmp.nim"
# Field
test "-d:Constantine32", "tests/test_io_fields"
test "-d:Constantine32", "tests/test_finite_fields.nim"
test "-d:Constantine32", "tests/test_finite_fields_mulsquare.nim"
test "-d:Constantine32", "tests/test_finite_fields_sqrt.nim"
test "-d:Constantine32", "tests/test_finite_fields_powinv.nim"
test "-d:Constantine32", "tests/test_finite_fields_vs_gmp.nim"
# Precompute
test "-d:Constantine32", "tests/test_precomputed"
# Towers of extension fields
test "-d:Constantine32", "tests/test_fp2.nim"
test "-d:Constantine32", "tests/test_fp6.nim"
test "-d:Constantine32", "tests/test_fp12.nim"
# Elliptic curve arithmetic
test "-d:Constantine32", "tests/test_ec_weierstrass_projective_g1.nim"
test "-d:Constantine32", "tests/test_ec_bn254.nim"
test "-d:Constantine32", "tests/test_ec_bls12_381.nim"
for td in testDesc:
test "-d:Constantine32", td.path
# Benchmarks compile and run
# ignore Windows 32-bit for the moment
@ -125,66 +124,18 @@ task test, "Run all tests":
runBench("bench_fp6")
runBench("bench_fp12")
runBench("bench_ec_g1")
runBench("bench_ec_g2")
task test_no_gmp, "Run tests that don't require GMP":
# -d:testingCurves is configured in a *.nim.cfg for convenience
for td in testDesc:
if not td.useGMP:
test "", td.path
# Primitives
test "", "tests/test_primitives.nim"
# Big ints
test "", "tests/test_io_bigints.nim"
test "", "tests/test_bigints.nim"
test "", "tests/test_bigints_multimod.nim"
# Field
test "", "tests/test_io_fields"
test "", "tests/test_finite_fields.nim"
test "", "tests/test_finite_fields_mulsquare.nim"
test "", "tests/test_finite_fields_sqrt.nim"
test "", "tests/test_finite_fields_powinv.nim"
# Precompute
test "", "tests/test_precomputed"
# Towers of extension fields
test "", "tests/test_fp2.nim"
test "", "tests/test_fp6.nim"
test "", "tests/test_fp12.nim"
# Elliptic curve arithmetic
test "", "tests/test_ec_weierstrass_projective_g1.nim"
test "", "tests/test_ec_bn254.nim"
test "", "tests/test_ec_bls12_381.nim"
if sizeof(int) == 8: # 32-bit tests
# Primitives
test "-d:Constantine32", "tests/test_primitives.nim"
# Big ints
test "-d:Constantine32", "tests/test_io_bigints.nim"
test "-d:Constantine32", "tests/test_bigints.nim"
test "-d:Constantine32", "tests/test_bigints_multimod.nim"
# Field
test "-d:Constantine32", "tests/test_io_fields"
test "-d:Constantine32", "tests/test_finite_fields.nim"
test "-d:Constantine32", "tests/test_finite_fields_mulsquare.nim"
test "-d:Constantine32", "tests/test_finite_fields_sqrt.nim"
test "-d:Constantine32", "tests/test_finite_fields_powinv.nim"
# Precompute
test "-d:Constantine32", "tests/test_precomputed"
# Towers of extension fields
test "-d:Constantine32", "tests/test_fp2.nim"
test "-d:Constantine32", "tests/test_fp6.nim"
test "-d:Constantine32", "tests/test_fp12.nim"
# Elliptic curve arithmetic
test "-d:Constantine32", "tests/test_ec_weierstrass_projective_g1.nim"
test "-d:Constantine32", "tests/test_ec_bn254.nim"
test "-d:Constantine32", "tests/test_ec_bls12_381.nim"
if sizeof(int) == 8: # 32-bit tests on 64-bit arch
for td in testDesc:
if not td.useGMP:
test "-d:Constantine32", td.path
# Benchmarks compile and run
# ignore Windows 32-bit for the moment
@ -195,79 +146,24 @@ task test_no_gmp, "Run tests that don't require GMP":
runBench("bench_fp6")
runBench("bench_fp12")
runBench("bench_ec_g1")
runBench("bench_ec_g2")
task test_parallel, "Run all tests in parallel (via GNU parallel)":
# -d:testingCurves is configured in a *.nim.cfg for convenience
let cmdFile = true # open(buildParallel, mode = fmWrite) # Nimscript doesn't support IO :/
exec "> " & buildParallel
# Primitives
test "", "tests/test_primitives.nim", cmdFile
# Big ints
test "", "tests/test_io_bigints.nim", cmdFile
test "", "tests/test_bigints.nim", cmdFile
test "", "tests/test_bigints_multimod.nim", cmdFile
test "", "tests/test_bigints_mul_vs_gmp.nim", cmdFile
test "", "tests/test_bigints_mod_vs_gmp.nim", cmdFile
# Field
test "", "tests/test_io_fields", cmdFile
test "", "tests/test_finite_fields.nim", cmdFile
test "", "tests/test_finite_fields_mulsquare.nim", cmdFile
test "", "tests/test_finite_fields_sqrt.nim", cmdFile
test "", "tests/test_finite_fields_powinv.nim", cmdFile
test "", "tests/test_finite_fields_vs_gmp.nim", cmdFile
# Towers of extension fields
test "", "tests/test_fp2.nim", cmdFile
test "", "tests/test_fp6.nim", cmdFile
test "", "tests/test_fp12.nim", cmdFile
# Elliptic curve arithmetic
test "", "tests/test_ec_weierstrass_projective_g1.nim", cmdFile
test "", "tests/test_ec_bn254.nim", cmdFile
test "", "tests/test_ec_bls12_381.nim", cmdFile
for td in testDesc:
test "", td.path, cmdFile
# cmdFile.close()
# Execute everything in parallel with GNU parallel
exec "parallel --keep-order --group < " & buildParallel
exec "> " & buildParallel
if sizeof(int) == 8: # 32-bit tests on 64-bit arch
# Primitives
test "-d:Constantine32", "tests/test_primitives.nim", cmdFile
# Big ints
test "-d:Constantine32", "tests/test_io_bigints.nim", cmdFile
test "-d:Constantine32", "tests/test_bigints.nim", cmdFile
test "-d:Constantine32", "tests/test_bigints_multimod.nim", cmdFile
test "-d:Constantine32", "tests/test_bigints_mul_vs_gmp.nim", cmdFile
test "-d:Constantine32", "tests/test_bigints_mod_vs_gmp.nim", cmdFile
# Field
test "-d:Constantine32", "tests/test_io_fields", cmdFile
test "-d:Constantine32", "tests/test_finite_fields.nim", cmdFile
test "-d:Constantine32", "tests/test_finite_fields_mulsquare.nim", cmdFile
test "-d:Constantine32", "tests/test_finite_fields_sqrt.nim", cmdFile
test "-d:Constantine32", "tests/test_finite_fields_powinv.nim", cmdFile
test "-d:Constantine32", "tests/test_finite_fields_vs_gmp.nim", cmdFile
# Towers of extension fields
test "-d:Constantine32", "tests/test_fp2.nim", cmdFile
test "-d:Constantine32", "tests/test_fp6.nim", cmdFile
test "-d:Constantine32", "tests/test_fp12.nim", cmdFile
# Elliptic curve arithmetic
test "-d:Constantine32", "tests/test_ec_weierstrass_projective_g1.nim", cmdFile
test "-d:Constantine32", "tests/test_ec_bn254.nim", cmdFile
test "-d:Constantine32", "tests/test_ec_bls12_381.nim", cmdFile
for td in testDesc:
test "-d:Constantine32", td.path, cmdFile
# cmdFile.close()
# Execute everything in parallel with GNU parallel
exec "parallel --keep-order --group < " & buildParallel
@ -283,6 +179,7 @@ task test_parallel, "Run all tests in parallel (via GNU parallel)":
runBench("bench_fp6")
runBench("bench_fp12")
runBench("bench_ec_g1")
runBench("bench_ec_g2")
task bench_fp, "Run benchmark 𝔽p with your default compiler":
runBench("bench_fp")
@ -323,8 +220,17 @@ task bench_fp12_clang, "Run benchmark 𝔽p12 with clang":
task bench_ec_g1, "Run benchmark on Elliptic Curve group 𝔾1 - Short Weierstrass with Projective Coordinates - GCC":
runBench("bench_ec_g1")
task bench_ec_gcc, "Run benchmark on Elliptic Curve group 𝔾1 - Short Weierstrass with Projective Coordinates - GCC":
task bench_ec_g1_gcc, "Run benchmark on Elliptic Curve group 𝔾1 - Short Weierstrass with Projective Coordinates - GCC":
runBench("bench_ec_g1", "gcc")
task bench_ec_g1_clang, "Run benchmark on Elliptic Curve group 𝔾1 - Short Weierstrass with Projective Coordinates - Clang":
runBench("bench_ec_g1", "clang")
task bench_ec_g2, "Run benchmark on Elliptic Curve group 𝔾2 - Short Weierstrass with Projective Coordinates - GCC":
runBench("bench_ec_g2")
task bench_ec_g2_gcc, "Run benchmark on Elliptic Curve group 𝔾2 - Short Weierstrass with Projective Coordinates - GCC":
runBench("bench_ec_g2", "gcc")
task bench_ec_g2_clang, "Run benchmark on Elliptic Curve group 𝔾2 - Short Weierstrass with Projective Coordinates - Clang":
runBench("bench_ec_g2", "clang")

View File

@ -301,7 +301,7 @@ func reduce*[aBits, mBits](r: var BigInt[mBits], a: BigInt[aBits], M: BigInt[mBi
# pass a pointer+length to a fixed session of the BSS.
reduce(r.limbs, a.limbs, aBits, M.limbs, mBits)
func div2mod*[bits](a: var BigInt[bits], mp1div2: BigInt[bits]) =
func div2_modular*[bits](a: var BigInt[bits], mp1div2: BigInt[bits]) =
## Compute a <- a/2 (mod M)
## `mp1div2` is the modulus (M+1)/2
##
@ -313,7 +313,7 @@ func div2mod*[bits](a: var BigInt[bits], mp1div2: BigInt[bits]) =
## overflowing the "Limbs" by dividing by 2 first
## and add 1
## Otherwise `mp1div2` should be M/2
a.limbs.div2mod(mp1div2.limbs)
a.limbs.div2_modular(mp1div2.limbs)
func steinsGCD*[bits](r: var BigInt[bits], a, F, M, mp1div2: BigInt[bits]) =
## Compute F multiplied the modular inverse of ``a`` modulo M

View File

@ -45,7 +45,7 @@ func fromBig*[C: static Curve](T: type Fp[C], src: BigInt): Fp[C] {.noInit.} =
## Convert a BigInt to its Montgomery form
result.mres.montyResidue(src, C.Mod, C.getR2modP(), C.getNegInvModWord(), C.canUseNoCarryMontyMul())
func fromBig*[C: static Curve](dst: var Fp[C], src: BigInt) {.noInit.} =
func fromBig*[C: static Curve](dst: var Fp[C], src: BigInt) =
## Convert a BigInt to its Montgomery form
dst.mres.montyResidue(src, C.Mod, C.getR2modP(), C.getNegInvModWord(), C.canUseNoCarryMontyMul())
@ -168,7 +168,7 @@ func neg*(r: var Fp, a: Fp) =
func div2*(a: var Fp) =
## Modular division by 2
a.mres.div2mod(Fp.C.getPrimePlus1div2())
a.mres.div2_modular(Fp.C.getPrimePlus1div2())
# ############################################################
#
@ -247,11 +247,13 @@ func isSquare*[C](a: Fp[C]): SecretBool =
# 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 = xi.isOne()
# 0 is also a square
result = result or xi.isZero()
result = not(xi.mres == C.getMontyPrimeMinus1())
# xi can be:
# - 1 if a square
# - 0 if 0
# - -1 if a quadratic non-residue
func sqrt_p3mod4*[C](a: var Fp[C]) =
func sqrt_p3mod4[C](a: var Fp[C]) =
## Compute the square root of ``a``
##
## This requires ``a`` to be a square
@ -262,10 +264,10 @@ func sqrt_p3mod4*[C](a: var Fp[C]) =
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
static: doAssert C.Mod.limbs[0].BaseType mod 4 == 3
static: doAssert BaseType(C.Mod.limbs[0]) mod 4 == 3
a.powUnsafeExponent(C.getPrimePlus1div4_BE())
func sqrt_if_square_p3mod4*[C](a: var Fp[C]): SecretBool =
func sqrt_if_square_p3mod4[C](a: var Fp[C]): SecretBool =
## If ``a`` is a square, compute the square root of ``a``
## if not, ``a`` is unmodified.
##
@ -290,6 +292,33 @@ func sqrt_if_square_p3mod4*[C](a: var Fp[C]): SecretBool =
result = not(a0.mres == C.getMontyPrimeMinus1())
a.ccopy(a1a, result)
func sqrt*[C](a: var Fp[C]) =
## 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 =
## 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)".}
# ############################################################
#
# Field arithmetic ergonomic primitives

View File

@ -162,3 +162,18 @@ func inv*(r: var Fp, a: Fp) =
# Performance is slower than GCD
# To be revisited with faster squaring/multiplications
r.mres.steinsGCD(a.mres, Fp.C.getR2modP(), Fp.C.Mod, Fp.C.getPrimePlus1div2())
func inv*(a: var Fp) =
## Inversion modulo p
##
## The inverse of 0 is 0.
## Incidentally this avoids extra check
## to convert Jacobian and Projective coordinates
## to affine for elliptic curve
# For now we don't activate the addition chains
# neither for Secp256k1 nor BN curves
# Performance is slower than GCD
# To be revisited with faster squaring/multiplications
var t: typeof(a) # TODO: zero-init needed?
t.mres.steinsGCD(a.mres, Fp.C.getR2modP(), Fp.C.Mod, Fp.C.getPrimePlus1div2())
a = t

View File

@ -20,7 +20,7 @@ import
#
# ############################################################
func div2mod*(a: var Limbs, mp1div2: Limbs) {.inline.}=
func div2_modular*(a: var Limbs, mp1div2: Limbs) {.inline.}=
## Modular Division by 2
## `a` will be divided in-place
## `mp1div2` is the modulus (M+1)/2
@ -39,7 +39,7 @@ func div2mod*(a: var Limbs, mp1div2: Limbs) {.inline.}=
# a = a shr 1
let wasOdd = a.isOdd()
a.shiftRight(1)
let carry = a.cadd(mp1div2, wasOdd)
let carry {.used.} = a.cadd(mp1div2, wasOdd)
debug: doAssert not carry.bool
# ############################################################
@ -133,10 +133,10 @@ func steinsGCD*(v: var Limbs, a: Limbs, F, M: Limbs, bits: int, mp1div2: Limbs)
u.cswap(v, aLessThanB)
# if isOddA: u -= v (mod M)
let neg = isOddA and (SecretBool) u.csub(v, isOddA)
let corrected = u.cadd(M, neg)
discard u.cadd(M, neg)
# u = u/2 (mod M)
u.div2mod(mp1div2)
u.div2_modular(mp1div2)
debug:
doAssert bool a.isZero()

View File

@ -596,7 +596,7 @@ func montyPowUnsafeExponent*(
acc, acc_len: uint
e = 0
while acc_len > 0 or e < exponent.len:
let (k, bits) = montyPowSquarings(
let (_, bits) = montyPowSquarings(
a, exponent, M, m0ninv,
scratchspace[0], window,
acc, acc_len, e,

View File

@ -65,7 +65,7 @@ func scalarMulPrologue(
P: var ECP_SWei_Proj,
scratchspace: var openarray[ECP_SWei_Proj]
): uint =
## Setup the scratchspace
## Setup the scratchspace then set P to infinity
## Returns the fixed-window size for scalar mul with window optimization
result = scratchspace.len.getWindowLen()
# Precompute window content, special case for window = 1
@ -218,7 +218,7 @@ func scalarMul*(
## P <- [k] P
# This calls endomorphism accelerated scalar mul if available
# or the generic scalar mul otherwise
when ECP_SWei_Proj.F.C in {BN254_Snarks, BLS12_381}:
when ECP_SWei_Proj.F is Fp and ECP_SWei_Proj.F.C in {BN254_Snarks, BLS12_381}:
# ⚠️ This requires the cofactor to be cleared
scalarMulGLV(P, scalar)
else:

View File

@ -23,15 +23,29 @@ func curve_eq_rhs*[F](y2: var F, x: F) =
t.square(x)
t *= x
# No need to precompute `b` in 𝔽p or 𝔽p² or `b/µ` `µ b`
# This procedure is not use in perf critcal situation like signing/verification
# This procedure is not use in perf critical situation like signing/verification
# but for testing to quickly create points on a curve.
y2 = F.fromBig F.C.matchingBigInt().fromUint F.C.getCoefB()
# That said D-Twists require an inversion
# and we could avoid doing `b/µ` or `µ*b` at runtime on 𝔽
# which would accelerate random point generation
#
# This is preferred to generating random point
# via random scalar multiplication of the curve generator
# as the latter assumes:
# - point addition, doubling work
# - scalar multiplication works
# - a generator point is defined
# i.e. you can't test unless everything is already working
#
# TODO: precomputation needed when deserializing points
# to check if a point is on-curve and prevent denial-of-service
# using slow inversion.
y2.fromBig F.C.matchingBigInt().fromUint F.C.getCoefB()
when F is Fp2:
when F.C.getSexticTwist() == D_Twist:
y2 /= F.C.get_SNR_Fp2()
y2 /= SexticNonResidue
elif F.C.getSexticTwist() == M_Twist:
y2 *= F.C.get_SNR_Fp2()
y2 *= SexticNonResidue
else:
{.error: "Only twisted curves are supported on extension field 𝔽".}

View File

@ -81,7 +81,7 @@ func trySetFromCoordsXandZ*[F](P: var ECP_SWei_Proj[F], x, z: F): SecretBool =
## will be provided, this is intended for testing purposes.
P.y.curve_eq_rhs(x)
# TODO: supports non p ≡ 3 (mod 4) modulus like BLS12-377
result = sqrt_if_square_p3mod4(P.y)
result = sqrt_if_square(P.y)
P.x.prod(x, z)
P.y *= z
@ -100,7 +100,7 @@ func trySetFromCoordX*[F](P: var ECP_SWei_Proj[F], x: F): SecretBool =
## will be provided, this is intended for testing purposes.
P.y.curve_eq_rhs(x)
# TODO: supports non p ≡ 3 (mod 4) modulus like BLS12-377
result = sqrt_if_square_p3mod4(P.y)
result = sqrt_if_square(P.y)
P.x = x
P.z.setOne()
@ -178,32 +178,32 @@ func sum*[F](
t4.sum(t0, t1) # 7. t4 <- t0 + t1
t3 -= t4 # 8. t3 <- t3 - t4 t3 = (X1 + Y1)(X2 + Y2) - (X1 X2 + Y1 Y2) = X1.Y2 + X2.Y1
when F is Fp2 and F.C.getSexticTwist() == D_Twist:
t3 *= F.sexticNonResidue()
t3 *= SexticNonResidue
t4.sum(P.y, P.z) # 9. t4 <- Y1 + Z1
r.x.sum(Q.y, Q.z) # 10. X3 <- Y2 + Z2
t4 *= r.x # 11. t4 <- t4 X3
r.x.sum(t1, t2) # 12. X3 <- t1 + t2 X3 = Y1 Y2 + Z1 Z2
t4 -= r.x # 13. t4 <- t4 - X3 t4 = (Y1 + Z1)(Y2 + Z2) - (Y1 Y2 + Z1 Z2) = Y1 Z2 + Y2 Z1
when F is Fp2 and F.C.getSexticTwist() == D_Twist:
t4 *= F.sexticNonResidue()
t4 *= SexticNonResidue
r.x.sum(P.x, P.z) # 14. X3 <- X1 + Z1
r.y.sum(Q.x, Q.z) # 15. Y3 <- X2 + Z2
r.x *= r.y # 16. X3 <- X3 Y3 X3 = (X1 Z1)(X2 Z2)
r.y.sum(t0, t2) # 17. Y3 <- t0 + t2 Y3 = X1 X2 + Z1 Z2
r.y.diff(r.x, r.y) # 18. Y3 <- X3 - Y3 Y3 = (X1 + Z1)(X2 + Z2) - (X1 X2 + Z1 Z2) = X1 Z2 + X2 Z1
when F is Fp2 and F.C.getSexticTwist() == D_Twist:
t0 *= F.sexticNonResidue()
t1 *= F.sexticNonResidue()
t0 *= SexticNonResidue
t1 *= SexticNonResidue
r.x.double(t0) # 19. X3 <- t0 + t0 X3 = 2 X1 X2
t0 += r.x # 20. t0 <- X3 + t0 t0 = 3 X1 X2
t2 *= b3 # 21. t2 <- b3 t2 t2 = 3b Z1 Z2
when F is Fp2 and F.C.getSexticTwist() == M_Twist:
t2 *= F.sexticNonResidue()
t2 *= SexticNonResidue
r.z.sum(t1, t2) # 22. Z3 <- t1 + t2 Z3 = Y1 Y2 + 3b Z1 Z2
t1 -= t2 # 23. t1 <- t1 - t2 t1 = Y1 Y2 - 3b Z1 Z2
r.y *= b3 # 24. Y3 <- b3 Y3 Y3 = 3b(X1 Z2 + X2 Z1)
when F is Fp2 and F.C.getSexticTwist() == M_Twist:
r.y *= F.sexticNonResidue()
r.y *= SexticNonResidue
r.x.prod(t4, r.y) # 25. X3 <- t4 Y3 X3 = 3b(Y1 Z2 + Y2 Z1)(X1 Z2 + X2 Z1)
t2.prod(t3, t1) # 26. t2 <- t3 t1 t2 = (X1 Y2 + X2 Y1) (Y1 Y2 - 3b Z1 Z2)
r.x.diff(t2, r.x) # 27. X3 <- t2 - X3 X3 = (X1 Y2 + X2 Y1) (Y1 Y2 - 3b Z1 Z2) - 3b(Y1 Z2 + Y2 Z1)(X1 Z2 + X2 Z1)
@ -252,7 +252,7 @@ func double*[F](
# Cost: 8M + 3S + 3 mul(a) + 2 mul(3b) + 15a
when F.C.getCoefA() == 0:
var t0 {.noInit.}, t1 {.noInit.}, t2 {.noInit.}: F
var t0 {.noInit.}, t1 {.noInit.}, t2 {.noInit.}, snrY {.noInit.}: F
const b3 = 3 * F.C.getCoefB()
# Algorithm 9 for curves:
@ -261,14 +261,21 @@ func double*[F](
# X3 = 2XY(Y² - 9bZ²)
# Y3 = (Y² - 9bZ²)(Y² + 3bZ²) + 24bY²Z²
# Z3 = 8Y³Z
t0.square(P.y) # 1. t0 <- Y Y
snrY = P.y
when F is Fp2 and F.C.getSexticTwist() == D_Twist:
snrY *= SexticNonResidue
t0.square(P.y)
t0 *= SexticNonResidue
else:
t0.square(P.y) # 1. t0 <- Y Y
r.z.double(t0) # 2. Z3 <- t0 + t0
r.z.double() # 3. Z3 <- Z3 + Z3
r.z.double() # 4. Z3 <- Z3 + Z3 Z3 = 8Y²
t1.prod(P.y, P.z) # 5. t1 <- Y Z
t1.prod(snrY, P.z) # 5. t1 <- Y Z
t2.square(P.z) # 6. t2 <- Z Z
t2 *= b3 # 7. t2 <- b3 t2
when F is Fp2 and F.C.getSexticTwist() == M_Twist:
t2 *= SexticNonResidue
r.x.prod(t2, r.z) # 8. X3 <- t2 Z3
r.y.sum(t0, t2) # 9. Y3 <- t0 + t2
r.z *= t1 # 10. Z3 <- t1 Z3
@ -277,7 +284,7 @@ func double*[F](
t0 -= t2 # 13. t0 <- t0 - t2
r.y *= t0 # 14. Y3 <- t0 Y3
r.y += r.x # 15. Y3 <- X3 + Y3
t1.prod(P.x, P.y) # 16. t1 <- X Y
t1.prod(P.x, snrY) # 16. t1 <- X Y
r.x.prod(t0, t1) # 17. X3 <- t0 t1
r.x.double() # 18. X3 <- X3 + X3
else:

View File

@ -428,6 +428,27 @@ func fromHex*(T: type BigInt, s: string): T {.noInit.} =
# 2. Convert canonical uint to Big Int
result.fromRawUint(bytes, bigEndian)
func appendHex*(dst: var string, big: BigInt, order: static Endianness = bigEndian) =
## Append the BigInt hex into an accumulator
## Note. Leading zeros are not removed.
## Result is prefixed with 0x
##
## Output will be padded with 0s to maintain constant-time.
##
## CT:
## - no leaks
##
## This is useful to reduce the number of allocations when serializing
## Fp towers
# 1. Convert Big Int to canonical uint
const canonLen = (big.bits + 8 - 1) div 8
var bytes: array[canonLen, byte]
exportRawUint(bytes, big, cpuEndian)
# 2 Convert canonical uint to hex
dst.add bytes.nativeEndianToHex(order)
func toHex*(big: BigInt, order: static Endianness = bigEndian): string =
## Stringify an int to hex.
## Note. Leading zeros are not removed.
@ -437,11 +458,4 @@ func toHex*(big: BigInt, order: static Endianness = bigEndian): string =
##
## CT:
## - no leaks
# 1. Convert Big Int to canonical uint
const canonLen = (big.bits + 8 - 1) div 8
var bytes: array[canonLen, byte]
exportRawUint(bytes, big, cpuEndian)
# 2 Convert canonical uint to hex
result = bytes.nativeEndianToHex(order)
result.appendHex(big, order)

View File

@ -7,7 +7,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
./io_bigints, ./io_fields,
./io_bigints, ./io_fields, ./io_towers,
../config/curves,
../elliptic/[
ec_weierstrass_affine,
@ -41,11 +41,11 @@ func toHex*(P: ECP_SWei_Proj): string =
var aff {.noInit.}: typeof(P)
aff.affineFromProjective(P)
result = $aff.F.C & "(x: "
result &= aff.x.tohex(bigEndian)
result &= ", y: "
result &= aff.y.tohex(bigEndian)
result &= ')'
result = "ECP[" & $aff.F & "](\n x: "
result.appendHex(aff.x, bigEndian)
result &= ",\n y: "
result.appendHex(aff.y, bigEndian)
result &= "\n)"
func fromHex*(dst: var ECP_SWei_Proj, x, y: string): bool {.raises: [ValueError].}=
## Convert hex strings to a curve point

View File

@ -42,6 +42,17 @@ func exportRawUint*(dst: var openarray[byte],
## I.e least significant bit is aligned to buffer boundary
exportRawUint(dst, src.toBig(), dstEndianness)
func appendHex*(dst: var string, f: Fp, order: static Endianness = bigEndian) =
## Stringify a finite field element to hex.
## Note. Leading zeros are not removed.
## Result is prefixed with 0x
##
## Output will be padded with 0s to maintain constant-time.
##
## CT:
## - no leaks
dst.appendHex(f.toBig(), order)
func toHex*(f: Fp, order: static Endianness = bigEndian): string =
## Stringify a finite field element to hex.
## Note. Leading zeros are not removed.
@ -51,7 +62,7 @@ func toHex*(f: Fp, order: static Endianness = bigEndian): string =
##
## CT:
## - no leaks
result = f.toBig().toHex(order)
result.appendHex(f, order)
func fromHex*(dst: var Fp, s: string) {.raises: [ValueError].}=
## Convert a hex string to a element of Fp

View File

@ -0,0 +1,48 @@
# 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/typetraits,
# Internal
./io_bigints, ./io_fields,
../config/curves,
../arithmetic/finite_fields,
../towers
# No exceptions allowed
{.push raises: [].}
{.push inline.}
# ############################################################
#
# Parsing from canonical inputs to internal representation
#
# ############################################################
func appendHex*(accum: var string, f: Fp2 or Fp6 or Fp12, order: static Endianness = bigEndian) =
## Hex accumulator
accum.add static($f.typeof.genericHead() & '(')
for fieldName, fieldValue in fieldPairs(f):
when fieldName != "c0":
accum.add ", "
accum.add fieldName & ": "
accum.appendHex(fieldValue, order)
accum.add ")"
func toHex*(f: Fp2 or Fp6 or Fp12, order: static Endianness = bigEndian): string =
## Stringify a tower field element to hex.
## Note. Leading zeros are not removed.
## Result is prefixed with 0x
##
## Output will be padded with 0s to maintain constant-time.
##
## CT:
## - no leaks
result.appendHex(f, order)

View File

@ -50,7 +50,7 @@ func square_Chung_Hasan_SQR2(r: var CubicExt, a: CubicExt) =
v4.prod(a.c0, a.c1)
v4.double()
v5.square(a.c2)
r.c1 = β * v5
r.c1 = NonResidue * v5
r.c1 += v4
r.c2.diff(v4, v5)
v3.square(a.c0)
@ -59,7 +59,7 @@ func square_Chung_Hasan_SQR2(r: var CubicExt, a: CubicExt) =
v5.prod(a.c1, a.c2)
v5.double()
v4.square()
r.c0 = β * v5
r.c0 = NonResidue * v5
r.c0 += v3
r.c2 += v4
r.c2 += v5
@ -70,29 +70,29 @@ func square_Chung_Hasan_SQR3(r: var CubicExt, a: CubicExt) =
mixin prod, square, sum
var v0{.noInit.}, v2{.noInit.}: typeof(r.c0)
r.c1.sum(a.c0, a.c2) # r1 = a0 + a2
v2.diff(r.c1, a.c1) # v2 = a0 - a1 + a2
r.c1 += a.c1 # r1 = a0 + a1 + a2
r.c1.square() # r1 = (a0 + a1 + a2)²
v2.square() # v2 = (a0 - a1 + a2)²
r.c1.sum(a.c0, a.c2) # r1 = a0 + a2
v2.diff(r.c1, a.c1) # v2 = a0 - a1 + a2
r.c1 += a.c1 # r1 = a0 + a1 + a2
r.c1.square() # r1 = (a0 + a1 + a2)²
v2.square() # v2 = (a0 - a1 + a2)²
r.c2.sum(r.c1, v2) # r2 = (a0 + a1 + a2)² + (a0 - a1 + a2)²
r.c2.div2() # r2 = ((a0 + a1 + a2)² + (a0 - a1 + a2)²)/2
r.c2.sum(r.c1, v2) # r2 = (a0 + a1 + a2)² + (a0 - a1 + a2)²
r.c2.div2() # r2 = ((a0 + a1 + a2)² + (a0 - a1 + a2)²)/2
r.c0.prod(a.c1, a.c2) # r0 = a1 a2
r.c0.double() # r0 = 2 a1 a2
r.c0.prod(a.c1, a.c2) # r0 = a1 a2
r.c0.double() # r0 = 2 a1 a2
v2.square(a.c2) # v2 = a2²
r.c1 += β * v2 # r1 = (a0 + a1 + a2)² + β a2²
r.c1 -= r.c0 # r1 = (a0 + a1 + a2)² - 2 a1 a2 + β a2²
r.c1 -= r.c2 # r1 = (a0 + a1 + a2)² - 2 a1 a2 - ((a0 + a1 + a2)² + (a0 - a1 + a2)²)/2 + β a2²
v2.square(a.c2) # v2 = a2²
r.c1 += NonResidue * v2 # r1 = (a0 + a1 + a2)² + β a2²
r.c1 -= r.c0 # r1 = (a0 + a1 + a2)² - 2 a1 a2 + β a2²
r.c1 -= r.c2 # r1 = (a0 + a1 + a2)² - 2 a1 a2 - ((a0 + a1 + a2)² + (a0 - a1 + a2)²)/2 + β a2²
v0.square(a.c0) # v0 = a0²
r.c0 *= β # r0 = β 2 a1 a2
r.c0 += v0 # r0 = a0² + β 2 a1 a2
v0.square(a.c0) # v0 = a0²
r.c0 *= NonResidue # r0 = β 2 a1 a2
r.c0 += v0 # r0 = a0² + β 2 a1 a2
r.c2 -= v0 # r2 = ((a0 + a1 + a2)² + (a0 - a1 + a2)²)/2 - a0²
r.c2 -= v2 # r2 = ((a0 + a1 + a2)² + (a0 - a1 + a2)²)/2 - a0² - a2²
r.c2 -= v0 # r2 = ((a0 + a1 + a2)² + (a0 - a1 + a2)²)/2 - a0²
r.c2 -= v2 # r2 = ((a0 + a1 + a2)² + (a0 - a1 + a2)²)/2 - a0² - a2²
func square*(r: var CubicExt, a: CubicExt) {.inline.} =
## Returns r = a²
@ -115,7 +115,7 @@ func prod*(r: var CubicExt, a, b: CubicExt) =
r.c0 *= t
r.c0 -= v1
r.c0 -= v2
r.c0 *= β
r.c0 *= NonResidue
r.c0 += v0
# r.c1 = (a.c0 + a.c1) * (b.c0 + b.c1) - v0 - v1 + β v2
@ -124,7 +124,7 @@ func prod*(r: var CubicExt, a, b: CubicExt) =
r.c1 *= t
r.c1 -= v0
r.c1 -= v1
r.c1 += β * v2
r.c1 += NonResidue * v2
# r.c2 = (a.c0 + a.c2) * (b.c0 + b.c2) - v0 - v2 + v1
r.c2.sum(a.c0, a.c2)
@ -159,13 +159,13 @@ func inv*(r: var CubicExt, a: CubicExt) =
# A <- a0² - β a1 a2
r.c0.square(a.c0)
v1.prod(a.c1, a.c2)
v1 *= β
v1 *= NonResidue
r.c0 -= v1
# B in v1
# B <- β a2² - a0 a1
v1.square(a.c2)
v1 *= β
v1 *= NonResidue
v2.prod(a.c0, a.c1)
v1 -= v2
@ -177,8 +177,8 @@ func inv*(r: var CubicExt, a: CubicExt) =
# F in v3
# F <- β a1 C + a0 A + β a2 B
r.c1.prod(v1, β * a.c2)
r.c2.prod(v2, β * a.c1)
r.c1.prod(v1, NonResidue * a.c2)
r.c2.prod(v2, NonResidue * a.c1)
v3.prod(r.c0, a.c0)
v3 += r.c1
v3 += r.c2

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.
import
../arithmetic,
../config/[common, curves],
../primitives,
./tower_common,
./quadratic_extensions,
./cubic_extensions
# ############################################################
#
# Exponentiations (pow and square roots) in extension fields
#
# ############################################################
# Square root should be implemented in constant-time for hash-to-curve:
# https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-05#section-4
#
# Further non-constant-time optimization may be used
# - Square Root Computation over Even Extension Fields
# Gora Adj, Francisco Rodríguez-Henríquez, 2012
# https://eprint.iacr.org/2012/685
# No exceptions allowed
{.push raises: [].}
# Pow
# -----------------------------------------------------------
template checkPowScratchSpaceLen(len: int) =
## Checks that there is a minimum of scratchspace to hold the temporaries
debug:
assert len >= 2, "Internal Error: the scratchspace for powmod should be equal or greater than 2"
func getWindowLen(bufLen: int): uint =
## Compute the maximum window size that fits in the scratchspace buffer
checkPowScratchSpaceLen(bufLen)
result = 4
while (1 shl result) + 1 > bufLen:
dec result
func powPrologue[F](a: var F, scratchspace: var openarray[F]): uint =
## Setup the scratchspace, then set a to 1.
## Returns the fixed-window size for exponentiation with window optimization
result = scratchspace.len.getWindowLen
# Precompute window content, special case for window = 1
# (i.e scratchspace has only space for 2 temporaries)
# The content scratchspace[2+k] is set at [k]P
# with scratchspace[0] untouched
if result == 1:
scratchspace[1] = a
else:
scratchspace[2] = a
for k in 2 ..< 1 shl result:
scratchspace[k+1]
a.setOne()
func powSquarings[F](
a: var F,
exponent: openArray[byte],
tmp: var F,
window: uint,
acc, acc_len: var uint,
e: var uint
): tuple[k, bits: uint] {.inline.}=
## Squaring step of exponentiation by squaring
## Get the next k bits in range [1, window)
## Square k times
## Returns the number of squarings done and the corresponding bits
##
## Updates iteration variables and accumulators
# Due to the high number of parameters,
# forcing this inline actually reduces the code size
#
# ⚠️: Extreme care should be used to not leak
# the exponent bits nor its real bitlength
# i.e. if the exponent is zero but encoded in a
# 256-bit integer, only "256" should leak
# as for some application like RSA
# the exponent might be the user secret key.
# Get the next bits
# acc/acc_len must be uint to avoid Nim runtime checks leaking bits
# acc/acc_len must be uint to avoid Nim runtime checks leaking bits
# e is public
var k = window
if acc_len < window:
if e < exponent.len:
acc = (acc shl 8) or exponent[e].uint
inc e
acc_len += 8
else: # Drained all exponent bits
k = acc_len
let bits = (acc shr (acc_len - k)) and ((1'u32 shl k) - 1)
acc_len -= k
# We have k bits and can do k squaring
for i in 0 ..< k:
a.square()
return (k, bits)
func powUnsafeExponent(
a: var ExtensionField,
exponent: openArray[byte],
scratchspace: var openArray[byte]
) =
## Extension field exponentiation r = a^exponent (mod p^m)
##
## Warning ⚠️ :
## This is an optimization for public exponent
## Otherwise bits of the exponent can be retrieved with:
## - memory access analysis
## - power analysis
## - timing analysis
# TODO: scratchspace[1] is unused when window > 1
let window = powPrologue(a, scratchspace)
var
acc, acc_len: uint
e = 0
while acc_len > 0 or e < exponent.len:
let (_, bits) = powSquarings(
a, exponent,
scratchspace[0], window,
acc, acc_len, e
)
## Warning ⚠️: Exposes the exponent bits
if bits != 0:
if window > 1:
scratchspace[0].prod(a, scratchspace[1+bits])
else:
# scratchspace[1] holds the original `a`
scratchspace[0].prod(a, scratchspace[1])
a = scratchspace[0]
# Square root
# -----------------------------------------------------------
#
# Warning ⚠️:
# p the characteristic, i.e. the prime modulus of the base field
# in extension field we require q = p^m be of special form
# i.e. q ≡ 3 (mod 4) or q ≡ 9 (mod 16)
#
# In Fp2 in particular p² ≡ 1 (mod 4) always hold
# and p² ≡ 5 (mod 8) is not possible
# if Fp2 = Fp[v]/(v² β) with β a quadratic non-residue in Fp
func isSquare*(a: QuadraticExt): SecretBool =
## Returns true if ``a`` is a square (quadratic residue) in 𝔽p2
##
## Assumes that the prime modulus ``p`` is public.
# Implementation:
#
# (a0, a1) = a in F(p^2)
# is_square(a) = is_square(|a|) over F(p)
# where |a| = a0^2 + a1^2
#
# This can be done recursively in an extension tower
#
# 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()
var tv1{.noInit.}, tv2{.noInit.}: typeof(a.c0)
tv1.square(a.c0) # a0²
tv2.square(a.c1) # - β a1² with β = 𝑖² in a complex extension field
tv1 += tv2 # a0 - (-1) a1²
result = tv1.isSquare()
func sqrt_if_square*(a: var QuadraticExt): SecretBool =
## 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
#
# 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()
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
t1 += t2 # a0 - (-1) a1²
result = t1.sqrt_if_square()
t2.sum(a.c0, t1)
t2.div2()
t3.diff(a.c0, t1)
t3.div2()
let quadResidTest = t2.isSquare()
t2.ccopy(t3, not quadResidTest)
t2.sqrt()
a.c0.ccopy(t2, result)
t2.double()
t1.inv(t2)
t1 *= a.c1
a.c1.ccopy(t1, result)

View File

@ -126,14 +126,14 @@ func square_generic(r: var QuadraticExt, a: QuadraticExt) =
# r0 <- (c0 + c1)(c0 + β c1)
r.c0.sum(a.c0, a.c1)
r.c1.sum(a.c0, β * a.c1)
r.c1.sum(a.c0, NonResidue * a.c1)
r.c0 *= r.c1
# r1 <- c0 c1
r.c1.prod(a.c0, a.c1)
# r0 = (c0 + c1)(c0 + β c1) - β c0c1 - c0c1
r.c0 -= β * r.c1
r.c0 -= NonResidue * r.c1
r.c0 -= r.c1
# r1 = 2 c0c1
@ -161,7 +161,7 @@ func prod_generic(r: var QuadraticExt, a, b: QuadraticExt) =
r.c1 -= t
# r0 <- a0 b0 + β a1 b1
r.c0 += β * t
r.c0 += NonResidue * t
# Exported symbols
# -------------------------------------------------------------------
@ -204,15 +204,15 @@ func inv*(r: var QuadraticExt, a: QuadraticExt) =
when r.fromComplexExtension():
v0 += v1
else:
v0 -= β * v1 # v0 = a0² - β a1² (the norm / squared magnitude of a)
v0 -= NonResidue * v1 # v0 = a0² - β a1² (the norm / squared magnitude of a)
# [1 Inv, 2 Sqr, 1 Add]
v1.inv(v0) # v1 = 1 / (a0² - β a1²)
v1.inv(v0) # v1 = 1 / (a0² - β a1²)
# [1 Inv, 2 Mul, 2 Sqr, 1 Add, 1 Neg]
r.c0.prod(a.c0, v1) # r0 = a0 / (a0² - β a1²)
v0.neg(v1) # v0 = -1 / (a0² - β a1²)
r.c1.prod(a.c1, v0) # r1 = -a1 / (a0² - β a1²)
r.c0.prod(a.c0, v1) # r0 = a0 / (a0² - β a1²)
v0.neg(v1) # v0 = -1 / (a0² - β a1²)
r.c1.prod(a.c1, v0) # r1 = -a1 / (a0² - β a1²)
func `*=`*(a: var QuadraticExt, b: QuadraticExt) {.inline.} =
## In-place multiplication

View File

@ -18,10 +18,10 @@ import
# -------------------------------------------------------------------
type
β* = object
## Non-Residue β
NonResidue* = object
## Non-Residue
##
## Placeholder for the appropriate quadratic or cubic non-residue
## Placeholder for the appropriate quadratic, cubic or sectic non-residue
CubicExt* = concept x
## Cubic Extension field concept
@ -38,7 +38,7 @@ type
x.c0 is BaseField
x.c1 is BaseField
ExtensionField = QuadraticExt or CubicExt
ExtensionField* = QuadraticExt or CubicExt
# Initialization
# -------------------------------------------------------------------
@ -56,6 +56,14 @@ func setOne*(a: var ExtensionField) =
else:
fA.setZero()
func fromBig*(a: var ExtensionField, src: BigInt) =
## Set ``a`` to the bigint value int eh extension field
for fieldName, fA in fieldPairs(a):
when fieldName == "c0":
fA.fromBig(src)
else:
fA.setZero()
# Comparison
# -------------------------------------------------------------------
@ -145,3 +153,77 @@ func diff*(r: var CubicExt, a, b: CubicExt) =
r.c0.diff(a.c0, b.c0)
r.c1.diff(a.c1, b.c1)
r.c2.diff(a.c2, b.c2)
# Multiplication by a small integer known at compile-time
# -------------------------------------------------------------------
func `*=`*(a: var ExtensionField, b: static int) {.inline.} =
## Multiplication by a small integer known at compile-time
const negate = b < 0
const b = if negate: -b
else: b
when negate:
a.neg(a)
when b == 0:
a.setZero()
elif b == 1:
return
elif b == 2:
a.double()
elif b == 3:
let t1 = a
a.double()
a += t1
elif b == 4:
a.double()
a.double()
elif b == 5:
let t1 = a
a.double()
a.double()
a += t1
elif b == 6:
a.double()
let t2 = a
a.double() # 4
a += t2
elif b == 7:
let t1 = a
a.double()
let t2 = a
a.double() # 4
a += t2
a += t1
elif b == 8:
a.double()
a.double()
a.double()
elif b == 9:
let t1 = a
a.double()
a.double()
a.double() # 8
a += t1
elif b == 10:
a.double()
let t2 = a
a.double()
a.double() # 8
a += t2
elif b == 11:
let t1 = a
a.double()
let t2 = a
a.double()
a.double() # 8
a += t2
a += t1
elif b == 12:
a.double()
a.double() # 4
let t4 = a
a.double() # 8
a += t4
else:
{.error: "Multiplication by this small int not implemented".}

View File

@ -9,13 +9,15 @@
import
./arithmetic,
./config/curves,
./io/io_fields,
./tower_field_extensions/[
tower_common,
quadratic_extensions,
cubic_extensions
cubic_extensions,
exponentiations
]
export tower_common, quadratic_extensions, cubic_extensions
export tower_common, quadratic_extensions, cubic_extensions, exponentiations
# 𝔽p2
# ----------------------------------------------------------------
@ -24,6 +26,8 @@ type
Fp2*[C: static Curve] = object
c0*, c1*: Fp[C]
β = NonResidue
template fromComplexExtension*[F](elem: F): static bool =
## Returns true if the input is a complex extension
## i.e. the irreducible polynomial chosen is
@ -46,6 +50,73 @@ func `*`*(_: typedesc[β], a: Fp): Fp {.inline, noInit.} =
result = a
result *= β
type
SexticNonResidue* = object
func `*=`*(a: var Fp2, _: typedesc[SexticNonResidue]) {.inline.} =
## Multiply an element of 𝔽p2 by the sextic non-residue
## chosen to construct the sextic twist
# Yet another const tuple unpacking bug
const u = Fp2.C.get_SNR_Fp2()[0] # Sextic non-residue to construct 𝔽p12
const v = Fp2.C.get_SNR_Fp2()[1]
const Beta = Fp2.C.get_QNR_Fp() # Quadratic non-residue to construct 𝔽p2
# ξ = u + v x
# and x² = β
#
# (c0 + c1 x) (u + v x) => u c0 + (u c0 + u c1)x + v c1 x²
# => u c0 + β v c1 + (v c0 + u c1) x
when a.fromComplexExtension() and u == 1 and v == 1:
let t = a.c0
a.c0 -= a.c1
a.c1 += t
else:
let a0 = a.c0
let a1 = a.c1
when a.fromComplexExtension():
a.c0.diff(u * a0, v * a1)
else:
a.c0.sum(u * a0, (Beta * v) * a1)
a.c1.sum(v * a0, u * a1)
func `/=`*(a: var Fp2, _: typedesc[SexticNonResidue]) {.inline.} =
## Multiply an element of 𝔽p by the quadratic non-residue
## chosen to construct sextic twist
# Yet another const tuple unpacking bug
const u = Fp2.C.get_SNR_Fp2()[0] # Sextic non-residue to construct 𝔽p12
const v = Fp2.C.get_SNR_Fp2()[1]
const Beta = Fp2.C.get_QNR_Fp() # Quadratic non-residue to construct 𝔽p2
# ξ = u + v x
# and x² = β
#
# (c0 + c1 x) / (u + v x) => (c0 + c1 x)(u - v x) / ((u + vx)(u-vx))
# => u c0 - v c1 x² + (u c1 - v c0) x / (u² - x²v²)
# => 1/(u² - βv²) * (uc0 - β v c1, u c1 - v c0)
# With β = 𝑖 = √-1
# 1/(u² + v²) * (u c0 + v c1, u c1 - v c0)
#
# With β = 𝑖 = √-1 and ξ = 1 + 𝑖
# 1/2 * (c0 + c1, c1 - c0)
when a.fromComplexExtension() and u == 1 and v == 1:
let t = a.c0
a.c0 += a.c1
a.c1 -= t
a.div2()
else:
let a0 = a.c0
let a1 = a.c1
const u2v2 = u*u - Beta*v*v # (u² - βv²)
# TODO can be precomputed (or precompute b/µ the twist coefficient)
# and use faster non-constant-time inversion in the VM
var u2v2inv {.noInit.}: a.c0.typeof
u2v2inv.fromUint(u2v2)
u2v2inv.inv()
a.c0.diff(u * a0, (Beta * v) * a1)
a.c1.diff(u * a1, v * a0)
a.c0 *= u2v2inv
a.c1 *= u2v2inv
# 𝔽p6
# ----------------------------------------------------------------
@ -82,8 +153,8 @@ func `*`*(_: typedesc[β], a: Fp2): Fp2 {.inline, noInit.} =
result.c1.sum(v * a.c0, u * a.c1 )
func `*=`*(a: var Fp2, _: typedesc[ξ]) {.inline.} =
## Multiply an element of 𝔽p by the quadratic non-residue
## chosen to construct 𝔽p2
## Multiply an element of 𝔽p2 by the quadratic non-residue
## chosen to construct 𝔽p6
# Yet another const tuple unpacking bug
const u = Fp2.C.get_CNR_Fp2()[0] # Cubic non-residue to construct 𝔽p6
const v = Fp2.C.get_CNR_Fp2()[1]

View File

@ -30,6 +30,8 @@ def compute_curve_characteristic(u_str):
else:
print(' Parameter u (hex): 0x' + u.hex())
print()
print(f' p mod 3: ' + str(p % 3))
print(f' p mod 4: ' + str(p % 4))
print(f' p mod 8: ' + str(p % 8))
@ -38,6 +40,14 @@ def compute_curve_characteristic(u_str):
print()
print(f' p^2 mod 3: ' + str(p^2 % 3))
print(f' p^2 mod 4: ' + str(p^2 % 4))
print(f' p^2 mod 8: ' + str(p^2 % 8))
print(f' p^2 mod 12: ' + str(p^2 % 12))
print(f' p^2 mod 16: ' + str(p^2 % 16))
print()
print(f' Endomorphism-based acceleration when p mod 3 == 1')
print(f' Endomorphism can be field multiplication by one of the non-trivial cube root of unity 𝜑')
print(f' Rationale:')

View File

@ -38,6 +38,14 @@ def compute_curve_characteristic(u_str):
print()
print(f' p^2 mod 3: ' + str(p^2 % 3))
print(f' p^2 mod 4: ' + str(p^2 % 4))
print(f' p^2 mod 8: ' + str(p^2 % 8))
print(f' p^2 mod 12: ' + str(p^2 % 12))
print(f' p^2 mod 16: ' + str(p^2 % 16))
print()
print(f' Endomorphism-based acceleration when p mod 3 == 1')
print(f' Endomorphism can be field multiplication by one of the non-trivial cube root of unity 𝜑')
print(f' Rationale:')

View File

@ -10,7 +10,7 @@ import
# Internals
../../constantine/config/[common, curves],
../../constantine/arithmetic,
../../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective]
../../constantine/elliptic/ec_weierstrass_projective
# Support files for testing Elliptic Curve arithmetic
# ------------------------------------------------------------------------------

View File

@ -12,8 +12,10 @@ import std/unittest,
../constantine/config/common,
../constantine/primitives
echo "\n------------------------------------------------------\n"
proc mainArith() =
suite "isZero":
suite "isZero" & " [" & $WordBitwidth & "-bit mode]":
test "isZero for zero":
var x: BigInt[128]
check: x.isZero().bool
@ -28,7 +30,7 @@ proc mainArith() =
var x = fromHex(BigInt[128], "0xFFFFFFFF_FFFFFFFF_FFFFFFFF_FFFFFFFF")
check: not x.isZero().bool
suite "Arithmetic operations - Addition":
suite "Arithmetic operations - Addition" & " [" & $WordBitwidth & "-bit mode]":
test "Adding 2 zeros":
var a = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000000")
let b = fromHex(BigInt[128], "0x00000000_00000000_00000000_00000000")
@ -128,7 +130,7 @@ proc mainArith() =
bool(a == c)
not bool(carry)
suite "BigInt + SecretWord":
suite "BigInt + SecretWord" & " [" & $WordBitwidth & "-bit mode]":
test "Addition limbs carry":
block: # P256 / 2
var a = BigInt[256].fromhex"0x7fffffff800000008000000000000000000000007fffffffffffffffffffffff"
@ -138,7 +140,7 @@ proc mainArith() =
discard a.add(SecretWord 1)
check: bool(a == expected)
suite "Multi-precision multiplication":
suite "Multi-precision multiplication" & " [" & $WordBitwidth & "-bit mode]":
test "Same size operand into double size result":
block:
var r: BigInt[256]
@ -178,7 +180,7 @@ proc mainArith() =
r.prod(b, a)
check: bool(r == expected)
suite "Multi-precision multiplication keeping only high words":
suite "Multi-precision multiplication keeping only high words" & " [" & $WordBitwidth & "-bit mode]":
test "Same size operand into double size result - discard first word":
block:
var r: BigInt[256]
@ -263,7 +265,7 @@ proc mainArith() =
r.prod_high_words(b, a, 2)
check: bool(r == expected)
suite "Modular operations - small modulus":
suite "Modular operations - small modulus" & " [" & $WordBitwidth & "-bit mode]":
# Vectors taken from Stint - https://github.com/status-im/nim-stint
test "100 mod 13":
# Test 1 word and more than 1 word
@ -312,7 +314,7 @@ proc mainArith() =
check:
bool(r == BigInt[8].fromUint(0'u8))
suite "Modular operations - small modulus - Stint specific failures highlighted by property-based testing":
suite "Modular operations - small modulus - Stint specific failures highlighted by property-based testing" & " [" & $WordBitwidth & "-bit mode]":
# Vectors taken from Stint - https://github.com/status-im/nim-stint
test "Modulo: 65696211516342324 mod 174261910798982":
let u = 65696211516342324'u64
@ -341,7 +343,7 @@ proc mainArith() =
bool(r == BigInt[40].fromUint(u mod v))
proc mainNeg() =
suite "Conditional negation":
suite "Conditional negation" & " [" & $WordBitwidth & "-bit mode]":
test "Conditional negation":
block:
var a = fromHex(BigInt[128], "0x12345678_FF11FFAA_00321321_CAFECAFE")
@ -439,7 +441,7 @@ proc mainNeg() =
bool(b == b2)
proc mainCopySwap() =
suite "Copy and Swap":
suite "Copy and Swap" & " [" & $WordBitwidth & "-bit mode]":
test "Conditional copy":
block:
var a = fromHex(BigInt[128], "0x12345678_FF11FFAA_00321321_CAFECAFE")
@ -485,7 +487,7 @@ proc mainCopySwap() =
bool(eB == b)
proc mainModularInverse() =
suite "Modular Inverse (with odd modulus)":
suite "Modular Inverse (with odd modulus)" & " [" & $WordBitwidth & "-bit mode]":
# Note: We don't define multi-precision multiplication
# because who needs it when you have Montgomery?
# ¯\_(ツ)_/¯

View File

@ -16,6 +16,7 @@ import
../constantine/arithmetic,
../constantine/primitives
echo "\n------------------------------------------------------\n"
# We test up to 1024-bit, more is really slow
var bitSizeRNG {.compileTime.} = initRand(1234)

View File

@ -17,6 +17,7 @@ import
../constantine/primitives,
../constantine/config/[common, type_bigint]
echo "\n------------------------------------------------------\n"
# We test up to 1024-bit, more is really slow
var bitSizeRNG {.compileTime.} = initRand(1234)

View File

@ -17,6 +17,7 @@ import
../constantine/primitives,
../constantine/config/[common, type_bigint]
echo "\n------------------------------------------------------\n"
# We test up to 1024-bit, more is really slow
var bitSizeRNG {.compileTime.} = initRand(1234)

View File

@ -10,12 +10,15 @@ import
# Standard library
std/unittest,
# Third-party
../constantine/config/common,
../constantine/io/io_bigints,
../constantine/arithmetic,
../constantine/primitives
echo "\n------------------------------------------------------\n"
proc main() =
suite "Bigints - Multiprecision modulo":
suite "Bigints - Multiprecision modulo" & " [" & $WordBitwidth & "-bit mode]":
test "bitsize 237 mod bitsize 192":
let a = BigInt[237].fromHex("0x123456789012345678901234567890123456789012345678901234567890")
let m = BigInt[192].fromHex("0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB")

View File

@ -17,6 +17,8 @@ import
# Test utilities
./support/ec_reference_scalar_mult
echo "\n------------------------------------------------------\n"
proc test(
id: int,
EC: typedesc[ECP_SWei_Proj],
@ -51,7 +53,7 @@ proc test(
doAssert: bool(Q == impl)
doAssert: bool(Q == endo)
suite "Scalar Multiplication (cofactor cleared): BLS12_381 implementation (and unsafe reference impl) vs SageMath":
suite "Scalar Multiplication (cofactor cleared): BLS12_381 implementation vs SageMath" & " [" & $WordBitwidth & "-bit mode]":
# Generated via sage sage/testgen_bls12_381.sage
test(
id = 1,

View File

@ -17,6 +17,8 @@ import
# Test utilities
./support/ec_reference_scalar_mult
echo "\n------------------------------------------------------\n"
proc test(
id: int,
EC: typedesc[ECP_SWei_Proj],
@ -51,7 +53,7 @@ proc test(
doAssert: bool(Q == impl)
doAssert: bool(Q == endo)
suite "Scalar Multiplication: BN254 implementation (and unsafe reference impl) vs SageMath":
suite "Scalar Multiplication: BN254 implementation vs SageMath" & " [" & $WordBitwidth & "-bit mode]":
# Generated via sage sage/testgen_bn254_snarks.sage
test(
id = 1,

399
tests/test_ec_template.nim Normal file
View File

@ -0,0 +1,399 @@
# 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.
# ############################################################
#
# Template tests for elliptic curve operations
#
# ############################################################
import
# Standard library
std/[unittest, times],
# Internals
../constantine/config/[common, curves],
../constantine/arithmetic,
../constantine/towers,
../constantine/io/io_bigints,
../constantine/elliptic/[ec_weierstrass_projective, ec_scalar_mul],
# Test utilities
../helpers/prng_unsafe,
./support/ec_reference_scalar_mult
proc run_EC_addition_tests*(
ec: typedesc,
Iters: static int,
moduleName: string
) =
# Random seed for reproducibility
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
rng.seed(seed)
echo "\n------------------------------------------------------\n"
echo moduleName, " xoshiro512** seed: ", seed
when ec.F is Fp:
const G1_or_G2 = "G1"
else:
const G1_or_G2 = "G2"
const testSuiteDesc = "Elliptic curve in Short Weierstrass form with projective coordinates"
suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]":
test "The infinity point is the neutral element w.r.t. to EC " & G1_or_G2 & " addition":
proc test(EC: typedesc, randZ: static bool) =
var inf {.noInit.}: EC
inf.setInf()
check: bool inf.isInf()
for _ in 0 ..< Iters:
var r{.noInit.}: EC
when randZ:
let P = rng.random_unsafe_with_randZ(EC)
else:
let P = rng.random_unsafe(EC)
r.sum(P, inf)
check: bool(r == P)
r.sum(inf, P)
check: bool(r == P)
test(ec, randZ = false)
test(ec, randZ = true)
test "Adding opposites gives an infinity point":
proc test(EC: typedesc, randZ: static bool) =
for _ in 0 ..< Iters:
var r{.noInit.}: EC
when randZ:
let P = rng.random_unsafe_with_randZ(EC)
else:
let P = rng.random_unsafe(EC)
var Q = P
Q.neg()
r.sum(P, Q)
check: bool r.isInf()
r.sum(Q, P)
check: bool r.isInf()
test(ec, randZ = false)
test(ec, randZ = true)
test "EC " & G1_or_G2 & " add is commutative":
proc test(EC: typedesc, randZ: static bool) =
for _ in 0 ..< Iters:
var r0{.noInit.}, r1{.noInit.}: EC
when randZ:
let P = rng.random_unsafe_with_randZ(EC)
let Q = rng.random_unsafe_with_randZ(EC)
else:
let P = rng.random_unsafe(EC)
let Q = rng.random_unsafe(EC)
r0.sum(P, Q)
r1.sum(Q, P)
check: bool(r0 == r1)
test(ec, randZ = false)
test(ec, randZ = true)
test "EC " & G1_or_G2 & " add is associative":
proc test(EC: typedesc, randZ: static bool) =
for _ in 0 ..< Iters:
when randZ:
let a = rng.random_unsafe_with_randZ(EC)
let b = rng.random_unsafe_with_randZ(EC)
let c = rng.random_unsafe_with_randZ(EC)
else:
let a = rng.random_unsafe(EC)
let b = rng.random_unsafe(EC)
let c = rng.random_unsafe(EC)
var tmp1{.noInit.}, tmp2{.noInit.}: EC
# r0 = (a + b) + c
tmp1.sum(a, b)
tmp2.sum(tmp1, c)
let r0 = tmp2
# r1 = a + (b + c)
tmp1.sum(b, c)
tmp2.sum(a, tmp1)
let r1 = tmp2
# r2 = (a + c) + b
tmp1.sum(a, c)
tmp2.sum(tmp1, b)
let r2 = tmp2
# r3 = a + (c + b)
tmp1.sum(c, b)
tmp2.sum(a, tmp1)
let r3 = tmp2
# r4 = (c + a) + b
tmp1.sum(c, a)
tmp2.sum(tmp1, b)
let r4 = tmp2
# ...
check:
bool(r0 == r1)
bool(r0 == r2)
bool(r0 == r3)
bool(r0 == r4)
test(ec, randZ = false)
test(ec, randZ = true)
test "EC " & G1_or_G2 & " double and EC " & G1_or_G2 & " add are consistent":
proc test(EC: typedesc, randZ: static bool) =
for _ in 0 ..< Iters:
when randZ:
let a = rng.random_unsafe_with_randZ(EC)
else:
let a = rng.random_unsafe(EC)
var r0{.noInit.}, r1{.noInit.}: EC
r0.double(a)
r1.sum(a, a)
check: bool(r0 == r1)
test(ec, randZ = false)
test(ec, randZ = true)
proc run_EC_mul_sanity_tests*(
ec: typedesc,
ItersMul: static int,
moduleName: string
) =
# Random seed for reproducibility
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
rng.seed(seed)
echo "\n------------------------------------------------------\n"
echo moduleName, " xoshiro512** seed: ", seed
when ec.F is Fp:
const G1_or_G2 = "G1"
else:
const G1_or_G2 = "G2"
const testSuiteDesc = "Elliptic curve in Short Weierstrass form with projective coordinates"
suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]":
test "EC " & G1_or_G2 & " mul [0]P == Inf":
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)
# zeroInit
var exponentCanonical: array[(bits+7) div 8, byte]
var
impl = a
reference = a
scratchSpace{.noInit.}: array[1 shl 4, EC]
impl.scalarMulGeneric(exponentCanonical, scratchSpace)
reference.unsafe_ECmul_double_add(exponentCanonical)
check:
bool(impl.isInf())
bool(reference.isInf())
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true)
test "EC " & G1_or_G2 & " mul [1]P == P":
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)
var exponent{.noInit.}: BigInt[bits]
exponent.setOne()
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
var
impl = a
reference = a
scratchSpace{.noInit.}: array[1 shl 4, EC]
impl.scalarMulGeneric(exponentCanonical, scratchSpace)
reference.unsafe_ECmul_double_add(exponentCanonical)
check:
bool(impl == a)
bool(reference == a)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true)
test "EC " & G1_or_G2 & " mul [2]P == P.double()":
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)
var doubleA{.noInit.}: EC
doubleA.double(a)
let exponent = BigInt[bits].fromUint(2)
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
var
impl = a
reference = a
scratchSpace{.noInit.}: array[1 shl 4, EC]
impl.scalarMulGeneric(exponentCanonical, scratchSpace)
reference.unsafe_ECmul_double_add(exponentCanonical)
check:
bool(impl == doubleA)
bool(reference == doubleA)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true)
proc run_EC_mul_distributive_tests*(
ec: typedesc,
ItersMul: static int,
moduleName: string
) =
# Random seed for reproducibility
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
rng.seed(seed)
echo "\n------------------------------------------------------\n"
echo moduleName, " xoshiro512** seed: ", seed
when ec.F is Fp:
const G1_or_G2 = "G1"
else:
const G1_or_G2 = "G2"
const testSuiteDesc = "Elliptic curve in Short Weierstrass form with projective coordinates"
suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]":
test "EC " & G1_or_G2 & " mul is distributive over EC add":
proc test(EC: typedesc, bits: static int, randZ: static bool) =
for _ in 0 ..< ItersMul:
when randZ:
let a = rng.random_unsafe_with_randZ(EC)
let b = rng.random_unsafe_with_randZ(EC)
else:
let a = rng.random_unsafe(EC)
let b = rng.random_unsafe_with_randZ(EC)
let exponent = rng.random_unsafe(BigInt[bits])
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
# [k](a + b) - Factorized
var
fImpl{.noInit.}: EC
fReference{.noInit.}: EC
scratchSpace{.noInit.}: array[1 shl 4, EC]
fImpl.sum(a, b)
fReference.sum(a, b)
fImpl.scalarMulGeneric(exponentCanonical, scratchSpace)
fReference.unsafe_ECmul_double_add(exponentCanonical)
# [k]a + [k]b - Distributed
var kaImpl = a
var kaRef = a
kaImpl.scalarMulGeneric(exponentCanonical, scratchSpace)
kaRef.unsafe_ECmul_double_add(exponentCanonical)
var kbImpl = b
var kbRef = b
kbImpl.scalarMulGeneric(exponentCanonical, scratchSpace)
kbRef.unsafe_ECmul_double_add(exponentCanonical)
var kakbImpl{.noInit.}, kakbRef{.noInit.}: EC
kakbImpl.sum(kaImpl, kbImpl)
kakbRef.sum(kaRef, kbRef)
check:
bool(fImpl == kakbImpl)
bool(fReference == kakbRef)
bool(fImpl == fReference)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true)
proc run_EC_mul_vs_ref_impl*(
ec: typedesc,
ItersMul: static int,
moduleName: string
) =
# Random seed for reproducibility
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
rng.seed(seed)
echo "\n------------------------------------------------------\n"
echo moduleName, " xoshiro512** seed: ", seed
when ec.F is Fp:
const G1_or_G2 = "G1"
else:
const G1_or_G2 = "G2"
const testSuiteDesc = "Elliptic curve in Short Weierstrass form with projective coordinates"
suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]":
test "EC " & G1_or_G2 & " mul constant-time is equivalent to a simple double-and-add algorithm":
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 = rng.random_unsafe(BigInt[bits])
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
var
impl = a
reference = a
scratchSpace{.noInit.}: array[1 shl 4, EC]
impl.scalarMulGeneric(exponentCanonical, scratchSpace)
reference.unsafe_ECmul_double_add(exponentCanonical)
check: bool(impl == reference)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = false)
test(ec, bits = ec.F.C.getCurveOrderBitwidth(), randZ = true)

View File

@ -1,381 +0,0 @@
# 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/io/io_bigints,
../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective, ec_scalar_mul],
# Test utilities
../helpers/prng_unsafe,
./support/ec_reference_scalar_mult
const
Iters = 128
ItersMul = Iters div 4
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
rng.seed(seed)
echo "test_ec_weierstrass_projective_g1 xoshiro512** seed: ", seed
# Import: wrap in elliptic curve tests in small procedures
# otherwise they will become globals,
# and will create binary size issues.
# Also due to Nim stack scanning,
# having too many elements on the stack (a couple kB)
# will significantly slow down testing (100x is possible)
suite "Elliptic curve in Short Weierstrass form y² = x³ + a x + b with projective coordinates (X, Y, Z): Y²Z = X³ + aXZ² + bZ³ i.e. X = xZ, Y = yZ":
test "The infinity point is the neutral element w.r.t. to EC addition":
proc test(F: typedesc, randZ: static bool) =
var inf {.noInit.}: ECP_SWei_Proj[F]
inf.setInf()
check: bool inf.isInf()
for _ in 0 ..< Iters:
var r{.noInit.}: ECP_SWei_Proj[F]
when randZ:
let P = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
else:
let P = rng.random_unsafe(ECP_SWei_Proj[F])
r.sum(P, inf)
check: bool(r == P)
r.sum(inf, P)
check: bool(r == P)
test(Fp[BN254_Snarks], randZ = false)
test(Fp[BN254_Snarks], randZ = true)
test(Fp[BLS12_381], randZ = false)
test(Fp[BLS12_381], randZ = true)
test "Adding opposites gives an infinity point":
proc test(F: typedesc, randZ: static bool) =
for _ in 0 ..< Iters:
var r{.noInit.}: ECP_SWei_Proj[F]
when randZ:
let P = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
else:
let P = rng.random_unsafe(ECP_SWei_Proj[F])
var Q = P
Q.neg()
r.sum(P, Q)
check: bool r.isInf()
r.sum(Q, P)
check: bool r.isInf()
test(Fp[BN254_Snarks], randZ = false)
test(Fp[BN254_Snarks], randZ = true)
test(Fp[BLS12_381], randZ = false)
test(Fp[BLS12_381], randZ = true)
test "EC add is commutative":
proc test(F: typedesc, randZ: static bool) =
for _ in 0 ..< Iters:
var r0{.noInit.}, r1{.noInit.}: ECP_SWei_Proj[F]
when randZ:
let P = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
let Q = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
else:
let P = rng.random_unsafe(ECP_SWei_Proj[F])
let Q = rng.random_unsafe(ECP_SWei_Proj[F])
r0.sum(P, Q)
r1.sum(Q, P)
check: bool(r0 == r1)
test(Fp[BN254_Snarks], randZ = false)
test(Fp[BN254_Snarks], randZ = true)
test(Fp[BLS12_381], randZ = false)
test(Fp[BLS12_381], randZ = true)
test "EC add is associative":
proc test(F: typedesc, randZ: static bool) =
for _ in 0 ..< Iters:
when randZ:
let a = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
let b = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
let c = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
else:
let a = rng.random_unsafe(ECP_SWei_Proj[F])
let b = rng.random_unsafe(ECP_SWei_Proj[F])
let c = rng.random_unsafe(ECP_SWei_Proj[F])
var tmp1{.noInit.}, tmp2{.noInit.}: ECP_SWei_Proj[F]
# r0 = (a + b) + c
tmp1.sum(a, b)
tmp2.sum(tmp1, c)
let r0 = tmp2
# r1 = a + (b + c)
tmp1.sum(b, c)
tmp2.sum(a, tmp1)
let r1 = tmp2
# r2 = (a + c) + b
tmp1.sum(a, c)
tmp2.sum(tmp1, b)
let r2 = tmp2
# r3 = a + (c + b)
tmp1.sum(c, b)
tmp2.sum(a, tmp1)
let r3 = tmp2
# r4 = (c + a) + b
tmp1.sum(c, a)
tmp2.sum(tmp1, b)
let r4 = tmp2
# ...
check:
bool(r0 == r1)
bool(r0 == r2)
bool(r0 == r3)
bool(r0 == r4)
test(Fp[BN254_Snarks], randZ = false)
test(Fp[BN254_Snarks], randZ = true)
test(Fp[BLS12_381], randZ = false)
test(Fp[BLS12_381], randZ = true)
test "EC double and EC add are consistent":
proc test(F: typedesc, randZ: static bool) =
for _ in 0 ..< Iters:
when randZ:
let a = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
else:
let a = rng.random_unsafe(ECP_SWei_Proj[F])
var r0{.noInit.}, r1{.noInit.}: ECP_SWei_Proj[F]
r0.double(a)
r1.sum(a, a)
check: bool(r0 == r1)
test(Fp[BN254_Snarks], randZ = false)
test(Fp[BN254_Snarks], randZ = true)
test(Fp[BLS12_381], randZ = false)
test(Fp[BLS12_381], randZ = true)
const BN254_Snarks_order_bits = BN254_Snarks.getCurveOrderBitwidth()
const BLS12_381_order_bits = BLS12_381.getCurveOrderBitwidth()
test "EC mul [0]P == Inf":
proc test(F: typedesc, bits: static int, randZ: static bool) =
for _ in 0 ..< ItersMul:
when randZ:
let a = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
else:
let a = rng.random_unsafe(ECP_SWei_Proj[F])
# zeroInit
var exponentCanonical: array[(bits+7) div 8, byte]
var
impl = a
reference = a
scratchSpace{.noInit.}: array[1 shl 4, ECP_SWei_Proj[F]]
impl.scalarMulGeneric(exponentCanonical, scratchSpace)
reference.unsafe_ECmul_double_add(exponentCanonical)
check:
bool(impl.isInf())
bool(reference.isInf())
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = false)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = true)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = false)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = true)
test "EC mul [Order]P == Inf":
proc test(F: typedesc, bits: static int, randZ: static bool) =
for _ in 0 ..< ItersMul:
when randZ:
let a = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
else:
let a = rng.random_unsafe(ECP_SWei_Proj[F])
let exponent = F.C.getCurveOrder()
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
var
impl = a
reference = a
scratchSpace{.noInit.}: array[1 shl 4, ECP_SWei_Proj[F]]
impl.scalarMulGeneric(exponentCanonical, scratchSpace)
reference.unsafe_ECmul_double_add(exponentCanonical)
check:
bool(impl.isInf())
bool(reference.isInf())
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = false)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = true)
# TODO: BLS12 is using a subgroup of order "r" such as r*h = CurveOrder
# with h the curve cofactor
# instead of the full group
# test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = false)
# test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = true)
test "EC mul [1]P == P":
proc test(F: typedesc, bits: static int, randZ: static bool) =
for _ in 0 ..< ItersMul:
when randZ:
let a = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
else:
let a = rng.random_unsafe(ECP_SWei_Proj[F])
var exponent{.noInit.}: BigInt[bits]
exponent.setOne()
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
var
impl = a
reference = a
scratchSpace{.noInit.}: array[1 shl 4, ECP_SWei_Proj[F]]
impl.scalarMulGeneric(exponentCanonical, scratchSpace)
reference.unsafe_ECmul_double_add(exponentCanonical)
check:
bool(impl == a)
bool(reference == a)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = false)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = true)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = false)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = true)
test "EC mul [2]P == P.double()":
proc test(F: typedesc, bits: static int, randZ: static bool) =
for _ in 0 ..< ItersMul:
when randZ:
let a = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
else:
let a = rng.random_unsafe(ECP_SWei_Proj[F])
var doubleA{.noInit.}: ECP_SWei_Proj[F]
doubleA.double(a)
let exponent = BigInt[bits].fromUint(2)
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
var
impl = a
reference = a
scratchSpace{.noInit.}: array[1 shl 4, ECP_SWei_Proj[F]]
impl.scalarMulGeneric(exponentCanonical, scratchSpace)
reference.unsafe_ECmul_double_add(exponentCanonical)
check:
bool(impl == doubleA)
bool(reference == doubleA)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = false)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = true)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = false)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = true)
test "EC mul is distributive over EC add":
proc test(F: typedesc, bits: static int, randZ: static bool) =
for _ in 0 ..< ItersMul:
when randZ:
let a = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
let b = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
else:
let a = rng.random_unsafe(ECP_SWei_Proj[F])
let b = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
let exponent = rng.random_unsafe(BigInt[bits])
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
# [k](a + b) - Factorized
var
fImpl{.noInit.}: ECP_SWei_Proj[F]
fReference{.noInit.}: ECP_SWei_Proj[F]
scratchSpace{.noInit.}: array[1 shl 4, ECP_SWei_Proj[F]]
fImpl.sum(a, b)
fReference.sum(a, b)
fImpl.scalarMulGeneric(exponentCanonical, scratchSpace)
fReference.unsafe_ECmul_double_add(exponentCanonical)
# [k]a + [k]b - Distributed
var kaImpl = a
var kaRef = a
kaImpl.scalarMulGeneric(exponentCanonical, scratchSpace)
kaRef.unsafe_ECmul_double_add(exponentCanonical)
var kbImpl = b
var kbRef = b
kbImpl.scalarMulGeneric(exponentCanonical, scratchSpace)
kbRef.unsafe_ECmul_double_add(exponentCanonical)
var kakbImpl{.noInit.}, kakbRef{.noInit.}: ECP_SWei_Proj[F]
kakbImpl.sum(kaImpl, kbImpl)
kakbRef.sum(kaRef, kbRef)
check:
bool(fImpl == kakbImpl)
bool(fReference == kakbRef)
bool(fImpl == fReference)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = false)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = true)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = false)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = true)
test "EC mul constant-time is equivalent to a simple double-and-add algorithm":
proc test(F: typedesc, bits: static int, randZ: static bool) =
for _ in 0 ..< ItersMul:
when randZ:
let a = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
else:
let a = rng.random_unsafe(ECP_SWei_Proj[F])
let exponent = rng.random_unsafe(BigInt[bits])
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
var
impl = a
reference = a
scratchSpace{.noInit.}: array[1 shl 4, ECP_SWei_Proj[F]]
impl.scalarMulGeneric(exponentCanonical, scratchSpace)
reference.unsafe_ECmul_double_add(exponentCanonical)
check: bool(impl == reference)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = false)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = true)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = false)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = true)

View File

@ -0,0 +1,34 @@
# 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/io/io_bigints,
../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective],
# Test utilities
../helpers/prng_unsafe,
./test_ec_template
const
Iters = 128
run_EC_addition_tests(
ec = ECP_SWei_Proj[Fp[BN254_Snarks]],
Iters = Iters,
moduleName = "test_ec_weierstrass_projective_g1_add_double_" & $BN254_Snarks
)
run_EC_addition_tests(
ec = ECP_SWei_Proj[Fp[BLS12_381]],
Iters = Iters,
moduleName = "test_ec_weierstrass_projective_g1_add_double_" & $BLS12_381
)

View File

@ -0,0 +1,36 @@
# 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/io/io_bigints,
../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective, ec_scalar_mul],
# Test utilities
../helpers/prng_unsafe,
./support/ec_reference_scalar_mult,
./test_ec_template
const
Iters = 128
ItersMul = Iters div 4
run_EC_mul_distributive_tests(
ec = ECP_SWei_Proj[Fp[BN254_Snarks]],
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g1_mul_distributive_" & $BN254_Snarks
)
run_EC_mul_distributive_tests(
ec = ECP_SWei_Proj[Fp[BLS12_381]],
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g1_mul_distributive_" & $BLS12_381
)

View File

@ -0,0 +1,74 @@
# 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/io/io_bigints,
../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective, ec_scalar_mul],
# Test utilities
../helpers/prng_unsafe,
./support/ec_reference_scalar_mult,
./test_ec_template
const
Iters = 128
ItersMul = Iters div 4
run_EC_mul_sanity_tests(
ec = ECP_SWei_Proj[Fp[BN254_Snarks]],
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g1_mul_sanity_" & $BN254_Snarks
)
run_EC_mul_sanity_tests(
ec = ECP_SWei_Proj[Fp[BLS12_381]],
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g1_mul_sanity_" & $BLS12_381
)
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 = EC.F.C.getCurveOrder()
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
var
impl = a
reference = a
scratchSpace{.noInit.}: array[1 shl 4, EC]
impl.scalarMulGeneric(exponentCanonical, scratchSpace)
reference.unsafe_ECmul_double_add(exponentCanonical)
check:
bool(impl.isInf())
bool(reference.isInf())
test(ECP_SWei_Proj[Fp[BN254_Snarks]], bits = BN254_Snarks.getCurveOrderBitwidth(), randZ = false)
test(ECP_SWei_Proj[Fp[BN254_Snarks]], bits = BN254_Snarks.getCurveOrderBitwidth(), randZ = true)
# TODO: BLS12 is using a subgroup of order "r" such as r*h = CurveOrder
# with h the curve cofactor
# instead of the full group
# test(Fp[BLS12_381], bits = BLS12_381.getCurveOrderBitwidth(), randZ = false)
# test(Fp[BLS12_381], bits = BLS12_381.getCurveOrderBitwidth(), randZ = true)

View File

@ -0,0 +1,36 @@
# 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/io/io_bigints,
../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective, ec_scalar_mul],
# Test utilities
../helpers/prng_unsafe,
./support/ec_reference_scalar_mult,
./test_ec_template
const
Iters = 128
ItersMul = Iters div 4
run_EC_mul_vs_ref_impl(
ec = ECP_SWei_Proj[Fp[BN254_Snarks]],
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g1_mul_vs_ref_" & $BN254_Snarks
)
run_EC_mul_vs_ref_impl(
ec = ECP_SWei_Proj[Fp[BLS12_381]],
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g1_mul_vs_ref_" & $BLS12_381
)

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,
./test_ec_template
const
Iters = 128
run_EC_addition_tests(
ec = ECP_SWei_Proj[Fp2[BLS12_381]],
Iters = Iters,
moduleName = "test_ec_weierstrass_projective_g2_add_double_" & $BLS12_381
)

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,
./test_ec_template
const
Iters = 128
run_EC_addition_tests(
ec = ECP_SWei_Proj[Fp2[BN254_Snarks]],
Iters = Iters,
moduleName = "test_ec_weierstrass_projective_g2_add_double_" & $BN254_Snarks
)

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,
./test_ec_template
const
Iters = 128
ItersMul = Iters div 4
run_EC_mul_distributive_tests(
ec = ECP_SWei_Proj[Fp2[BLS12_381]],
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g2_mul_distributive_" & $BLS12_381
)

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,
./test_ec_template
const
Iters = 128
ItersMul = Iters div 4
run_EC_mul_distributive_tests(
ec = ECP_SWei_Proj[Fp2[BN254_Snarks]],
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g2_mul_distributive_" & $BN254_Snarks
)

View File

@ -0,0 +1,65 @@
# 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,
./test_ec_template
const
Iters = 128
ItersMul = Iters div 4
run_EC_mul_sanity_tests(
ec = ECP_SWei_Proj[Fp2[BLS12_381]],
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g2_mul_sanity_" & $BLS12_381
)
# 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 exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
# exponentCanonical.exportRawUint(exponent, bigEndian)
#
# var
# impl = a
# reference = a
# scratchSpace{.noInit.}: array[1 shl 4, EC]
#
# impl.scalarMulGeneric(exponentCanonical, scratchSpace)
# reference.unsafe_ECmul_double_add(exponentCanonical)
#
# check:
# bool(impl.isInf())
# bool(reference.isInf())
#
# test(ECP_SWei_Proj[Fp2[BLS12_381]], bits = BLS12_381.getCurveOrderBitwidth(), randZ = false)
# test(ECP_SWei_Proj[Fp2[BLS12_381]], bits = BLS12_381.getCurveOrderBitwidth(), randZ = true)

View File

@ -0,0 +1,65 @@
# 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,
./test_ec_template
const
Iters = 128
ItersMul = Iters div 4
run_EC_mul_sanity_tests(
ec = ECP_SWei_Proj[Fp2[BN254_Snarks]],
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g2_mul_sanity_" & $BN254_Snarks
)
# TODO: the order on E'(Fp2) for BN curve is r(2pr) 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 exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
# exponentCanonical.exportRawUint(exponent, bigEndian)
#
# var
# impl = a
# reference = a
# scratchSpace{.noInit.}: array[1 shl 4, EC]
#
# impl.scalarMulGeneric(exponentCanonical, scratchSpace)
# reference.unsafe_ECmul_double_add(exponentCanonical)
#
# check:
# bool(impl.isInf())
# bool(reference.isInf())
#
# test(ECP_SWei_Proj[Fp2[BN254_Snarks]], bits = BN254_Snarks.getCurveOrderBitwidth(), randZ = false)
# test(ECP_SWei_Proj[Fp2[BN254_Snarks]], bits = BN254_Snarks.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,
./test_ec_template
const
Iters = 128
ItersMul = Iters div 4
run_EC_mul_vs_ref_impl(
ec = ECP_SWei_Proj[Fp2[BLS12_381]],
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g2_mul_vs_ref_" & $BLS12_381
)

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,
./test_ec_template
const
Iters = 128
ItersMul = Iters div 4
run_EC_mul_vs_ref_impl(
ec = ECP_SWei_Proj[Fp2[BN254_Snarks]],
ItersMul = ItersMul,
moduleName = "test_ec_weierstrass_projective_g2_mul_vs_ref_" & $BN254_Snarks
)

View File

@ -13,6 +13,8 @@ import std/unittest,
static: doAssert defined(testingCurves), "This modules requires the -d:testingCurves compile option"
echo "\n------------------------------------------------------\n"
proc main() =
suite "Basic arithmetic over finite fields":
test "Addition mod 101":

View File

@ -21,6 +21,7 @@ const Iters = 128
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
rng.seed(seed)
echo "\n------------------------------------------------------\n"
echo "test_finite_fields_mulsquare xoshiro512** seed: ", seed
static: doAssert defined(testingCurves), "This modules requires the -d:testingCurves compile option"
@ -76,7 +77,7 @@ proc sanity(C: static Curve) =
bool(n == expected)
proc mainSanity() =
suite "Modular squaring is consistent with multiplication on special elements":
suite "Modular squaring is consistent with multiplication on special elements" & " [" & $WordBitwidth & "-bit mode]":
sanity Fake101
sanity Mersenne61
sanity Mersenne127
@ -87,7 +88,7 @@ proc mainSanity() =
mainSanity()
proc mainSelectCases() =
suite "Modular Squaring: selected tricky cases":
suite "Modular Squaring: selected tricky cases" & " [" & $WordBitwidth & "-bit mode]":
test "P-256 [FastSquaring = " & $P256.canUseNoCarryMontySquare & "]":
block:
# Triggered an issue in the (t[N+1], t[N]) = t[N] + (A1, A0)
@ -114,7 +115,7 @@ proc randomCurve(C: static Curve) =
doAssert bool(r_mul == r_sqr)
suite "Random Modular Squaring is consistent with Modular Multiplication":
suite "Random Modular Squaring is consistent with Modular Multiplication" & " [" & $WordBitwidth & "-bit mode]":
test "Random squaring mod P-224 [FastSquaring = " & $P224.canUseNoCarryMontySquare & "]":
for _ in 0 ..< Iters:
randomCurve(P224)

View File

@ -10,6 +10,7 @@ import
# Standard library
std/[unittest, times],
# Internal
../constantine/config/common,
../constantine/arithmetic,
../constantine/io/[io_bigints, io_fields],
../constantine/config/curves,
@ -24,10 +25,11 @@ const Iters = 512
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
rng.seed(seed)
echo "\n------------------------------------------------------\n"
echo "test_finite_fields_powinv xoshiro512** seed: ", seed
proc main() =
suite "Modular exponentiation over finite fields":
suite "Modular exponentiation over finite fields" & " [" & $WordBitwidth & "-bit mode]":
test "n² mod 101":
let exponent = BigInt[64].fromUint(2'u64)
@ -181,7 +183,7 @@ proc main() =
testRandomDiv2 BLS12_461
testRandomDiv2 BN462
suite "Modular inversion over prime fields":
suite "Modular inversion over prime fields" & " [" & $WordBitwidth & "-bit mode]":
test "Specific tests on Fp[BLS12_381]":
block: # No inverse exist for 0 --> should return 0 for projective/jacobian to affine coordinate conversion
var r, x: Fp[BLS12_381]

View File

@ -22,6 +22,7 @@ const Iters = 128
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
rng.seed(seed)
echo "\n------------------------------------------------------\n"
echo "test_finite_fields_sqrt xoshiro512** seed: ", seed
static: doAssert defined(testingCurves), "This modules requires the -d:testingCurves compile option"
@ -59,11 +60,11 @@ proc exhaustiveCheck_p3mod4(C: static Curve, modulus: static int) =
var a2 = a
check:
bool a.isSquare()
bool a.sqrt_if_square_p3mod4()
bool a.sqrt_if_square()
# 2 different code paths have the same result
# (despite 2 square roots existing per square)
a2.sqrt_p3mod4()
a2.sqrt()
check: bool(a == a2)
var r_bytes: array[8, byte]
@ -78,7 +79,7 @@ proc exhaustiveCheck_p3mod4(C: static Curve, modulus: static int) =
check:
bool not a.isSquare()
bool not a.sqrt_if_square_p3mod4()
bool not a.sqrt_if_square()
bool (a == a2) # a shouldn't be modified
proc randomSqrtCheck_p3mod4(C: static Curve) =
@ -97,15 +98,15 @@ proc randomSqrtCheck_p3mod4(C: static Curve) =
bool a2.isSquare()
var r, s = a2
r.sqrt_p3mod4()
let ok = s.sqrt_if_square_p3mod4()
r.sqrt()
let ok = s.sqrt_if_square()
check:
bool ok
bool(r == s)
bool(r == a or r == na)
proc main() =
suite "Modular square root":
suite "Modular square root" & " [" & $WordBitwidth & "-bit mode]":
exhaustiveCheck_p3mod4 Fake103, 103
exhaustiveCheck_p3mod4 Fake10007, 10007
exhaustiveCheck_p3mod4 Fake65519, 65519

View File

@ -17,6 +17,8 @@ import
../constantine/primitives,
../constantine/config/curves
echo "\n------------------------------------------------------\n"
var RNG {.compileTime.} = initRand(1234)
const CurveParams = [
P224,

View File

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

View File

@ -0,0 +1,26 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
# Internals
../constantine/towers,
../constantine/config/curves,
# Test utilities
./test_fp_tower_template
const TestCurves = [
BLS12_381
]
runTowerTests(
ExtDegree = 12,
Iters = 128,
TestCurves = TestCurves,
moduleName = "test_fp12_" & $BLS12_381,
testSuiteDesc = "𝔽p12 = 𝔽p6[w] " & $BLS12_381
)

View File

@ -14,20 +14,13 @@ import
./test_fp_tower_template
const TestCurves = [
# BN254_Nogami
BN254_Snarks,
BLS12_377,
BLS12_381,
# BN446
# FKM12_447
# BLS12_461
# BN462
]
runTowerTests(
ExtDegree = 12,
Iters = 128,
TestCurves = TestCurves,
moduleName = "test_fp12",
testSuiteDesc = "𝔽p12 = 𝔽p6[w] (irreducible polynomial w²-γ = 0) -> 𝔽p12 point (a, b) with coordinate a + bw and γ quadratic non-residue in 𝔽p6"
moduleName = "test_fp12_" & $BN254_Snarks,
testSuiteDesc = "𝔽p12 = 𝔽p6[w] " & $BN254_Snarks
)

56
tests/test_fp2_sqrt.nim Normal file
View File

@ -0,0 +1,56 @@
# 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/[tables, unittest, times],
# Internals
../constantine/config/common,
../constantine/[arithmetic, primitives],
../constantine/towers,
../constantine/config/curves,
# Test utilities
../helpers/prng_unsafe
const Iters = 128
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
rng.seed(seed)
echo "\n------------------------------------------------------\n"
echo "test_fp2_sqrt xoshiro512** seed: ", seed
proc randomSqrtCheck_p3mod4(C: static Curve) =
test "[𝔽p2] Random square root check for p ≡ 3 (mod 4) on " & $Curve(C):
for _ in 0 ..< Iters:
let a = rng.random_unsafe(Fp2[C])
var na{.noInit.}: Fp2[C]
na.neg(a)
var a2 = a
var na2 = na
a2.square()
na2.square()
check:
bool a2 == na2
bool a2.isSquare()
var r, s = a2
# r.sqrt()
let ok = s.sqrt_if_square()
check:
bool ok
# bool(r == s)
bool(s == a or s == na)
proc main() =
suite "Modular square root" & " [" & $WordBitwidth & "-bit mode]":
randomSqrtCheck_p3mod4 BN254_Snarks
randomSqrtCheck_p3mod4 BLS12_381
main()

View File

@ -0,0 +1,26 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
# Internals
../constantine/towers,
../constantine/config/curves,
# Test utilities
./test_fp_tower_template
const TestCurves = [
BLS12_377,
]
runTowerTests(
ExtDegree = 6,
Iters = 128,
TestCurves = TestCurves,
moduleName = "test_fp6_" & $BLS12_377,
testSuiteDesc = "𝔽p6 = 𝔽p2[v] " & $BLS12_377
)

View File

@ -0,0 +1,26 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
# Internals
../constantine/towers,
../constantine/config/curves,
# Test utilities
./test_fp_tower_template
const TestCurves = [
BLS12_381
]
runTowerTests(
ExtDegree = 6,
Iters = 128,
TestCurves = TestCurves,
moduleName = "test_fp6_" & $BLS12_381,
testSuiteDesc = "𝔽p6 = 𝔽p2[v] " & $BLS12_381
)

View File

@ -14,20 +14,13 @@ import
./test_fp_tower_template
const TestCurves = [
# BN254_Nogami
BN254_Snarks,
BLS12_377,
BLS12_381,
# BN446
# FKM12_447
# BLS12_461
# BN462
]
runTowerTests(
ExtDegree = 6,
Iters = 128,
TestCurves = TestCurves,
moduleName = "test_fp6",
testSuiteDesc = "𝔽p6 = 𝔽p2[v] (irreducible polynomial v³-ξ = 0) -> 𝔽p6 point (a, b, c) with coordinate a + bv + cv² and ξ cubic non-residue in 𝔽p2"
moduleName = "test_fp6_" & $BN254_Snarks,
testSuiteDesc = "𝔽p6 = 𝔽p2[w] " & $BN254_Snarks
)

View File

@ -23,6 +23,8 @@ import
# Test utilities
../helpers/[prng_unsafe, static_for]
echo "\n------------------------------------------------------\n"
template ExtField(degree: static int, curve: static Curve): untyped =
when degree == 2:
Fp2[curve]
@ -47,7 +49,7 @@ proc runTowerTests*[N](
rng.seed(seed)
echo moduleName, " xoshiro512** seed: ", seed
suite testSuiteDesc:
suite testSuiteDesc & " [" & $WordBitwidth & "-bit mode]":
test "Comparison sanity checks":
proc test(Field: typedesc) =
var z, o {.noInit.}: Field

View File

@ -16,12 +16,13 @@ import std/[unittest,times],
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
rng.seed(seed)
echo "\n------------------------------------------------------\n"
echo "test_io_bigints xoshiro512** seed: ", seed
type T = BaseType
proc main() =
suite "IO":
suite "IO - BigInt" & " [" & $WordBitwidth & "-bit mode]":
test "Parsing raw integers":
block: # Sanity check
let x = 0'u64

View File

@ -17,10 +17,11 @@ import std/[unittest, times],
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
rng.seed(seed)
echo "\n------------------------------------------------------\n"
echo "test_io_fields xoshiro512** seed: ", seed
proc main() =
suite "IO - Finite fields":
suite "IO - Finite fields" & " [" & $WordBitwidth & "-bit mode]":
test "Parsing and serializing round-trip on uint64":
# 101 ---------------------------------
block:

View File

@ -7,10 +7,13 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import std/unittest,
../constantine/config/common,
../constantine/arithmetic,
../constantine/config/curves,
../constantine/io/[io_bigints, io_fields]
echo "\n------------------------------------------------------\n"
proc checkCubeRootOfUnity(curve: static Curve) =
test $curve & " cube root of unity (mod p)":
var cru = curve.getCubicRootOfUnity_mod_p()
@ -30,7 +33,7 @@ proc checkCubeRootOfUnity(curve: static Curve) =
check: bool r.isOne()
proc main() =
suite "Sanity checks on precomputed values":
suite "Sanity checks on precomputed values" & " [" & $WordBitwidth & "-bit mode]":
checkCubeRootOfUnity(BN254_Snarks)
# checkCubeRootOfUnity(BLS12_381)

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/[unittest, times, math],
../constantine/config/common,
../constantine/primitives,
../helpers/prng_unsafe
@ -14,13 +15,14 @@ import std/[unittest, times, math],
var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
rng.seed(seed)
echo "\n------------------------------------------------------\n"
echo "test_primitives xoshiro512** seed: ", seed
template undistinct[T](x: Ct[T]): T =
T(x)
proc main() =
suite "Constant-time unsigned integers":
suite "Constant-time unsigned integers" & " [" & $WordBitwidth & "-bit mode]":
test "High - getting the biggest representable number":
check:
high(Ct[byte]).undistinct == 0xFF.byte