Fr: Finite Field parametrized by the curve order (#115)

* Introduce Fr type: finite field over curve order. Need workaround for https://github.com/nim-lang/Nim/issues/16774

* Split curve properties into core and derived

* Attach field properties to an instantiated field instead of the curve enum

* Workaround https://github.com/nim-lang/Nim/issues/14021, yet another "working with types in macros" is difficult https://github.com/nim-lang/RFCs/issues/44

* Implement finite field over prime order of a curve subgroup

* skip OpenSSL tests on windows
This commit is contained in:
Mamy Ratsimbazafy 2021-01-22 00:09:52 +01:00 committed by GitHub
parent ac6300555a
commit 638cb71e16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 702 additions and 444 deletions

View File

@ -105,7 +105,7 @@ proc invEuclidBench*(T: typedesc, iters: int) =
proc invPowFermatBench*(T: typedesc, iters: int) =
let x = rng.random_unsafe(T)
const exponent = T.C.getInvModExponent()
const exponent = T.getInvModExponent()
bench("Inversion via exponentiation p-2 (Little Fermat)", T, iters):
var r = x
r.powUnsafeExponent(exponent)

View File

@ -148,7 +148,12 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[
("tests/t_pairing_bls12_381_optate.nim", false),
# Hashing vs OpenSSL
("tests/t_hash_sha256_vs_openssl.nim", true),
# ----------------------------------------------------------
("tests/t_hash_sha256_vs_openssl.nim", true), # skip OpenSSL tests on Windows
# Prime order fields
# ----------------------------------------------------------
("tests/t_fr.nim", false),
]
# For temporary (hopefully) investigation that can only be reproduced in CI

View File

@ -8,25 +8,27 @@
# ############################################################
#
# Fp: Finite Field arithmetic with prime field modulus P
# FF: Finite Field arithmetic
# Fp: with prime field modulus P
# Fr: with prime curve subgroup order r
#
# ############################################################
# Constraints:
# - We assume that p is known at compile-time
# - We assume that p is not even:
# - We assume that p and r are known at compile-time
# - We assume that p and r are not even:
# - Operations are done in the Montgomery domain
# - The Montgomery domain introduce a Montgomery constant that must be coprime
# with the field modulus.
# - The constant is chosen a power of 2
# => to be coprime with a power of 2, p must be odd
# - We assume that p is a prime
# - Modular inversion uses the Fermat's little theorem
# => to be coprime with a power of 2, p and r must be odd
# - We assume that p and r are a prime
# - Modular inversion may use the Fermat's little theorem
# which requires a prime
import
../primitives,
../config/[common, type_fp, curves],
../config/[common, type_ff, curves],
./bigints, ./limbs_montgomery
when UseASM_X86_64:
@ -37,7 +39,7 @@ when nimvm:
else:
discard
export Fp
export Fp, Fr, FF
# No exceptions allowed
{.push raises: [].}
@ -48,34 +50,34 @@ export Fp
#
# ############################################################
func fromBig*[C: static Curve](dst: var Fp[C], src: BigInt) {.inline.}=
func fromBig*(dst: var FF, src: BigInt) {.inline.}=
## Convert a BigInt to its Montgomery form
when nimvm:
dst.mres.montyResidue_precompute(src, C.Mod, C.getR2modP(), C.getNegInvModWord())
dst.mres.montyResidue_precompute(src, FF.fieldMod(), FF.getR2modP(), FF.getNegInvModWord())
else:
dst.mres.montyResidue(src, C.Mod, C.getR2modP(), C.getNegInvModWord(), C.canUseNoCarryMontyMul())
dst.mres.montyResidue(src, FF.fieldMod(), FF.getR2modP(), FF.getNegInvModWord(), FF.canUseNoCarryMontyMul())
func fromBig*[C: static Curve](T: type Fp[C], src: BigInt): Fp[C] {.noInit, inline.} =
func fromBig*[C: static Curve](T: type FF[C], src: BigInt): FF[C] {.noInit, inline.} =
## Convert a BigInt to its Montgomery form
result.fromBig(src)
func toBig*(src: Fp): auto {.noInit, inline.} =
func toBig*(src: FF): auto {.noInit, inline.} =
## Convert a finite-field element to a BigInt in natural representation
var r {.noInit.}: typeof(src.mres)
r.redc(src.mres, Fp.C.Mod, Fp.C.getNegInvModWord(), Fp.C.canUseNoCarryMontyMul())
r.redc(src.mres, FF.fieldMod(), FF.getNegInvModWord(), FF.canUseNoCarryMontyMul())
return r
# Copy
# ------------------------------------------------------------
func ccopy*(a: var Fp, b: Fp, ctl: SecretBool) {.inline.} =
func ccopy*(a: var FF, b: FF, ctl: SecretBool) {.inline.} =
## Constant-time conditional copy
## If ctl is true: b is copied into a
## if ctl is false: b is not copied and a is unmodified
## Time and memory accesses are the same whether a copy occurs or not
ccopy(a.mres, b.mres, ctl)
func cswap*(a, b: var Fp, ctl: CTBool) {.inline.} =
func cswap*(a, b: var FF, ctl: CTBool) {.inline.} =
## Swap ``a`` and ``b`` if ``ctl`` is true
##
## Constant-time:
@ -98,144 +100,144 @@ func cswap*(a, b: var Fp, ctl: CTBool) {.inline.} =
# exist and can be implemented with compile-time specialization.
# Note: for `+=`, double, sum
# not(a.mres < Fp.C.Mod) is unnecessary if the prime has the form
# not(a.mres < FF.fieldMod()) is unnecessary if the prime has the form
# (2^64)^w - 1 (if using uint64 words).
# In practice I'm not aware of such prime being using in elliptic curves.
# 2^127 - 1 and 2^521 - 1 are used but 127 and 521 are not multiple of 32/64
func `==`*(a, b: Fp): SecretBool {.inline.} =
func `==`*(a, b: FF): SecretBool {.inline.} =
## Constant-time equality check
a.mres == b.mres
func isZero*(a: Fp): SecretBool {.inline.} =
func isZero*(a: FF): SecretBool {.inline.} =
## Constant-time check if zero
a.mres.isZero()
func isOne*(a: Fp): SecretBool {.inline.} =
func isOne*(a: FF): SecretBool {.inline.} =
## Constant-time check if one
a.mres == Fp.C.getMontyOne()
a.mres == FF.getMontyOne()
func isMinusOne*(a: Fp): SecretBool {.inline.} =
func isMinusOne*(a: FF): SecretBool {.inline.} =
## Constant-time check if -1 (mod p)
a.mres == Fp.C.getMontyPrimeMinus1()
a.mres == FF.getMontyPrimeMinus1()
func setZero*(a: var Fp) {.inline.} =
func setZero*(a: var FF) {.inline.} =
## Set ``a`` to zero
a.mres.setZero()
func setOne*(a: var Fp) {.inline.} =
func setOne*(a: var FF) {.inline.} =
## Set ``a`` to one
# Note: we need 1 in Montgomery residue form
# TODO: Nim codegen is not optimal it uses a temporary
# Check if the compiler optimizes it away
a.mres = Fp.C.getMontyOne()
a.mres = FF.getMontyOne()
func `+=`*(a: var Fp, b: Fp) {.inline.} =
func `+=`*(a: var FF, b: FF) {.inline.} =
## In-place addition modulo p
when UseASM_X86_64 and a.mres.limbs.len <= 6: # TODO: handle spilling
addmod_asm(a.mres.limbs, b.mres.limbs, Fp.C.Mod.limbs)
addmod_asm(a.mres.limbs, b.mres.limbs, FF.fieldMod().limbs)
else:
var overflowed = add(a.mres, b.mres)
overflowed = overflowed or not(a.mres < Fp.C.Mod)
discard csub(a.mres, Fp.C.Mod, overflowed)
overflowed = overflowed or not(a.mres < FF.fieldMod())
discard csub(a.mres, FF.fieldMod(), overflowed)
func `-=`*(a: var Fp, b: Fp) {.inline.} =
func `-=`*(a: var FF, b: FF) {.inline.} =
## In-place substraction modulo p
when UseASM_X86_64 and a.mres.limbs.len <= 6: # TODO: handle spilling
submod_asm(a.mres.limbs, b.mres.limbs, Fp.C.Mod.limbs)
submod_asm(a.mres.limbs, b.mres.limbs, FF.fieldMod().limbs)
else:
let underflowed = sub(a.mres, b.mres)
discard cadd(a.mres, Fp.C.Mod, underflowed)
discard cadd(a.mres, FF.fieldMod(), underflowed)
func double*(a: var Fp) {.inline.} =
func double*(a: var FF) {.inline.} =
## Double ``a`` modulo p
when UseASM_X86_64 and a.mres.limbs.len <= 6: # TODO: handle spilling
addmod_asm(a.mres.limbs, a.mres.limbs, Fp.C.Mod.limbs)
addmod_asm(a.mres.limbs, a.mres.limbs, FF.fieldMod().limbs)
else:
var overflowed = double(a.mres)
overflowed = overflowed or not(a.mres < Fp.C.Mod)
discard csub(a.mres, Fp.C.Mod, overflowed)
overflowed = overflowed or not(a.mres < FF.fieldMod())
discard csub(a.mres, FF.fieldMod(), overflowed)
func sum*(r: var Fp, a, b: Fp) {.inline.} =
func sum*(r: var FF, a, b: FF) {.inline.} =
## Sum ``a`` and ``b`` into ``r`` modulo p
## r is initialized/overwritten
when UseASM_X86_64 and a.mres.limbs.len <= 6: # TODO: handle spilling
r = a
addmod_asm(r.mres.limbs, b.mres.limbs, Fp.C.Mod.limbs)
addmod_asm(r.mres.limbs, b.mres.limbs, FF.fieldMod().limbs)
else:
var overflowed = r.mres.sum(a.mres, b.mres)
overflowed = overflowed or not(r.mres < Fp.C.Mod)
discard csub(r.mres, Fp.C.Mod, overflowed)
overflowed = overflowed or not(r.mres < FF.fieldMod())
discard csub(r.mres, FF.fieldMod(), overflowed)
func sumNoReduce*(r: var Fp, a, b: Fp) {.inline.} =
func sumNoReduce*(r: var FF, a, b: FF) {.inline.} =
## Sum ``a`` and ``b`` into ``r`` without reduction
discard r.mres.sum(a.mres, b.mres)
func diff*(r: var Fp, a, b: Fp) {.inline.} =
func diff*(r: var FF, a, b: FF) {.inline.} =
## Substract `b` from `a` and store the result into `r`.
## `r` is initialized/overwritten
## Requires r != b
when UseASM_X86_64 and a.mres.limbs.len <= 6: # TODO: handle spilling
r = a
submod_asm(r.mres.limbs, b.mres.limbs, Fp.C.Mod.limbs)
submod_asm(r.mres.limbs, b.mres.limbs, FF.fieldMod().limbs)
else:
var underflowed = r.mres.diff(a.mres, b.mres)
discard cadd(r.mres, Fp.C.Mod, underflowed)
discard cadd(r.mres, FF.fieldMod(), underflowed)
func diffAlias*(r: var Fp, a, b: Fp) {.inline.} =
func diffAlias*(r: var FF, a, b: FF) {.inline.} =
## Substract `b` from `a` and store the result into `r`.
## `r` is initialized/overwritten
## Handles r == b
when UseASM_X86_64 and a.mres.limbs.len <= 6: # TODO: handle spilling
var t = a
submod_asm(t.mres.limbs, b.mres.limbs, Fp.C.Mod.limbs)
submod_asm(t.mres.limbs, b.mres.limbs, FF.fieldMod().limbs)
r = t
else:
var underflowed = r.mres.diff(a.mres, b.mres)
discard cadd(r.mres, Fp.C.Mod, underflowed)
discard cadd(r.mres, FF.fieldMod(), underflowed)
func diffNoReduce*(r: var Fp, a, b: Fp) {.inline.} =
func diffNoReduce*(r: var FF, a, b: FF) {.inline.} =
## Substract `b` from `a` and store the result into `r`
## without reduction
discard r.mres.diff(a.mres, b.mres)
func double*(r: var Fp, a: Fp) {.inline.} =
func double*(r: var FF, a: FF) {.inline.} =
## Double ``a`` into ``r``
## `r` is initialized/overwritten
when UseASM_X86_64 and a.mres.limbs.len <= 6: # TODO: handle spilling
r = a
addmod_asm(r.mres.limbs, a.mres.limbs, Fp.C.Mod.limbs)
addmod_asm(r.mres.limbs, a.mres.limbs, FF.fieldMod().limbs)
else:
var overflowed = r.mres.double(a.mres)
overflowed = overflowed or not(r.mres < Fp.C.Mod)
discard csub(r.mres, Fp.C.Mod, overflowed)
overflowed = overflowed or not(r.mres < FF.fieldMod())
discard csub(r.mres, FF.fieldMod(), overflowed)
func prod*(r: var Fp, a, b: Fp) {.inline.} =
func prod*(r: var FF, a, b: FF) {.inline.} =
## Store the product of ``a`` by ``b`` modulo p into ``r``
## ``r`` is initialized / overwritten
r.mres.montyMul(a.mres, b.mres, Fp.C.Mod, Fp.C.getNegInvModWord(), Fp.C.canUseNoCarryMontyMul())
r.mres.montyMul(a.mres, b.mres, FF.fieldMod(), FF.getNegInvModWord(), FF.canUseNoCarryMontyMul())
func square*(r: var Fp, a: Fp) {.inline.} =
func square*(r: var FF, a: FF) {.inline.} =
## Squaring modulo p
r.mres.montySquare(a.mres, Fp.C.Mod, Fp.C.getNegInvModWord(), Fp.C.canUseNoCarryMontySquare())
r.mres.montySquare(a.mres, FF.fieldMod(), FF.getNegInvModWord(), FF.canUseNoCarryMontySquare())
func neg*(r: var Fp, a: Fp) {.inline.} =
func neg*(r: var FF, a: FF) {.inline.} =
## Negate modulo p
when UseASM_X86_64 and defined(gcc):
# Clang and every compiler besides GCC
# can cleanly optimized this
# especially on Fp2
negmod_asm(r.mres.limbs, a.mres.limbs, Fp.C.Mod.limbs)
# especially on FF2
negmod_asm(r.mres.limbs, a.mres.limbs, FF.fieldMod().limbs)
else:
discard r.mres.diff(Fp.C.Mod, a.mres)
discard r.mres.diff(FF.fieldMod(), a.mres)
func neg*(a: var Fp) {.inline.} =
func neg*(a: var FF) {.inline.} =
## Negate modulo p
a.neg(a)
func div2*(a: var Fp) {.inline.} =
func div2*(a: var FF) {.inline.} =
## Modular division by 2
a.mres.div2_modular(Fp.C.getPrimePlus1div2())
a.mres.div2_modular(FF.getPrimePlus1div2())
# ############################################################
#
@ -243,26 +245,26 @@ func div2*(a: var Fp) {.inline.} =
#
# ############################################################
func cneg*(r: var Fp, a: Fp, ctl: SecretBool) =
func cneg*(r: var FF, a: FF, ctl: SecretBool) =
## Constant-time in-place conditional negation
## The negation is only performed if ctl is "true"
r.neg(a)
r.ccopy(a, not ctl)
func cneg*(a: var Fp, ctl: SecretBool) =
func cneg*(a: var FF, ctl: SecretBool) =
## Constant-time in-place conditional negation
## The negation is only performed if ctl is "true"
var t = a
a.cneg(t, ctl)
func cadd*(a: var Fp, b: Fp, ctl: SecretBool) =
func cadd*(a: var FF, b: FF, ctl: SecretBool) =
## Constant-time in-place conditional addition
## The addition is only performed if ctl is "true"
var t = a
t += b
a.ccopy(t, ctl)
func csub*(a: var Fp, b: Fp, ctl: SecretBool) =
func csub*(a: var FF, b: FF, ctl: SecretBool) =
## Constant-time in-place conditional substraction
## The substraction is only performed if ctl is "true"
var t = a
@ -277,33 +279,33 @@ func csub*(a: var Fp, b: Fp, ctl: SecretBool) =
#
# Internally those procedures will allocate extra scratchspace on the stack
func pow*(a: var Fp, exponent: BigInt) {.inline.} =
func pow*(a: var FF, exponent: BigInt) {.inline.} =
## Exponentiation modulo p
## ``a``: a field element to be exponentiated
## ``exponent``: a big integer
const windowSize = 5 # TODO: find best window size for each curves
a.mres.montyPow(
exponent,
Fp.C.Mod, Fp.C.getMontyOne(),
Fp.C.getNegInvModWord(), windowSize,
Fp.C.canUseNoCarryMontyMul(),
Fp.C.canUseNoCarryMontySquare()
FF.fieldMod(), FF.getMontyOne(),
FF.getNegInvModWord(), windowSize,
FF.canUseNoCarryMontyMul(),
FF.canUseNoCarryMontySquare()
)
func pow*(a: var Fp, exponent: openarray[byte]) {.inline.} =
func pow*(a: var FF, exponent: openarray[byte]) {.inline.} =
## Exponentiation modulo p
## ``a``: a field element to be exponentiated
## ``exponent``: a big integer in canonical big endian representation
const windowSize = 5 # TODO: find best window size for each curves
a.mres.montyPow(
exponent,
Fp.C.Mod, Fp.C.getMontyOne(),
Fp.C.getNegInvModWord(), windowSize,
Fp.C.canUseNoCarryMontyMul(),
Fp.C.canUseNoCarryMontySquare()
FF.fieldMod(), FF.getMontyOne(),
FF.getNegInvModWord(), windowSize,
FF.canUseNoCarryMontyMul(),
FF.canUseNoCarryMontySquare()
)
func powUnsafeExponent*(a: var Fp, exponent: BigInt) {.inline.} =
func powUnsafeExponent*(a: var FF, exponent: BigInt) {.inline.} =
## Exponentiation modulo p
## ``a``: a field element to be exponentiated
## ``exponent``: a big integer
@ -317,13 +319,13 @@ func powUnsafeExponent*(a: var Fp, exponent: BigInt) {.inline.} =
const windowSize = 5 # TODO: find best window size for each curves
a.mres.montyPowUnsafeExponent(
exponent,
Fp.C.Mod, Fp.C.getMontyOne(),
Fp.C.getNegInvModWord(), windowSize,
Fp.C.canUseNoCarryMontyMul(),
Fp.C.canUseNoCarryMontySquare()
FF.fieldMod(), FF.getMontyOne(),
FF.getNegInvModWord(), windowSize,
FF.canUseNoCarryMontyMul(),
FF.canUseNoCarryMontySquare()
)
func powUnsafeExponent*(a: var Fp, exponent: openarray[byte]) {.inline.} =
func powUnsafeExponent*(a: var FF, exponent: openarray[byte]) {.inline.} =
## Exponentiation modulo p
## ``a``: a field element to be exponentiated
## ``exponent``: a big integer a big integer in canonical big endian representation
@ -337,10 +339,10 @@ func powUnsafeExponent*(a: var Fp, exponent: openarray[byte]) {.inline.} =
const windowSize = 5 # TODO: find best window size for each curves
a.mres.montyPowUnsafeExponent(
exponent,
Fp.C.Mod, Fp.C.getMontyOne(),
Fp.C.getNegInvModWord(), windowSize,
Fp.C.canUseNoCarryMontyMul(),
Fp.C.canUseNoCarryMontySquare()
FF.fieldMod(), FF.getMontyOne(),
FF.getNegInvModWord(), windowSize,
FF.canUseNoCarryMontyMul(),
FF.canUseNoCarryMontySquare()
)
# ############################################################
@ -355,36 +357,36 @@ func powUnsafeExponent*(a: var Fp, exponent: openarray[byte]) {.inline.} =
# - Those that return a field element
# - Those that internally allocate a temporary field element
func `+`*(a, b: Fp): Fp {.noInit, inline.} =
func `+`*(a, b: FF): FF {.noInit, inline.} =
## Addition modulo p
result.sum(a, b)
func `-`*(a, b: Fp): Fp {.noInit, inline.} =
func `-`*(a, b: FF): FF {.noInit, inline.} =
## Substraction modulo p
result.diff(a, b)
func `*`*(a, b: Fp): Fp {.noInit, inline.} =
func `*`*(a, b: FF): FF {.noInit, inline.} =
## Multiplication modulo p
##
## It is recommended to assign with {.noInit.}
## as Fp elements are usually large and this
## as FF elements are usually large and this
## routine will zero init internally the result.
result.prod(a, b)
func `*=`*(a: var Fp, b: Fp) {.inline.} =
func `*=`*(a: var FF, b: FF) {.inline.} =
## Multiplication modulo p
a.prod(a, b)
func square*(a: var Fp) {.inline.} =
func square*(a: var FF) {.inline.} =
## Squaring modulo p
a.mres.montySquare(a.mres, Fp.C.Mod, Fp.C.getNegInvModWord(), Fp.C.canUseNoCarryMontySquare())
a.mres.montySquare(a.mres, FF.fieldMod(), FF.getNegInvModWord(), FF.canUseNoCarryMontySquare())
func square_repeated*(r: var Fp, num: int) {.inline.} =
func square_repeated*(r: var FF, num: int) {.inline.} =
## Repeated squarings
for _ in 0 ..< num:
r.square()
func `*=`*(a: var Fp, b: static int) {.inline.} =
func `*=`*(a: var FF, b: static int) {.inline.} =
## Multiplication by a small integer known at compile-time
# Implementation:
# We don't want to go convert the integer to the Montgomery domain (O(n²))
@ -466,7 +468,7 @@ func `*=`*(a: var Fp, b: static int) {.inline.} =
else:
{.error: "Multiplication by this small int not implemented".}
func `*`*(b: static int, a: Fp): Fp {.noinit, inline.} =
func `*`*(b: static int, a: FF): FF {.noinit, inline.} =
## Multiplication by a small integer known at compile-time
result = a
result *= b

View File

@ -7,7 +7,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../config/[common, curves, type_fp],
../config/[common, curves, type_ff],
../primitives,
./bigints,
./finite_fields,
@ -38,8 +38,8 @@ func reduce*(r: var Fp, a: FpDbl) {.inline.} =
r.mres.limbs,
a.limbs2x,
Fp.C.Mod.limbs,
Fp.C.getNegInvModWord(),
Fp.C.canUseNoCarryMontyMul()
Fp.getNegInvModWord(),
Fp.canUseNoCarryMontyMul()
)
func diffNoInline(r: var FpDbl, a, b: FpDbl): Borrow =

View File

@ -7,7 +7,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../config/[curves, type_fp],
../config/[curves, type_ff],
./bigints,
../curves/zoo_inversions
@ -23,7 +23,7 @@ func inv_euclid*(r: var Fp, a: Fp) {.inline.} =
## Inversion modulo p via
## Niels Moller constant-time version of
## Stein's GCD derived from extended binary Euclid algorithm
r.mres.steinsGCD(a.mres, Fp.C.getR2modP(), Fp.C.Mod, Fp.C.getPrimePlus1div2())
r.mres.steinsGCD(a.mres, Fp.getR2modP(), Fp.C.Mod, Fp.getPrimePlus1div2())
func inv*(r: var Fp, a: Fp) {.inline.} =
## Inversion modulo p

View File

@ -8,7 +8,7 @@
import
../primitives,
../config/[common, type_fp, curves],
../config/[common, type_ff, curves],
../curves/zoo_square_roots,
./bigints, ./finite_fields
@ -21,7 +21,7 @@ import
# Legendre symbol / Euler's Criterion / Kronecker's symbol
# ------------------------------------------------------------
func isSquare*[C](a: Fp[C]): SecretBool {.inline.} =
func isSquare*(a: Fp): SecretBool {.inline.} =
## Returns true if ``a`` is a square (quadratic residue) in 𝔽p
##
## Assumes that the prime modulus ``p`` is public.
@ -30,7 +30,7 @@ func isSquare*[C](a: Fp[C]): SecretBool {.inline.} =
# Note that we don't care about leaking the bits of p
# as we assume that
var xi {.noInit.} = a # TODO: is noInit necessary? see https://github.com/mratsim/constantine/issues/21
xi.powUnsafeExponent(C.getPrimeMinus1div2_BE())
xi.powUnsafeExponent(Fp.getPrimeMinus1div2_BE())
result = not(xi.isMinusOne())
# xi can be:
# - 1 if a square
@ -46,7 +46,7 @@ func isSquare*[C](a: Fp[C]): SecretBool {.inline.} =
# Specialized routine for p ≡ 3 (mod 4)
# ------------------------------------------------------------
func sqrt_p3mod4[C](a: var Fp[C]) {.inline.} =
func sqrt_p3mod4(a: var Fp) {.inline.} =
## Compute the square root of ``a``
##
## This requires ``a`` to be a square
@ -57,10 +57,10 @@ func sqrt_p3mod4[C](a: var Fp[C]) {.inline.} =
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
static: doAssert BaseType(C.Mod.limbs[0]) mod 4 == 3
a.powUnsafeExponent(C.getPrimePlus1div4_BE())
static: doAssert BaseType(Fp.C.Mod.limbs[0]) mod 4 == 3
a.powUnsafeExponent(Fp.getPrimePlus1div4_BE())
func sqrt_invsqrt_p3mod4[C](sqrt, invsqrt: var Fp[C], a: Fp[C]) {.inline.} =
func sqrt_invsqrt_p3mod4(sqrt, invsqrt: var Fp, a: Fp) {.inline.} =
## If ``a`` is a square, compute the square root of ``a`` in sqrt
## and the inverse square root of a in invsqrt
##
@ -74,14 +74,14 @@ func sqrt_invsqrt_p3mod4[C](sqrt, invsqrt: var Fp[C], a: Fp[C]) {.inline.} =
# a^((p-1)/2)) * a^-1 ≡ 1/a (mod p)
# a^((p-3)/2)) ≡ 1/a (mod p)
# a^((p-3)/4)) ≡ 1/√a (mod p) # Requires p ≡ 3 (mod 4)
static: doAssert BaseType(C.Mod.limbs[0]) mod 4 == 3
static: doAssert BaseType(Fp.C.Mod.limbs[0]) mod 4 == 3
invsqrt = a
invsqrt.powUnsafeExponent(C.getPrimeMinus3div4_BE())
invsqrt.powUnsafeExponent(Fp.getPrimeMinus3div4_BE())
# √a ≡ a * 1/√a ≡ a^((p+1)/4) (mod p)
sqrt.prod(invsqrt, a)
func sqrt_invsqrt_if_square_p3mod4[C](sqrt, invsqrt: var Fp[C], a: Fp[C]): SecretBool {.inline.} =
func sqrt_invsqrt_if_square_p3mod4(sqrt, invsqrt: var Fp, a: Fp): SecretBool {.inline.} =
## If ``a`` is a square, compute the square root of ``a`` in sqrt
## and the inverse square root of a in invsqrt
##
@ -89,11 +89,11 @@ func sqrt_invsqrt_if_square_p3mod4[C](sqrt, invsqrt: var Fp[C], a: Fp[C]): Secre
##
## This assumes that the prime field modulus ``p``: p ≡ 3 (mod 4)
sqrt_invsqrt_p3mod4(sqrt, invsqrt, a)
var test {.noInit.}: Fp[C]
var test {.noInit.}: Fp
test.square(sqrt)
result = test == a
func sqrt_if_square_p3mod4[C](a: var Fp[C]): SecretBool {.inline.} =
func sqrt_if_square_p3mod4(a: var Fp): SecretBool {.inline.} =
## If ``a`` is a square, compute the square root of ``a``
## if not, ``a`` is unmodified.
##
@ -104,28 +104,28 @@ func sqrt_if_square_p3mod4[C](a: var Fp[C]): SecretBool {.inline.} =
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
var sqrt {.noInit.}, invsqrt {.noInit.}: Fp[C]
var sqrt {.noInit.}, invsqrt {.noInit.}: Fp
result = sqrt_invsqrt_if_square_p3mod4(sqrt, invsqrt, a)
a.ccopy(sqrt, result)
# Tonelli Shanks for any prime
# ------------------------------------------------------------
func precompute_tonelli_shanks[C](
a_pre_exp: var Fp[C],
a: Fp[C]) =
func precompute_tonelli_shanks(
a_pre_exp: var Fp,
a: Fp) =
a_pre_exp = a
a_pre_exp.powUnsafeExponent(C.tonelliShanks(exponent))
a_pre_exp.powUnsafeExponent(Fp.C.tonelliShanks(exponent))
func isSquare_tonelli_shanks[C](
a, a_pre_exp: Fp[C]): SecretBool =
func isSquare_tonelli_shanks(
a, a_pre_exp: Fp): SecretBool =
## Returns if `a` is a quadratic residue
## This uses common precomputation for
## Tonelli-Shanks based square root and inverse square root
##
## a^((p-1-2^e)/(2*2^e))
const e = C.tonelliShanks(twoAdicity)
var r {.noInit.}: Fp[C]
const e = Fp.C.tonelliShanks(twoAdicity)
var r {.noInit.}: Fp
r.square(a_pre_exp) # a^(2(q-1-2^e)/(2*2^e)) = a^((q-1)/2^e - 1)
r *= a # a^((q-1)/2^e)
for _ in 0 ..< e-1:
@ -143,9 +143,9 @@ func isSquare_tonelli_shanks[C](
r.isMinusOne()
)
func sqrt_invsqrt_tonelli_shanks[C](
sqrt, invsqrt: var Fp[C],
a, a_pre_exp: Fp[C]) =
func sqrt_invsqrt_tonelli_shanks(
sqrt, invsqrt: var Fp,
a, a_pre_exp: Fp) =
## Compute the square_root and inverse_square_root
## of `a` via constant-time Tonelli-Shanks
##
@ -153,16 +153,16 @@ func sqrt_invsqrt_tonelli_shanks[C](
## ThItat is shared with the simultaneous isSquare routine
template z: untyped = a_pre_exp
template r: untyped = invsqrt
var t {.noInit.}: Fp[C]
const e = C.tonelliShanks(twoAdicity)
var t {.noInit.}: Fp
const e = Fp.C.tonelliShanks(twoAdicity)
t.square(z)
t *= a
r = z
var b = t
var root = C.tonelliShanks(root_of_unity)
var root = Fp.C.tonelliShanks(root_of_unity)
var buf {.noInit.}: Fp[C]
var buf {.noInit.}: Fp
for i in countdown(e, 2, 1):
for j in 1 .. i-2:

View File

@ -6,191 +6,5 @@
# * 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/macros,
# Internal
./type_bigint, ./common,
./curves_declaration, ./curves_derived, ./curves_parser
export CurveFamily, Curve, SexticTwist
# ############################################################
#
# Field properties
#
# ############################################################
{.experimental: "dynamicBindSym".}
macro Mod*(C: static Curve): untyped =
## Get the Modulus associated to a curve
result = bindSym($C & "_Modulus")
template getCurveBitwidth*(C: Curve): int =
## Returns the number of bits taken by the curve modulus
CurveBitWidth[C]
template matchingBigInt*(C: static Curve): untyped =
BigInt[CurveBitWidth[C]]
template family*(C: Curve): CurveFamily =
CurveFamilies[C]
template matchingLimbs2x*(C: Curve): untyped =
const N2 = wordsRequired(getCurveBitwidth(C)) * 2 # TODO upstream, not precomputing N2 breaks semcheck
array[N2, SecretWord] # TODO upstream, using Limbs[N2] breaks semcheck
# ############################################################
#
# Curve properties
#
# ############################################################
macro getCurveOrder*(C: static Curve): untyped =
## Get the curve order `r`
## i.e. the number of points on the elliptic curve
result = bindSym($C & "_Order")
macro getCurveOrderBitwidth*(C: static Curve): untyped =
## Get the curve order `r`
## i.e. the number of points on the elliptic curve
result = nnkDotExpr.newTree(
getAST(getCurveOrder(C)),
ident"bits"
)
macro getEquationForm*(C: static Curve): untyped =
## Returns the equation form
## (ShortWeierstrass, Montgomery, Twisted Edwards, Weierstrass, ...)
result = bindSym($C & "_equation_form")
macro getCoefA*(C: static Curve): untyped =
## Returns the A coefficient of the curve
## The return type is polymorphic, it can be an int
## or a bigInt depending on the curve
result = bindSym($C & "_coef_A")
macro getCoefB*(C: static Curve): untyped =
## Returns the B coefficient of the curve
## The return type is polymorphic, it can be an int
## or a bigInt depending on the curve
result = bindSym($C & "_coef_B")
macro get_QNR_Fp*(C: static Curve): untyped =
## Returns the tower extension quadratic non-residue in 𝔽p
## i.e. a number that is not a square in 𝔽p
result = bindSym($C & "_nonresidue_quad_fp")
macro get_CNR_Fp2*(C: static Curve): untyped =
## Returns the tower extension cubic non-residue 𝔽
## i.e. a number that is not a cube in 𝔽
##
## The return value is a tuple (a, b)
## that corresponds to the number a + b𝑗
## with 𝑗 choosen for 𝑗² - QNR_Fp == 0
## i.e. if -1 is chosen as a quadratic non-residue 𝑗 = √-1
## if -2 is chosen as a quadratic non-residue 𝑗 = √-2
result = bindSym($C & "_nonresidue_cube_fp2")
macro getEmbeddingDegree*(C: static Curve): untyped =
## Returns the prime embedding degree,
## i.e. the smallest k such that r|𝑝^𝑘1
## equivalently 𝑝^𝑘 ≡ 1 (mod r)
## with r the curve order and p its field modulus
result = bindSym($C & "_embedding_degree")
macro getSexticTwist*(C: static Curve): untyped =
## Returns if D-Twist or M-Twist
result = bindSym($C & "_sexticTwist")
macro get_SNR_Fp2*(C: static Curve): untyped =
## Returns the sextic non-residue in 𝔽
## choosen to build the twisted curve E'(𝔽p²)
## i.e. a number µ so that x⁶ - µ is irreducible
result = bindSym($C & "_sexticNonResidue_fp2")
# ############################################################
#
# Access precomputed derived constants in ROM
#
# ############################################################
genDerivedConstants()
macro canUseNoCarryMontyMul*(C: static Curve): untyped =
## Returns true if the Modulus is compatible with a fast
## Montgomery multiplication that avoids many carries
result = bindSym($C & "_CanUseNoCarryMontyMul")
macro canUseNoCarryMontySquare*(C: static Curve): untyped =
## Returns true if the Modulus is compatible with a fast
## Montgomery squaring that avoids many carries
result = bindSym($C & "_CanUseNoCarryMontySquare")
macro getR2modP*(C: static Curve): untyped =
## Get the Montgomery "R^2 mod P" constant associated to a curve field modulus
result = bindSym($C & "_R2modP")
macro getNegInvModWord*(C: static Curve): untyped =
## Get the Montgomery "-1/P[0] mod 2^Wordbitwidth" constant associated to a curve field modulus
result = bindSym($C & "_NegInvModWord")
macro getMontyOne*(C: static Curve): untyped =
## Get one in Montgomery representation (i.e. R mod P)
result = bindSym($C & "_MontyOne")
macro getMontyPrimeMinus1*(C: static Curve): untyped =
## Get (P+1) / 2 for an odd prime
result = bindSym($C & "_MontyPrimeMinus1")
macro getInvModExponent*(C: static Curve): untyped =
## Get modular inversion exponent (Modulus-2 in canonical representation)
result = bindSym($C & "_InvModExponent")
macro getPrimePlus1div2*(C: static Curve): untyped =
## Get (P+1) / 2 for an odd prime
## Warning ⚠️: Result in canonical domain (not Montgomery)
result = bindSym($C & "_PrimePlus1div2")
macro getPrimeMinus1div2_BE*(C: static Curve): untyped =
## Get (P-1) / 2 in big-endian serialized format
result = bindSym($C & "_PrimeMinus1div2_BE")
macro getPrimeMinus3div4_BE*(C: static Curve): untyped =
## Get (P-3) / 2 in big-endian serialized format
result = bindSym($C & "_PrimeMinus3div4_BE")
macro getPrimePlus1div4_BE*(C: static Curve): untyped =
## Get (P+1) / 4 for an odd prime in big-endian serialized format
result = bindSym($C & "_PrimePlus1div4_BE")
# ############################################################
#
# Debug info printed at compile-time
#
# ############################################################
macro debugConsts(): untyped {.used.} =
let curves = bindSym("Curve")
let E = curves.getImpl[2]
result = newStmtList()
for i in 1 ..< E.len:
let curve = E[i]
let curveName = $curve
let modulus = bindSym(curveName & "_Modulus")
let r2modp = bindSym(curveName & "_R2modP")
let negInvModWord = bindSym(curveName & "_NegInvModWord")
result.add quote do:
echo "Curve ", `curveName`,':'
echo " Field Modulus: ", `modulus`
echo " Montgomery R² (mod P): ", `r2modp`
echo " Montgomery -1/P[0] (mod 2^", WordBitWidth, "): ", `negInvModWord`
result.add quote do:
echo "----------------------------------------------------------------------------"
# debug: # displayed with -d:debugConstantine
# debugConsts()
import curves_prop_core, curves_prop_derived
export curves_prop_core, curves_prop_derived

View File

@ -15,7 +15,12 @@ import
{.experimental: "dynamicBindSym".}
macro genDerivedConstants*(): untyped =
type
DerivedConstantMode* = enum
kModulus
kOrder
macro genDerivedConstants*(mode: static DerivedConstantMode): untyped =
## Generate constants derived from the main constants
##
## For example
@ -38,87 +43,92 @@ macro genDerivedConstants*(): untyped =
nnkPragma.newTree(ident"used")
)
let ff = if mode == kModulus: "_Fp" else: "_Fr"
for curveSym in low(Curve) .. high(Curve):
let curve = $curveSym
let M = if mode == kModulus: bindSym(curve & "_Modulus")
else: bindSym(curve & "_Order")
# const MyCurve_CanUseNoCarryMontyMul = useNoCarryMontyMul(MyCurve_Modulus)
result.add newConstStmt(
used(curve & "_CanUseNoCarryMontyMul"), newCall(
used(curve & ff & "_CanUseNoCarryMontyMul"), newCall(
bindSym"useNoCarryMontyMul",
bindSym(curve & "_Modulus")
M
)
)
# const MyCurve_CanUseNoCarryMontySquare = useNoCarryMontySquare(MyCurve_Modulus)
result.add newConstStmt(
used(curve & "_CanUseNoCarryMontySquare"), newCall(
used(curve & ff & "_CanUseNoCarryMontySquare"), newCall(
bindSym"useNoCarryMontySquare",
bindSym(curve & "_Modulus")
M
)
)
# const MyCurve_R2modP = r2mod(MyCurve_Modulus)
result.add newConstStmt(
used(curve & "_R2modP"), newCall(
used(curve & ff & "_R2modP"), newCall(
bindSym"r2mod",
bindSym(curve & "_Modulus")
M
)
)
# const MyCurve_NegInvModWord = negInvModWord(MyCurve_Modulus)
result.add newConstStmt(
used(curve & "_NegInvModWord"), newCall(
used(curve & ff & "_NegInvModWord"), newCall(
bindSym"negInvModWord",
bindSym(curve & "_Modulus")
M
)
)
# const MyCurve_montyOne = montyOne(MyCurve_Modulus)
result.add newConstStmt(
used(curve & "_MontyOne"), newCall(
used(curve & ff & "_MontyOne"), newCall(
bindSym"montyOne",
bindSym(curve & "_Modulus")
M
)
)
# const MyCurve_MontyPrimeMinus1 = montyPrimeMinus1(MyCurve_Modulus)
result.add newConstStmt(
used(curve & "_MontyPrimeMinus1"), newCall(
used(curve & ff & "_MontyPrimeMinus1"), newCall(
bindSym"montyPrimeMinus1",
bindSym(curve & "_Modulus")
M
)
)
# const MyCurve_InvModExponent = primeMinus2_BE(MyCurve_Modulus)
result.add newConstStmt(
used(curve & "_InvModExponent"), newCall(
used(curve & ff & "_InvModExponent"), newCall(
bindSym"primeMinus2_BE",
bindSym(curve & "_Modulus")
M
)
)
# const MyCurve_PrimePlus1div2 = primePlus1div2(MyCurve_Modulus)
result.add newConstStmt(
used(curve & "_PrimePlus1div2"), newCall(
used(curve & ff & "_PrimePlus1div2"), newCall(
bindSym"primePlus1div2",
bindSym(curve & "_Modulus")
M
)
)
# const MyCurve_PrimeMinus1div2_BE = primeMinus1div2_BE(MyCurve_Modulus)
result.add newConstStmt(
used(curve & "_PrimeMinus1div2_BE"), newCall(
used(curve & ff & "_PrimeMinus1div2_BE"), newCall(
bindSym"primeMinus1div2_BE",
bindSym(curve & "_Modulus")
M
)
)
# const MyCurve_PrimeMinus3div4_BE = primeMinus3div4_BE(MyCurve_Modulus)
result.add newConstStmt(
used(curve & "_PrimeMinus3div4_BE"), newCall(
used(curve & ff & "_PrimeMinus3div4_BE"), newCall(
bindSym"primeMinus3div4_BE",
bindSym(curve & "_Modulus")
M
)
)
# const MyCurve_PrimePlus1div4_BE = primePlus1div4_BE(MyCurve_Modulus)
result.add newConstStmt(
used(curve & "_PrimePlus1div4_BE"), newCall(
used(curve & ff & "_PrimePlus1div4_BE"), newCall(
bindSym"primePlus1div4_BE",
bindSym(curve & "_Modulus")
M
)
)

View File

@ -237,8 +237,12 @@ template getCoef(c: CurveCoef, width: NimNode): untyped {.dirty.}=
proc genMainConstants(defs: var seq[CurveParams]): NimNode =
## Generate curves and fields main constants
# MapCurveBitWidth & MapCurveOrderBitWidth
# are workaround for https://github.com/nim-lang/Nim/issues/16774
var Curves: seq[NimNode]
var MapCurveBitWidth = nnkBracket.newTree()
var MapCurveOrderBitWidth = nnkBracket.newTree()
var MapCurveFamily = nnkBracket.newTree()
var curveModStmts = newStmtList()
var curveEllipticStmts = newStmtList()
@ -290,6 +294,22 @@ proc genMainConstants(defs: var seq[CurveParams]): NimNode =
curveDef.order
)
)
MapCurveOrderBitWidth.add nnkExprColonExpr.newTree(
curve, curveDef.orderBitwidth
)
else: # Dummy
curveEllipticStmts.add newConstStmt(
exported($curve & "_Order"),
newCall(
bindSym"fromHex",
nnkBracketExpr.newTree(bindSym"BigInt", newLit 1),
newLit"0x1"
)
)
MapCurveOrderBitWidth.add nnkExprColonExpr.newTree(
curve, newLit 1
)
if curveDef.coef_A.kind != NoCoef and curveDef.coef_B.kind != NoCoef:
curveEllipticStmts.add newConstStmt(
exported($curve & "_coef_A"),
@ -347,6 +367,10 @@ proc genMainConstants(defs: var seq[CurveParams]): NimNode =
result.add newConstStmt(
exported("CurveFamilies"), MapCurveFamily
)
# const CurveOrderBitSize: array[Curve, int] = ...
result.add newConstStmt(
exported("CurveOrderBitWidth"), MapCurveOrderBitWidth
)
result.add curveModStmts
result.add curveEllipticStmts

View File

@ -0,0 +1,116 @@
# 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/macros,
# Internal
./type_bigint, ./common,
./curves_declaration, ./curves_parser
export CurveFamily, Curve, SexticTwist
# ############################################################
#
# Field properties
#
# ############################################################
{.experimental: "dynamicBindSym".}
macro Mod*(C: static Curve): untyped =
## Get the Modulus associated to a curve
result = bindSym($C & "_Modulus")
template getCurveBitwidth*(C: Curve): int =
## Returns the number of bits taken by the curve modulus
CurveBitWidth[C]
template matchingBigInt*(C: static Curve): untyped =
# Workaround: https://github.com/nim-lang/Nim/issues/16774
BigInt[CurveBitWidth[C]]
template family*(C: Curve): CurveFamily =
CurveFamilies[C]
template matchingLimbs2x*(C: Curve): untyped =
const N2 = wordsRequired(getCurveBitwidth(C)) * 2 # TODO upstream, not precomputing N2 breaks semcheck
array[N2, SecretWord] # TODO upstream, using Limbs[N2] breaks semcheck
# ############################################################
#
# Curve properties
#
# ############################################################
macro getCurveOrder*(C: static Curve): untyped =
## Get the curve order `r`
## i.e. the number of points on the elliptic curve
result = bindSym($C & "_Order")
macro getCurveOrderBitwidth*(C: static Curve): untyped =
## Get the curve order `r`
## i.e. the number of points on the elliptic curve
result = nnkDotExpr.newTree(
getAST(getCurveOrder(C)),
ident"bits"
)
template matchingOrderBigInt*(C: static Curve): untyped =
# Workaround: https://github.com/nim-lang/Nim/issues/16774
BigInt[CurveOrderBitWidth[C]]
macro getEquationForm*(C: static Curve): untyped =
## Returns the equation form
## (ShortWeierstrass, Montgomery, Twisted Edwards, Weierstrass, ...)
result = bindSym($C & "_equation_form")
macro getCoefA*(C: static Curve): untyped =
## Returns the A coefficient of the curve
## The return type is polymorphic, it can be an int
## or a bigInt depending on the curve
result = bindSym($C & "_coef_A")
macro getCoefB*(C: static Curve): untyped =
## Returns the B coefficient of the curve
## The return type is polymorphic, it can be an int
## or a bigInt depending on the curve
result = bindSym($C & "_coef_B")
macro get_QNR_Fp*(C: static Curve): untyped =
## Returns the tower extension quadratic non-residue in 𝔽p
## i.e. a number that is not a square in 𝔽p
result = bindSym($C & "_nonresidue_quad_fp")
macro get_CNR_Fp2*(C: static Curve): untyped =
## Returns the tower extension cubic non-residue 𝔽
## i.e. a number that is not a cube in 𝔽
##
## The return value is a tuple (a, b)
## that corresponds to the number a + b𝑗
## with 𝑗 choosen for 𝑗² - QNR_Fp == 0
## i.e. if -1 is chosen as a quadratic non-residue 𝑗 = √-1
## if -2 is chosen as a quadratic non-residue 𝑗 = √-2
result = bindSym($C & "_nonresidue_cube_fp2")
macro getEmbeddingDegree*(C: static Curve): untyped =
## Returns the prime embedding degree,
## i.e. the smallest k such that r|𝑝^𝑘1
## equivalently 𝑝^𝑘 ≡ 1 (mod r)
## with r the curve order and p its field modulus
result = bindSym($C & "_embedding_degree")
macro getSexticTwist*(C: static Curve): untyped =
## Returns if D-Twist or M-Twist
result = bindSym($C & "_sexticTwist")
macro get_SNR_Fp2*(C: static Curve): untyped =
## Returns the sextic non-residue in 𝔽
## choosen to build the twisted curve E'(𝔽p²)
## i.e. a number µ so that x⁶ - µ is irreducible
result = bindSym($C & "_sexticNonResidue_fp2")

View File

@ -0,0 +1,139 @@
# 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/macros,
# Internal
./type_bigint, ./type_ff, ./common,
./curves_declaration, ./curves_derived
# ############################################################
#
# Access precomputed derived constants in ROM
#
# ############################################################
{.experimental: "dynamicBindSym".}
genDerivedConstants(kModulus)
genDerivedConstants(kOrder)
proc bindConstant(ff: NimNode, property: string): NimNode =
# Need to workaround https://github.com/nim-lang/Nim/issues/14021
# which prevents checking if a type FF[C] = Fp[C] or Fr[C]
# was instantiated with Fp or Fr.
# getTypeInst only returns FF and sameType doesn't work.
# so quote do + when checks.
let T = getTypeInst(ff)
T.expectKind(nnkBracketExpr)
doAssert T[0].eqIdent("typedesc")
if T[1].kind == nnkBracketExpr: # typedesc[Fp[BLS12_381]]
# doAssert T[1][0].eqIdent"Fp" or T[1][0].eqIdent"Fr", "Found ident: '" & $T[1][0] & "' instead of 'Fp' or 'Fr'"
T[1][1].expectKind(nnkIntLit) # static enum are ints in the VM
let curve = $Curve(T[1][1].intVal)
let curve_fp = bindSym(curve & "_Fp_" & property)
let curve_fr = bindSym(curve & "_Fr_" & property)
result = quote do:
when `ff` is Fp:
`curve_fp`
elif `ff` is Fr:
`curve_fr`
else:
{.error: "Unreachable, received type: " & $`ff`.}
else:
echo T.repr()
echo getTypeInst(T[1]).treerepr
error "getTypeInst didn't return the full instantiation." &
" Dealing with types in macros is hard, complain at https://github.com/nim-lang/RFCs/issues/44"
template fieldMod*(Field: type FF): auto =
when Field is Fp:
Field.C.Mod
else:
Field.C.getCurveOrder()
macro canUseNoCarryMontyMul*(ff: type FF): untyped =
## Returns true if the Modulus is compatible with a fast
## Montgomery multiplication that avoids many carries
result = bindConstant(ff, "CanUseNoCarryMontyMul")
macro canUseNoCarryMontySquare*(ff: type FF): untyped =
## Returns true if the Modulus is compatible with a fast
## Montgomery squaring that avoids many carries
result = bindConstant(ff, "CanUseNoCarryMontySquare")
macro getR2modP*(ff: type FF): untyped =
## Get the Montgomery "R^2 mod P" constant associated to a curve field modulus
result = bindConstant(ff, "R2modP")
macro getNegInvModWord*(ff: type FF): untyped =
## Get the Montgomery "-1/P[0] mod 2^Wordbitwidth" constant associated to a curve field modulus
result = bindConstant(ff, "NegInvModWord")
macro getMontyOne*(ff: type FF): untyped =
## Get one in Montgomery representation (i.e. R mod P)
result = bindConstant(ff, "MontyOne")
macro getMontyPrimeMinus1*(ff: type FF): untyped =
## Get (P+1) / 2 for an odd prime
result = bindConstant(ff, "MontyPrimeMinus1")
macro getInvModExponent*(ff: type FF): untyped =
## Get modular inversion exponent (Modulus-2 in canonical representation)
result = bindConstant(ff, "InvModExponent")
macro getPrimePlus1div2*(ff: type FF): untyped =
## Get (P+1) / 2 for an odd prime
## Warning ⚠️: Result in canonical domain (not Montgomery)
result = bindConstant(ff, "PrimePlus1div2")
macro getPrimeMinus1div2_BE*(ff: type FF): untyped =
## Get (P-1) / 2 in big-endian serialized format
result = bindConstant(ff, "PrimeMinus1div2_BE")
macro getPrimeMinus3div4_BE*(ff: type FF): untyped =
## Get (P-3) / 2 in big-endian serialized format
result = bindConstant(ff, "PrimeMinus3div4_BE")
macro getPrimePlus1div4_BE*(ff: type FF): untyped =
## Get (P+1) / 4 for an odd prime in big-endian serialized format
result = bindConstant(ff, "PrimePlus1div4_BE")
# ############################################################
#
# Debug info printed at compile-time
#
# ############################################################
macro debugConsts(): untyped {.used.} =
let curves = bindSym("Curve")
let E = curves.getImpl[2]
result = newStmtList()
for i in 1 ..< E.len:
let curve = E[i]
let curveName = $curve
let modulus = bindSym(curveName & "_Fp_Modulus")
let r2modp = bindSym(curveName & "_Fp_R2modP")
let negInvModWord = bindSym(curveName & "_Fp_NegInvModWord")
result.add quote do:
echo "Curve ", `curveName`,':'
echo " Field Modulus: ", `modulus`
echo " Montgomery R² (mod P): ", `r2modp`
echo " Montgomery -1/P[0] (mod 2^", WordBitWidth, "): ", `negInvModWord`
result.add quote do:
echo "----------------------------------------------------------------------------"
# debug: # displayed with -d:debugConstantine
# debugConsts()

View File

@ -12,12 +12,21 @@ import
type
Fp*[C: static Curve] = object
## All operations on a field are modulo P
## All operations on a Fp field are modulo P
## P being the prime modulus of the Curve C
## Internally, data is stored in Montgomery n-residue form
## with the magic constant chosen for convenient division (a power of 2 depending on P bitsize)
mres*: matchingBigInt(C)
Fr*[C: static Curve] = object
## All operations on a field are modulo `r`
## `r` being the prime curve order or subgroup order
## Internally, data is stored in Montgomery n-residue form
## with the magic constant chosen for convenient division (a power of 2 depending on P bitsize)
mres*: matchingOrderBigInt(C)
FF*[C: static Curve] = Fp[C] or Fr[C]
debug:
import ./type_bigint
@ -26,3 +35,9 @@ debug:
result.add "]("
result.add $a.mres
result.add ')'
func `$`*[C: static Curve](a: Fr[C]): string =
result = "Fr[" & $C
result.add "]("
result.add $a.mres
result.add ')'

View File

@ -7,7 +7,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../config/[curves, type_bigint, type_fp],
../config/[curves, type_bigint, type_ff],
../io/[io_bigints, io_fields]
# BLS12_377 G1

View File

@ -7,7 +7,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../config/[curves, type_bigint, type_fp],
../config/[curves, type_bigint, type_ff],
../io/[io_bigints, io_fields]
const

View File

@ -7,7 +7,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../config/[curves, type_bigint, type_fp],
../config/[curves, type_bigint, type_ff],
../io/[io_bigints, io_fields]
# BLS12_381 G1

View File

@ -7,7 +7,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../config/[curves, type_bigint, type_fp],
../config/[curves, type_bigint, type_ff],
../io/[io_bigints, io_fields]
# BN254_Nogami G1

View File

@ -7,7 +7,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../config/[curves, type_bigint, type_fp],
../config/[curves, type_bigint, type_ff],
../io/[io_bigints, io_fields]
# BN254_Snarks G1

View File

@ -7,7 +7,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../config/[curves, type_fp],
../config/[curves, type_ff],
../towers,
../io/[io_fields, io_towers]

View File

@ -7,7 +7,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../config/[curves, type_bigint, type_fp],
../config/[curves, type_bigint, type_ff],
../io/[io_bigints, io_fields]
# BW6_761 G1

View File

@ -8,7 +8,7 @@
import
std/macros,
../config/[curves, type_fp],
../config/[curves, type_ff],
../towers,
./bls12_377_glv,
./bls12_381_glv,

View File

@ -22,18 +22,18 @@ import
#
# ############################################################
func fromUint*(dst: var Fp,
func fromUint*(dst: var FF,
src: SomeUnsignedInt) =
## Parse a regular unsigned integer
## and store it into a Fp
let raw {.noinit.} = (type dst.mres).fromRawUint(cast[array[sizeof(src), byte]](src), cpuEndian)
## and store it into a Fp or Fr
let raw {.noinit.} = (typeof dst.mres).fromRawUint(cast[array[sizeof(src), byte]](src), cpuEndian)
dst.fromBig(raw)
func fromInt*(dst: var Fp,
func fromInt*(dst: var FF,
src: SomeInteger) =
## Parse a regular signed integer
## and store it into a Fp
## A negative integer will be instantiated as a negated number (mod p)
## and store it into a Fp or Fr
## A negative integer will be instantiated as a negated number (mod p) or (mod r)
when src is SomeUnsignedInt:
dst.fromUint(src)
else:
@ -45,7 +45,7 @@ func fromInt*(dst: var Fp,
dst.fromBig(raw)
func exportRawUint*(dst: var openarray[byte],
src: Fp,
src: FF,
dstEndianness: static Endianness) =
## Serialize a finite field element to its canonical big-endian or little-endian
## representation
@ -58,7 +58,7 @@ 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) =
func appendHex*(dst: var string, f: FF, order: static Endianness = bigEndian) =
## Stringify a finite field element to hex.
## Note. Leading zeros are not removed.
## Result is prefixed with 0x
@ -69,7 +69,7 @@ func appendHex*(dst: var string, f: Fp, order: static Endianness = bigEndian) =
## - no leaks
dst.appendHex(f.toBig(), order)
func toHex*(f: Fp, order: static Endianness = bigEndian): string =
func toHex*(f: FF, order: static Endianness = bigEndian): string =
## Stringify a finite field element to hex.
## Note. Leading zeros are not removed.
## Result is prefixed with 0x
@ -80,11 +80,11 @@ func toHex*(f: Fp, order: static Endianness = bigEndian): string =
## - no leaks
result.appendHex(f, order)
func fromHex*(dst: var Fp, hexString: string) {.raises: [ValueError].}=
## Convert a hex string to a element of Fp
func fromHex*(dst: var FF, hexString: string) {.raises: [ValueError].}=
## Convert a hex string to a element of Fp or Fr
let raw {.noinit.} = fromHex(dst.mres.typeof, hexString)
dst.fromBig(raw)
func fromHex*(T: type Fp, hexString: string): T {.noInit, raises: [ValueError].}=
func fromHex*(T: type FF, hexString: string): T {.noInit, raises: [ValueError].}=
## Convert a hex string to a element of Fp
result.fromHex(hexString)

View File

@ -7,7 +7,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../config/[curves, type_fp],
../config/[curves, type_ff],
../towers,
../elliptic/[
ec_shortweierstrass_affine,

View File

@ -7,7 +7,7 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../config/[curves, type_fp],
../config/[curves, type_ff],
../towers,
../elliptic/[
ec_shortweierstrass_affine,

View File

@ -105,7 +105,7 @@ func prod_complex(r: var QuadraticExt, a, b: QuadraticExt) =
# Deactivated for now Clang 360 cycles on i9-9980XE @4.1 GHz
var a0b0 {.noInit.}, a1b1 {.noInit.}: doubleWidth(typeof(r.c0))
var d {.noInit.}: doubleWidth(typeof(r.c0))
const msbSet = r.c0.typeof.C.canUseNoCarryMontyMul()
const msbSet = r.c0.typeof.canUseNoCarryMontyMul()
a0b0.mulNoReduce(a.c0, b.c0) # 44 cycles - cumul 44
a1b1.mulNoReduce(a.c1, b.c1) # 44 cycles - cumul 88

View File

@ -9,12 +9,13 @@
import
../constantine/arithmetic/bigints,
../constantine/primitives,
../constantine/config/[common, curves],
../constantine/config/[common, curves, type_ff],
../constantine/elliptic/[
ec_shortweierstrass_affine,
ec_shortweierstrass_projective,
ec_shortweierstrass_jacobian],
../constantine/io/io_bigints
../constantine/io/io_bigints,
../constantine/tower_field_extensions/tower_common
# ############################################################
#
@ -137,20 +138,21 @@ func random_unsafe(rng: var RngState, a: var BigInt) =
for i in 0 ..< a.limbs.len:
a.limbs[i] = SecretWord(rng.next())
func random_unsafe[T](rng: var RngState, a: var T, C: static Curve) =
## Recursively initialize a BigInt (part of a field) or Field element
func random_unsafe(rng: var RngState, a: var FF) =
## Initialize a Field element
## Unsafe: for testing and benchmarking purposes only
when T is BigInt:
var reduced, unreduced{.noInit.}: T
var reduced, unreduced{.noInit.}: typeof(a.mres)
rng.random_unsafe(unreduced)
# Note: a simple modulo will be biaised but it's simple and "fast"
reduced.reduce(unreduced, C.Mod)
a.montyResidue(reduced, C.Mod, C.getR2modP(), C.getNegInvModWord(), C.canUseNoCarryMontyMul())
reduced.reduce(unreduced, FF.fieldMod())
a.mres.montyResidue(reduced, FF.fieldMod(), FF.getR2modP(), FF.getNegInvModWord(), FF.canUseNoCarryMontyMul())
else:
func random_unsafe(rng: var RngState, a: var ExtensionField) =
## Recursively initialize an extension Field element
## Unsafe: for testing and benchmarking purposes only
for field in fields(a):
rng.random_unsafe(field, C)
rng.random_unsafe(field)
func random_word_highHammingWeight(rng: var RngState): BaseType =
let numZeros = rng.random_unsafe(WordBitWidth div 3) # Average Hamming Weight is 1-0.33/2 = 0.83
@ -165,22 +167,23 @@ func random_highHammingWeight(rng: var RngState, a: var BigInt) =
for i in 0 ..< a.limbs.len:
a.limbs[i] = SecretWord rng.random_word_highHammingWeight()
func random_highHammingWeight[T](rng: var RngState, a: var T, C: static Curve) =
func random_highHammingWeight(rng: var RngState, a: var FF) =
## Recursively initialize a BigInt (part of a field) or Field element
## Unsafe: for testing and benchmarking purposes only
## The result will have a high Hamming Weight
## to have a higher probability of triggering carries
when T is BigInt:
var reduced, unreduced{.noInit.}: T
var reduced, unreduced{.noInit.}: typeof(a.mres)
rng.random_highHammingWeight(unreduced)
# Note: a simple modulo will be biaised but it's simple and "fast"
reduced.reduce(unreduced, C.Mod)
a.montyResidue(reduced, C.Mod, C.getR2modP(), C.getNegInvModWord(), C.canUseNoCarryMontyMul())
reduced.reduce(unreduced, FF.fieldMod())
a.mres.montyResidue(reduced, FF.fieldMod(), FF.getR2modP(), FF.getNegInvModWord(), FF.canUseNoCarryMontyMul())
else:
func random_highHammingWeight(rng: var RngState, a: var ExtensionField) =
## Recursively initialize an extension Field element
## Unsafe: for testing and benchmarking purposes only
for field in fields(a):
rng.random_highHammingWeight(field, C)
rng.random_highHammingWeight(field)
func random_long01Seq(rng: var RngState, a: var openArray[byte]) =
## Initialize a bytearray
@ -210,21 +213,22 @@ func random_long01Seq(rng: var RngState, a: var BigInt) =
else:
a.fromRawUint(buf, littleEndian)
func random_long01Seq[T](rng: var RngState, a: var T, C: static Curve) =
func random_long01Seq(rng: var RngState, a: var FF) =
## Recursively initialize a BigInt (part of a field) or Field element
## It is skewed towards producing strings of 1111... and 0000
## to trigger edge cases
when T is BigInt:
var reduced, unreduced{.noInit.}: T
var reduced, unreduced{.noInit.}: typeof(a.mres)
rng.random_long01Seq(unreduced)
# Note: a simple modulo will be biaised but it's simple and "fast"
reduced.reduce(unreduced, C.Mod)
a.montyResidue(reduced, C.Mod, C.getR2modP(), C.getNegInvModWord(), C.canUseNoCarryMontyMul())
reduced.reduce(unreduced, FF.fieldMod())
a.mres.montyResidue(reduced, FF.fieldMod(), FF.getR2modP(), FF.getNegInvModWord(), FF.canUseNoCarryMontyMul())
else:
func random_long01Seq(rng: var RngState, a: var ExtensionField) =
## Recursively initialize an extension Field element
## Unsafe: for testing and benchmarking purposes only
for field in fields(a):
rng.random_highHammingWeight(field, C)
rng.random_long01Seq(field)
# Elliptic curves
# ------------------------------------------------------------
@ -238,20 +242,20 @@ func random_unsafe(rng: var RngState, a: var (ECP_ShortW_Proj or ECP_ShortW_Aff
while not bool(success):
# Euler's criterion: there are (p-1)/2 squares in a field with modulus `p`
# so we have a probability of ~0.5 to get a good point
rng.random_unsafe(fieldElem, a.F.C)
rng.random_unsafe(fieldElem)
success = trySetFromCoordX(a, fieldElem)
func random_unsafe_with_randZ(rng: var RngState, a: var (ECP_ShortW_Proj or ECP_ShortW_Jac)) =
## Initialize a random curve point with Z coordinate being random
## Unsafe: for testing and benchmarking purposes only
var Z{.noInit.}: a.F
rng.random_unsafe(Z, a.F.C) # If Z is zero, X will be zero and that will be an infinity point
rng.random_unsafe(Z) # If Z is zero, X will be zero and that will be an infinity point
var fieldElem {.noInit.}: a.F
var success = CtFalse
while not bool(success):
rng.random_unsafe(fieldElem, a.F.C)
rng.random_unsafe(fieldElem)
success = trySetFromCoordsXandZ(a, fieldElem, Z)
func random_highHammingWeight(rng: var RngState, a: var (ECP_ShortW_Proj or ECP_ShortW_Aff or ECP_ShortW_Jac)) =
@ -264,7 +268,7 @@ func random_highHammingWeight(rng: var RngState, a: var (ECP_ShortW_Proj or ECP_
while not bool(success):
# Euler's criterion: there are (p-1)/2 squares in a field with modulus `p`
# so we have a probability of ~0.5 to get a good point
rng.random_highHammingWeight(fieldElem, a.F.C)
rng.random_highHammingWeight(fieldElem)
success = trySetFromCoordX(a, fieldElem)
func random_highHammingWeight_with_randZ(rng: var RngState, a: var (ECP_ShortW_Proj or ECP_ShortW_Jac)) =
@ -272,13 +276,13 @@ func random_highHammingWeight_with_randZ(rng: var RngState, a: var (ECP_ShortW_P
## This will be generated with a biaised RNG with high Hamming Weight
## to trigger carry bugs
var Z{.noInit.}: a.F
rng.random_highHammingWeight(Z, a.F.C) # If Z is zero, X will be zero and that will be an infinity point
rng.random_highHammingWeight(Z) # If Z is zero, X will be zero and that will be an infinity point
var fieldElem {.noInit.}: a.F
var success = CtFalse
while not bool(success):
rng.random_highHammingWeight(fieldElem, a.F.C)
rng.random_highHammingWeight(fieldElem)
success = trySetFromCoordsXandZ(a, fieldElem, Z)
func random_long01Seq(rng: var RngState, a: var (ECP_ShortW_Proj or ECP_ShortW_Aff or ECP_ShortW_Jac)) =
@ -292,7 +296,7 @@ func random_long01Seq(rng: var RngState, a: var (ECP_ShortW_Proj or ECP_ShortW_A
while not bool(success):
# Euler's criterion: there are (p-1)/2 squares in a field with modulus `p`
# so we have a probability of ~0.5 to get a good point
rng.random_long01Seq(fieldElem, a.F.C)
rng.random_long01Seq(fieldElem)
success = trySetFromCoordX(a, fieldElem)
func random_long01Seq_with_randZ(rng: var RngState, a: var (ECP_ShortW_Proj or ECP_ShortW_Jac)) =
@ -301,13 +305,13 @@ func random_long01Seq_with_randZ(rng: var RngState, a: var (ECP_ShortW_Proj or E
## that produces long bitstrings of 0 and 1
## to trigger edge cases
var Z{.noInit.}: a.F
rng.random_long01Seq(Z, a.F.C) # If Z is zero, X will be zero and that will be an infinity point
rng.random_long01Seq(Z) # If Z is zero, X will be zero and that will be an infinity point
var fieldElem {.noInit.}: a.F
var success = CtFalse
while not bool(success):
rng.random_long01Seq(fieldElem, a.F.C)
rng.random_long01Seq(fieldElem)
success = trySetFromCoordsXandZ(a, fieldElem, Z)
# Generic over any Constantine type
@ -323,7 +327,7 @@ func random_unsafe*(rng: var RngState, T: typedesc): T =
elif T is BigInt:
rng.random_unsafe(result)
else: # Fields
rng.random_unsafe(result, T.C)
rng.random_unsafe(result)
func random_unsafe_with_randZ*(rng: var RngState, T: typedesc[ECP_ShortW_Proj or ECP_ShortW_Jac]): T =
## Create a random curve element with a random Z coordinate
@ -340,7 +344,7 @@ func random_highHammingWeight*(rng: var RngState, T: typedesc): T =
elif T is BigInt:
rng.random_highHammingWeight(result)
else: # Fields
rng.random_highHammingWeight(result, T.C)
rng.random_highHammingWeight(result)
func random_highHammingWeight_with_randZ*(rng: var RngState, T: typedesc[ECP_ShortW_Proj or ECP_ShortW_Jac]): T =
## Create a random curve element with a random Z coordinate
@ -357,7 +361,7 @@ func random_long01Seq*(rng: var RngState, T: typedesc): T =
elif T is BigInt:
rng.random_long01Seq(result)
else: # Fields
rng.random_long01Seq(result, T.C)
rng.random_long01Seq(result)
func random_long01Seq_with_randZ*(rng: var RngState, T: typedesc[ECP_ShortW_Proj or ECP_ShortW_Jac]): T =
## Create a random curve element with a random Z coordinate

View File

@ -266,7 +266,7 @@ if __name__ == "__main__":
f.write('\n\n')
f.write(inspect.cleandoc(f"""
import
../config/[curves, type_bigint, type_fp],
../config/[curves, type_bigint, type_ff],
../io/[io_bigints, io_fields]
# {curve} G1

View File

@ -285,7 +285,7 @@ if __name__ == "__main__":
else:
f.write(inspect.cleandoc("""
import
../config/[curves, type_fp],
../config/[curves, type_ff],
../towers,
../io/[io_fields, io_towers]
"""))

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/towers,
../constantine/elliptic/ec_shortweierstrass_jacobian,
../constantine/elliptic/ec_shortweierstrass_projective,

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/towers,
../constantine/elliptic/ec_shortweierstrass_jacobian,
../constantine/elliptic/ec_shortweierstrass_projective,

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/towers,
../constantine/elliptic/ec_shortweierstrass_jacobian,
../constantine/elliptic/ec_shortweierstrass_projective,

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/towers,
../constantine/elliptic/ec_shortweierstrass_jacobian,
../constantine/elliptic/ec_shortweierstrass_projective,

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_shortweierstrass_jacobian,
../constantine/elliptic/ec_shortweierstrass_projective,
# Test utilities

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_shortweierstrass_jacobian,
../constantine/elliptic/ec_shortweierstrass_projective,
# Test utilities

View File

@ -12,7 +12,7 @@ import
# 3rd party
serialization, json_serialization,
# Internals
../constantine/config/[common, curves, type_bigint, type_fp],
../constantine/config/[common, curves, type_bigint, type_ff],
../constantine/towers,
../constantine/io/[io_bigints, io_ec],
../constantine/elliptic/[

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_shortweierstrass_jacobian,
# Test utilities
./t_ec_template

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_shortweierstrass_jacobian,
# Test utilities
./t_ec_template

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_shortweierstrass_jacobian,
# Test utilities
./t_ec_template

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_shortweierstrass_jacobian,
# Test utilities
./t_ec_template

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_shortweierstrass_jacobian,
# Test utilities
./t_ec_template

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_shortweierstrass_jacobian,
# Test utilities
./t_ec_template

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_shortweierstrass_jacobian,
# Test utilities
./t_ec_template

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_shortweierstrass_jacobian,
# Test utilities
./t_ec_template

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_shortweierstrass_projective,
# Test utilities
./t_ec_template

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_shortweierstrass_projective,
# Test utilities
./t_ec_template

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_shortweierstrass_projective,
# Test utilities
./t_ec_template

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_shortweierstrass_projective,
# Test utilities
./t_ec_template

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_shortweierstrass_projective,
# Test utilities
./t_ec_template

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_shortweierstrass_projective,
# Test utilities
./t_ec_template

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_shortweierstrass_projective,
# Test utilities
./t_ec_template

View File

@ -8,7 +8,7 @@
import
# Internals
../constantine/config/[type_fp, curves],
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_shortweierstrass_projective,
# Test utilities
./t_ec_template

View File

@ -27,7 +27,7 @@ echo "test_finite_fields_mulsquare xoshiro512** seed: ", seed
static: doAssert defined(testingCurves), "This modules requires the -d:testingCurves compile option"
proc sanity(C: static Curve) =
test "Squaring 0,1,2 with "& $Curve(C) & " [FastSquaring = " & $C.canUseNoCarryMontySquare & "]":
test "Squaring 0,1,2 with "& $Curve(C) & " [FastSquaring = " & $Fp[C].canUseNoCarryMontySquare & "]":
block: # 0² mod
var n: Fp[C]
@ -89,7 +89,7 @@ mainSanity()
proc mainSelectCases() =
suite "Modular Squaring: selected tricky cases" & " [" & $WordBitwidth & "-bit mode]":
test "P-256 [FastSquaring = " & $P256.canUseNoCarryMontySquare & "]":
test "P-256 [FastSquaring = " & $Fp[P256].canUseNoCarryMontySquare & "]":
block:
# Triggered an issue in the (t[N+1], t[N]) = t[N] + (A1, A0)
# between the squaring and reduction step, with t[N+1] and A1 being carry bits.
@ -136,7 +136,7 @@ proc random_long01Seq(C: static Curve) =
doAssert bool(r_mul == r_sqr)
suite "Random Modular Squaring is consistent with Modular Multiplication" & " [" & $WordBitwidth & "-bit mode]":
test "Random squaring mod P-224 [FastSquaring = " & $P224.canUseNoCarryMontySquare & "]":
test "Random squaring mod P-224 [FastSquaring = " & $Fp[P224].canUseNoCarryMontySquare & "]":
for _ in 0 ..< Iters:
randomCurve(P224)
for _ in 0 ..< Iters:
@ -144,7 +144,7 @@ suite "Random Modular Squaring is consistent with Modular Multiplication" & " ["
for _ in 0 ..< Iters:
random_long01Seq(P224)
test "Random squaring mod P-256 [FastSquaring = " & $P256.canUseNoCarryMontySquare & "]":
test "Random squaring mod P-256 [FastSquaring = " & $Fp[P256].canUseNoCarryMontySquare & "]":
for _ in 0 ..< Iters:
randomCurve(P256)
for _ in 0 ..< Iters:
@ -152,7 +152,7 @@ suite "Random Modular Squaring is consistent with Modular Multiplication" & " ["
for _ in 0 ..< Iters:
random_long01Seq(P256)
test "Random squaring mod BLS12_381 [FastSquaring = " & $BLS12_381.canUseNoCarryMontySquare & "]":
test "Random squaring mod BLS12_381 [FastSquaring = " & $Fp[BLS12_381].canUseNoCarryMontySquare & "]":
for _ in 0 ..< Iters:
randomCurve(BLS12_381)
for _ in 0 ..< Iters:

129
tests/t_fr.nim Normal file
View File

@ -0,0 +1,129 @@
# 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],
# Internal
../constantine/arithmetic,
../constantine/io/[io_bigints, io_fields],
../constantine/config/[curves, common, type_bigint],
# Test utilities
../helpers/prng_unsafe
const Iters = 24
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_fr xoshiro512** seed: ", seed
proc sanity(C: static Curve) =
test "Fr: Squaring 0,1,2 with "& $Fr[C] & " [FastSquaring = " & $Fr[C].canUseNoCarryMontySquare & "]":
block: # 0² mod
var n: Fr[C]
n.fromUint(0'u32)
let expected = n
# Out-of-place
var r: Fr[C]
r.square(n)
# In-place
n.square()
check:
bool(r == expected)
bool(n == expected)
block: # 1² mod
var n: Fr[C]
n.fromUint(1'u32)
let expected = n
# Out-of-place
var r: Fr[C]
r.square(n)
# In-place
n.square()
check:
bool(r == expected)
bool(n == expected)
block: # 2² mod
var n, expected: Fr[C]
n.fromUint(2'u32)
expected.fromUint(4'u32)
# Out-of-place
var r: Fr[C]
r.square(n)
# In-place
n.square()
check:
bool(r == expected)
bool(n == expected)
proc mainSanity() =
suite "Fr: Modular squaring is consistent with multiplication on special elements" & " [" & $WordBitwidth & "-bit mode]":
sanity BN254_Snarks
sanity BLS12_381
mainSanity()
proc randomCurve(C: static Curve) =
let a = rng.random_unsafe(Fr[C])
var r_mul, r_sqr: Fr[C]
r_mul.prod(a, a)
r_sqr.square(a)
doAssert bool(r_mul == r_sqr)
proc randomHighHammingWeight(C: static Curve) =
let a = rng.random_highHammingWeight(Fr[C])
var r_mul, r_sqr: Fr[C]
r_mul.prod(a, a)
r_sqr.square(a)
doAssert bool(r_mul == r_sqr)
proc random_long01Seq(C: static Curve) =
let a = rng.random_long01Seq(Fr[C])
var r_mul, r_sqr: Fr[C]
r_mul.prod(a, a)
r_sqr.square(a)
doAssert bool(r_mul == r_sqr)
suite "Fr: Random Modular Squaring is consistent with Modular Multiplication" & " [" & $WordBitwidth & "-bit mode]":
test "Random squaring mod r_BN254_Snarks [FastSquaring = " & $Fr[BN254_Snarks].canUseNoCarryMontySquare & "]":
for _ in 0 ..< Iters:
randomCurve(BN254_Snarks)
for _ in 0 ..< Iters:
randomHighHammingWeight(BN254_Snarks)
for _ in 0 ..< Iters:
random_long01Seq(BN254_Snarks)
test "Random squaring mod r_BLS12_381 [FastSquaring = " & $Fr[BLS12_381].canUseNoCarryMontySquare & "]":
for _ in 0 ..< Iters:
randomCurve(BLS12_381)
for _ in 0 ..< Iters:
randomHighHammingWeight(BLS12_381)
for _ in 0 ..< Iters:
random_long01Seq(BLS12_381)