Less magics, les macros, faster compile-times (or not, Fp6 starts to get really slow, like 5s) + some cleanups in curve families + test 𝔽p6 on 32-bit

This commit is contained in:
Mamy André-Ratsimbazafy 2020-03-22 12:28:53 +01:00
parent c40bc1977d
commit 2d5b173a39
No known key found for this signature in database
GPG Key ID: 7B88AD1FE79492E1
9 changed files with 97 additions and 196 deletions

View File

@ -72,8 +72,8 @@ task test, "Run all tests":
test "-d:Constantine32", "tests/test_finite_fields_vs_gmp.nim"
# Towers of extension fields
test "", "tests/test_fp2.nim"
test "", "tests/test_fp6.nim"
test "-d:Constantine32", "tests/test_fp2.nim"
test "-d:Constantine32", "tests/test_fp6.nim"
task test_no_gmp, "Run tests that don't require GMP":
# -d:testingCurves is configured in a *.nim.cfg for convenience
@ -112,8 +112,8 @@ task test_no_gmp, "Run tests that don't require GMP":
test "-d:Constantine32", "tests/test_finite_fields_powinv.nim"
# Towers of extension fields
test "", "tests/test_fp2.nim"
test "", "tests/test_fp6.nim"
test "-d:Constantine32", "tests/test_fp2.nim"
test "-d:Constantine32", "tests/test_fp6.nim"
proc runBench(benchName: string, compiler = "") =
if not dirExists "build":

View File

@ -29,14 +29,13 @@ import
../config/[common, curves],
./bigints, ./limbs_montgomery
# type
# `Fp`*[C: static Curve] = object
# ## All operations on a field are modulo P
# ## P being the prime modulus of the Curve C
# ## Internally, data is stored in Montgomery n-residue form
# ## with the magic constant chosen for convenient division (a power of 2 depending on P bitsize)
# mres*: matchingBigInt(C)
export Fp # defined in ../config/curves to avoid recursive module dependencies
type
Fp*[C: static Curve] = object
## All operations on a field are modulo P
## P being the prime modulus of the Curve C
## Internally, data is stored in Montgomery n-residue form
## with the magic constant chosen for convenient division (a power of 2 depending on P bitsize)
mres*: matchingBigInt(C)
debug:
func `$`*[C: static Curve](a: Fp[C]): string =
@ -57,16 +56,16 @@ debug:
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.mres, C.getR2modP(), C.getNegInvModWord(), C.canUseNoCarryMontyMul())
result.mres.montyResidue(src, C.Mod, C.getR2modP(), C.getNegInvModWord(), C.canUseNoCarryMontyMul())
func fromBig*[C: static Curve](dst: var Fp[C], src: BigInt) {.noInit.} =
## Convert a BigInt to its Montgomery form
dst.mres.montyResidue(src, C.Mod.mres, C.getR2modP(), C.getNegInvModWord(), C.canUseNoCarryMontyMul())
dst.mres.montyResidue(src, C.Mod, C.getR2modP(), C.getNegInvModWord(), C.canUseNoCarryMontyMul())
func toBig*(src: Fp): auto {.noInit.} =
## Convert a finite-field element to a BigInt in natural representation
var r {.noInit.}: typeof(src.mres)
r.redc(src.mres, Fp.C.Mod.mres, Fp.C.getNegInvModWord(), Fp.C.canUseNoCarryMontyMul())
r.redc(src.mres, Fp.C.Mod, Fp.C.getNegInvModWord(), Fp.C.canUseNoCarryMontyMul())
return r
# ############################################################
@ -84,7 +83,7 @@ func toBig*(src: Fp): auto {.noInit.} =
# exist and can be implemented with compile-time specialization.
# Note: for `+=`, double, sum
# not(a.mres < Fp.C.Mod.mres) is unnecessary if the prime has the form
# not(a.mres < Fp.C.Mod) 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
@ -107,52 +106,52 @@ func setOne*(a: var Fp) =
func `+=`*(a: var Fp, b: Fp) =
## In-place addition modulo p
var overflowed = add(a.mres, b.mres)
overflowed = overflowed or not(a.mres < Fp.C.Mod.mres)
discard csub(a.mres, Fp.C.Mod.mres, overflowed)
overflowed = overflowed or not(a.mres < Fp.C.Mod)
discard csub(a.mres, Fp.C.Mod, overflowed)
func `-=`*(a: var Fp, b: Fp) =
## In-place substraction modulo p
let underflowed = sub(a.mres, b.mres)
discard cadd(a.mres, Fp.C.Mod.mres, underflowed)
discard cadd(a.mres, Fp.C.Mod, underflowed)
func double*(a: var Fp) =
## Double ``a`` modulo p
var overflowed = double(a.mres)
overflowed = overflowed or not(a.mres < Fp.C.Mod.mres)
discard csub(a.mres, Fp.C.Mod.mres, overflowed)
overflowed = overflowed or not(a.mres < Fp.C.Mod)
discard csub(a.mres, Fp.C.Mod, overflowed)
func sum*(r: var Fp, a, b: Fp) =
## Sum ``a`` and ``b`` into ``r`` module p
## r is initialized/overwritten
var overflowed = r.mres.sum(a.mres, b.mres)
overflowed = overflowed or not(r.mres < Fp.C.Mod.mres)
discard csub(r.mres, Fp.C.Mod.mres, overflowed)
overflowed = overflowed or not(r.mres < Fp.C.Mod)
discard csub(r.mres, Fp.C.Mod, overflowed)
func diff*(r: var Fp, a, b: Fp) =
## Substract `b` from `a` and store the result into `r`.
## `r` is initialized/overwritten
var underflowed = r.mres.diff(a.mres, b.mres)
discard cadd(r.mres, Fp.C.Mod.mres, underflowed)
discard cadd(r.mres, Fp.C.Mod, underflowed)
func double*(r: var Fp, a: Fp) =
## Double ``a`` into ``r``
## `r` is initialized/overwritten
var overflowed = r.mres.double(a.mres)
overflowed = overflowed or not(r.mres < Fp.C.Mod.mres)
discard csub(r.mres, Fp.C.Mod.mres, overflowed)
overflowed = overflowed or not(r.mres < Fp.C.Mod)
discard csub(r.mres, Fp.C.Mod, overflowed)
func prod*(r: var Fp, a, b: Fp) =
## 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.mres, Fp.C.getNegInvModWord(), Fp.C.canUseNoCarryMontyMul())
r.mres.montyMul(a.mres, b.mres, Fp.C.Mod, Fp.C.getNegInvModWord(), Fp.C.canUseNoCarryMontyMul())
func square*(r: var Fp, a: Fp) =
## Squaring modulo p
r.mres.montySquare(a.mres, Fp.C.Mod.mres, Fp.C.getNegInvModWord(), Fp.C.canUseNoCarryMontySquare())
r.mres.montySquare(a.mres, Fp.C.Mod, Fp.C.getNegInvModWord(), Fp.C.canUseNoCarryMontySquare())
func neg*(r: var Fp, a: Fp) =
## Negate modulo p
discard r.mres.diff(Fp.C.Mod.mres, a.mres)
discard r.mres.diff(Fp.C.Mod, a.mres)
# ############################################################
#
@ -169,7 +168,7 @@ func pow*(a: var Fp, exponent: BigInt) =
const windowSize = 5 # TODO: find best window size for each curves
a.mres.montyPow(
exponent,
Fp.C.Mod.mres, Fp.C.getMontyOne(),
Fp.C.Mod, Fp.C.getMontyOne(),
Fp.C.getNegInvModWord(), windowSize,
Fp.C.canUseNoCarryMontyMul()
)
@ -188,7 +187,7 @@ func powUnsafeExponent*(a: var Fp, exponent: BigInt) =
const windowSize = 5 # TODO: find best window size for each curves
a.mres.montyPowUnsafeExponent(
exponent,
Fp.C.Mod.mres, Fp.C.getMontyOne(),
Fp.C.Mod, Fp.C.getMontyOne(),
Fp.C.getNegInvModWord(), windowSize,
Fp.C.canUseNoCarryMontyMul()
)
@ -228,4 +227,4 @@ func `*=`*(a: var Fp, b: Fp) =
func square*(a: var Fp) =
## Squaring modulo p
a.mres.montySquare(a.mres, Fp.C.Mod.mres, Fp.C.getNegInvModWord(), Fp.C.canUseNoCarryMontySquare())
a.mres.montySquare(a.mres, Fp.C.Mod, Fp.C.getNegInvModWord(), Fp.C.canUseNoCarryMontySquare())

View File

@ -131,4 +131,4 @@ func inv*(r: var Fp, a: Fp) =
# For now we don't activate the addition chain.
# Performance is equal to GCD and it does not pass test on 𝔽p2
# We need faster squaring/multiplications
r.mres.steinsGCD(a.mres, Fp.C.getR2modP(), Fp.C.Mod.mres, Fp.C.getPrimePlus1div2())
r.mres.steinsGCD(a.mres, Fp.C.getR2modP(), Fp.C.Mod, Fp.C.getPrimePlus1div2())

View File

@ -131,62 +131,7 @@ declareCurves:
# ############################################################
#
# Curve Families
#
# ############################################################
type CurveFamily = enum
None
BN # Barreto-Naehrig
BLS # Barreto-Lynn-Scott
func family*(curve: Curve): CurveFamily =
case curve
of BN254:
BN
of BLS12_381:
BLS
else:
None
# ############################################################
#
# Curve Specific Parameters
#
# ############################################################
#
# In the form CurveXXX_ParameterName where CurveXXX is the curve name + number of bits
# of the field modulus
# BN Curves
# ------------------------------------------------------------
# See https://tools.ietf.org/id/draft-yonezawa-pairing-friendly-curves-00.html
#
# The prime p and order r are primes and of the form
# p = 36u^4 + 36u^3 + 24u^2 + 6u + 1
# r = 36u^4 + 36u^3 + 18u^2 + 6u + 1
#
# https://eprint.iacr.org/2010/429.pdf
# https://eprint.iacr.org/2013/879.pdf
# Usage: Zero-Knowledge Proofs / zkSNARKs in ZCash and Ethereum 1
# https://eips.ethereum.org/EIPS/eip-196
# BLS Curves
# ------------------------------------------------------------
# See https://tools.ietf.org/id/draft-yonezawa-pairing-friendly-curves-00.html
#
# BLS12 curves
# The prime p and order r are primes and of the form
# p = (u - 1)^2 (u^4 - u^2 + 1)/3 + u
# r = u^4 - u^2 + 1
#
# BLS48 curves
# The prime p and order r are primes and of the form
# p = (u - 1)^2 (u^16 - u^8 + 1)/3 + u
# r = u^16 - u^8 + 1
# ############################################################
#
# Curve Modulus Accessor
# Curve characteristics
#
# ############################################################
@ -196,6 +141,13 @@ macro Mod*(C: static Curve): untyped =
## Get the Modulus associated to a curve
result = bindSym($C & "_Modulus")
func getCurveBitSize*(C: static Curve): static int =
## Returns the number of bits taken by the curve modulus
result = static(CurveBitSize[C])
template matchingBigInt*(C: static Curve): untyped =
BigInt[CurveBitSize[C]]
# ############################################################
#
# Autogeneration of precomputed Montgomery constants in ROM
@ -220,10 +172,7 @@ macro genMontyMagics(T: typed): untyped =
result.add newConstStmt(
ident($curve & "_CanUseNoCarryMontyMul"), newCall(
bindSym"useNoCarryMontyMul",
nnkDotExpr.newTree(
bindSym($curve & "_Modulus"),
ident"mres"
)
bindSym($curve & "_Modulus")
)
)
@ -231,10 +180,7 @@ macro genMontyMagics(T: typed): untyped =
result.add newConstStmt(
ident($curve & "_CanUseNoCarryMontySquare"), newCall(
bindSym"useNoCarryMontySquare",
nnkDotExpr.newTree(
bindSym($curve & "_Modulus"),
ident"mres"
)
bindSym($curve & "_Modulus")
)
)
@ -242,10 +188,7 @@ macro genMontyMagics(T: typed): untyped =
result.add newConstStmt(
ident($curve & "_R2modP"), newCall(
bindSym"r2mod",
nnkDotExpr.newTree(
bindSym($curve & "_Modulus"),
ident"mres"
)
bindSym($curve & "_Modulus")
)
)
@ -253,40 +196,28 @@ macro genMontyMagics(T: typed): untyped =
result.add newConstStmt(
ident($curve & "_NegInvModWord"), newCall(
bindSym"negInvModWord",
nnkDotExpr.newTree(
bindSym($curve & "_Modulus"),
ident"mres"
)
bindSym($curve & "_Modulus")
)
)
# const MyCurve_montyOne = montyOne(MyCurve_Modulus)
result.add newConstStmt(
ident($curve & "_MontyOne"), newCall(
bindSym"montyOne",
nnkDotExpr.newTree(
bindSym($curve & "_Modulus"),
ident"mres"
)
bindSym($curve & "_Modulus")
)
)
# const MyCurve_InvModExponent = primeMinus2_BE(MyCurve_Modulus)
result.add newConstStmt(
ident($curve & "_InvModExponent"), newCall(
bindSym"primeMinus2_BE",
nnkDotExpr.newTree(
bindSym($curve & "_Modulus"),
ident"mres"
)
bindSym($curve & "_Modulus")
)
)
# const MyCurve_PrimePlus1div2 = primePlus1div2(MyCurve_Modulus)
result.add newConstStmt(
ident($curve & "_PrimePlus1div2"), newCall(
bindSym"primePlus1div2",
nnkDotExpr.newTree(
bindSym($curve & "_Modulus"),
ident"mres"
)
bindSym($curve & "_Modulus")
)
)
@ -294,10 +225,6 @@ macro genMontyMagics(T: typed): untyped =
genMontyMagics(Curve)
func getCurveBitSize*(C: static Curve): static int =
## Returns the number of bits taken by the curve modulus
result = static(CurveBitSize[C])
macro canUseNoCarryMontyMul*(C: static Curve): untyped =
## Returns true if the Modulus is compatible with a fast
## Montgomery multiplication that avoids many carries

View File

@ -59,8 +59,6 @@ macro declareCurves*(curves: untyped): untyped =
var CurveBitSize = nnKBracket.newTree()
var curveModStmts = newStmtList()
let Fp = ident"Fp"
for curveDesc in curves:
curveDesc.expectKind(nnkCommand)
doAssert curveDesc[0].eqIdent"curve"
@ -94,20 +92,14 @@ macro declareCurves*(curves: untyped): untyped =
curve, bitSize
)
# const BN254_Modulus = Fp[BN254](value: fromHex(BigInt[254], "0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47"))
# const BN254_Modulus = fromHex(BigInt[254], "0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47")
let modulusID = ident($curve & "_Modulus")
curveModStmts.add newConstStmt(
modulusID,
nnkObjConstr.newTree(
nnkBracketExpr.newTree(Fp, curve),
nnkExprColonExpr.newTree(
ident"mres",
newCall(
bindSym"fromHex",
nnkBracketExpr.newTree(bindSym"BigInt", bitSize),
modulus
)
)
newCall(
bindSym"fromHex",
nnkBracketExpr.newTree(bindSym"BigInt", bitSize),
modulus
)
)
@ -130,45 +122,6 @@ macro declareCurves*(curves: untyped): untyped =
cbs, CurveBitSize
)
# Need template indirection in the type section to avoid Nim sigmatch bug
# template matchingBigInt(C: static Curve): untyped =
# BigInt[CurveBitSize[C]]
let C = ident"C"
let matchingBigInt = genSym(nskTemplate, "matchingBigInt")
result.add newProc(
name = matchingBigInt,
params = [ident"untyped", newIdentDefs(C, nnkStaticTy.newTree(Curve))],
body = nnkBracketExpr.newTree(bindSym"BigInt", nnkBracketExpr.newTree(cbs, C)),
procType = nnkTemplateDef
)
# type
# `Fp`*[C: static Curve] = object
# ## All operations on a field are modulo P
# ## P being the prime modulus of the Curve C
# ## Internally, data is stored in Montgomery n-residue form
# ## with the magic constant chosen for convenient division (a power of 2 depending on P bitsize)
# mres*: matchingBigInt(C)
result.add nnkTypeSection.newTree(
nnkTypeDef.newTree(
nnkPostfix.newTree(ident"*", Fp),
nnkGenericParams.newTree(newIdentDefs(
C, nnkStaticTy.newTree(Curve), newEmptyNode()
)),
# TODO: where should I put the nnkCommentStmt?
nnkObjectTy.newTree(
newEmptyNode(),
newEmptyNode(),
nnkRecList.newTree(
newIdentDefs(
nnkPostfix.newTree(ident"*", ident"mres"),
newCall(matchingBigInt, C)
)
)
)
)
)
result.add curveModStmts
# echo result.toStrLit()

View File

@ -90,8 +90,8 @@ func random[T](rng: var RngState, a: var T, C: static Curve) {.noInit.}=
unreduced.limbs[i] = Word(rng.next())
# Note: a simple modulo will be biaised but it's simple and "fast"
reduced.reduce(unreduced, C.Mod.mres)
a.montyResidue(reduced, C.Mod.mres, C.getR2modP(), C.getNegInvModWord(), C.canUseNoCarryMontyMul())
reduced.reduce(unreduced, C.Mod)
a.montyResidue(reduced, C.Mod, C.getR2modP(), C.getNegInvModWord(), C.canUseNoCarryMontyMul())
else:
for field in fields(a):

View File

@ -6,14 +6,35 @@
# * 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.
# This script checks polynomial irreducibility
#
# Constructing Tower Extensions for the implementation of Pairing-Based Cryptography
# Naomi Benger and Michael Scott, 2009
# https://eprint.iacr.org/2009/556
# ############################################################
#
# Quadratic and Cubic Non-Residue
# Failed experiments of actually instantiating
# the tower of extension fields
#
# ############################################################
#
# This script checks the compatibility of a field modulus
# with given tower extensions
# ############################################################
# 1st try
@ -50,19 +71,20 @@
# K.<xi, im, p> = NumberField([x^3 - I - 1, x^2 + 1, x - 1])
# ############################################################
# Let's at least verify Fp6
print('Verifying non-residues')
# 4th try, just trying to verify Fp6
# print('Verifying non-residues')
modulus = Integer('0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47')
# modulus = Integer('0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47')
Fp.<p> = NumberField(x - 1)
r1 = Fp(-1).residue_symbol(Fp.ideal(modulus),2)
print('Fp² = Fp[sqrt(-1)]: ' + str(r1))
# Fp.<p> = NumberField(x - 1)
# r1 = Fp(-1).residue_symbol(Fp.ideal(modulus),2)
# print('Fp² = Fp[sqrt(-1)]: ' + str(r1))
Fp2.<im> = Fp.extension(x^2 + 1)
xi = Fp2(1+im)
r2 = xi.residue_symbol(Fp2.ideal(modulus),3)
# ValueError: The residue symbol to that power is not defined for the number field
# ^ AFAIK that means that Fp2 doesn't contain the 3rd root of unity
# so we are clear
print('Fp6 = Fp²[cubicRoot(1+I)]: ' + str(r2))
# Fp2.<im> = Fp.extension(x^2 + 1)
# xi = Fp2(1+im)
# r2 = xi.residue_symbol(Fp2.ideal(modulus),3)
# # ValueError: The residue symbol to that power is not defined for the number field
# # ^ AFAIK that means that Fp2 doesn't contain the 3rd root of unity
# # so we are clear
# print('Fp6 = Fp²[cubicRoot(1+I)]: ' + str(r2))

View File

@ -61,7 +61,7 @@ proc binary_prologue[C: static Curve, N: static int](
mpz_urandomb(a, gmpRng, uint bits)
mpz_urandomb(b, gmpRng, uint bits)
# Set modulus to curve modulus
let err = mpz_set_str(p, Curve(C).Mod.mres.toHex(), 0)
let err = mpz_set_str(p, Curve(C).Mod.toHex(), 0)
doAssert err == 0, "Error on prime for curve " & $Curve(C)
#########################################################

View File

@ -40,12 +40,12 @@ suite "𝔽p2 = 𝔽p[𝑖] (irreducible polynomial x²+1)":
O.setOne()
O
let oneBig = block:
var O{.noInit.}: typeof(C.Mod.mres)
var O{.noInit.}: typeof(C.Mod)
O.setOne()
O
var r: typeof(C.Mod.mres)
r.redc(oneFp2.c0.mres, C.Mod.mres, C.getNegInvModWord(), canUseNoCarryMontyMul = false)
var r: typeof(C.Mod)
r.redc(oneFp2.c0.mres, C.Mod, C.getNegInvModWord(), canUseNoCarryMontyMul = false)
check:
bool(r == oneBig)