Initial support for Twisted Edwards curves (#167)

* Point decoding: optimized sqrt for p ≡ 5 (mod 8) (Curve25519)

* Implement fused sqrt(u/v) for twisted edwards point deserialization

* Introduce twisted edwards affine

* Allow declaration of curve field elements (and fight against recursive dependencies

* Twisted edwards group law + tests

* Add support for jubjub and bandersnatch #162

* test twisted edwards scalar mul
This commit is contained in:
Mamy Ratsimbazafy 2021-12-29 01:54:17 +01:00 committed by GitHub
parent 1195e5e980
commit 53f9708c2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1311 additions and 192 deletions

View File

@ -89,8 +89,15 @@ Curves:
- BN254_Snarks (Zero-Knowledge Proofs, Snarks, Starks, Zcash, Ethereum 1) - BN254_Snarks (Zero-Knowledge Proofs, Snarks, Starks, Zcash, Ethereum 1)
- BLS12-377 (Zexe) - BLS12-377 (Zexe)
- BLS12-381 (Algorand, Chia Networks, Dfinity, Ethereum 2, Filecoin, Zcash Sapling) - BLS12-381 (Algorand, Chia Networks, Dfinity, Ethereum 2, Filecoin, Zcash Sapling)
- BW6-671 (Celo, EY Blockchain) (Pairings are WIP) - BW6-671 (Celo, EY Blockchain) (Pairings are WIP)\
BLS12-377 is embedded in BW6-761 for one layer proof composition in zk-SNARKS.
### Other curves
- Curve25519, used in ed25519 and X25519 from TLS 1.3 protocol and the Signal protocol.
With Ristretto, it can be used in bulletproofs.
- Jubjub, a curve embedded in BLS12-381 scalar field to be used in zk-SNARKS circuits.
- Bandersnatch, a more efficient curve embedded in BLS12-381 scalar field to be used in zk-SNARKS circuits.
## Security ## Security
Hardening an implementation against all existing and upcoming attack vectors is an extremely complex task. Hardening an implementation against all existing and upcoming attack vectors is an extremely complex task.

View File

@ -78,6 +78,11 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[
("tests/t_ec_shortw_jac_g1_mul_vs_ref.nim", false), ("tests/t_ec_shortw_jac_g1_mul_vs_ref.nim", false),
("tests/t_ec_shortw_jac_g1_mixed_add.nim", false), ("tests/t_ec_shortw_jac_g1_mixed_add.nim", false),
("tests/t_ec_twedwards_prj_add_double", false),
("tests/t_ec_twedwards_prj_mul_sanity", false),
("tests/t_ec_twedwards_prj_mul_distri", false),
# Elliptic curve arithmetic G2 # Elliptic curve arithmetic G2
# ---------------------------------------------------------- # ----------------------------------------------------------
# ("tests/t_ec_shortw_prj_g2_add_double_bn254_snarks.nim", false), # ("tests/t_ec_shortw_prj_g2_add_double_bn254_snarks.nim", false),

View File

@ -35,3 +35,21 @@ where code size is not an issue for example for multi-precision addition.
- Faster big-integer modular multiplication for most moduli\ - Faster big-integer modular multiplication for most moduli\
Gautam Botrel, Gus Gutoski, and Thomas Piellard, 2020\ Gautam Botrel, Gus Gutoski, and Thomas Piellard, 2020\
https://hackmd.io/@zkteam/modular_multiplication https://hackmd.io/@zkteam/modular_multiplication
### Square roots
- Probabilistic Primality Testing
A. Oliver L. Atkin
http://algo.inria.fr/seminars/sem91-92/atkin.pdf
- Square root computation over even extension fields
Gora Adj, Francisco Rodríguez-Henríquez, 2012
https://eprint.iacr.org/2012/685
- A Complete Generalization of Atkins Square Root Algorithm
Armand Stefan Rotaru, Sorin Iftene, 2013
https://profs.info.uaic.ro/~siftene/fi125(1)04.pdf
- Computing Square Roots Faster than the Tonelli-Shanks/Bernstein Algorithm
Palash Sarkar, 2020
https://eprint.iacr.org/2020/1407

View File

@ -28,7 +28,7 @@
import import
../primitives, ../primitives,
../config/[common, type_ff, curves], ../config/[common, type_ff, curves_prop_field_core, curves_prop_field_derived],
./bigints, ./bigints_montgomery ./bigints, ./bigints_montgomery
when UseASM_X86_64: when UseASM_X86_64:
@ -138,6 +138,13 @@ func setOne*(a: var FF) =
# Check if the compiler optimizes it away # Check if the compiler optimizes it away
a.mres = FF.getMontyOne() a.mres = FF.getMontyOne()
func setMinusOne*(a: var FF) =
## Set ``a`` to -1 (mod p)
# 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 = FF.getMontyPrimeMinus1()
func `+=`*(a: var FF, b: FF) {.meter.} = func `+=`*(a: var FF, b: FF) {.meter.} =
## In-place addition modulo p ## In-place addition modulo p
when UseASM_X86_64 and a.mres.limbs.len <= 6: # TODO: handle spilling when UseASM_X86_64 and a.mres.limbs.len <= 6: # TODO: handle spilling

View File

@ -10,7 +10,8 @@ import
../primitives, ../primitives,
../config/[common, type_ff, curves], ../config/[common, type_ff, curves],
../curves/zoo_square_roots, ../curves/zoo_square_roots,
./bigints, ./finite_fields ./bigints, ./finite_fields,
./finite_fields_inversion
# ############################################################ # ############################################################
# #
@ -65,19 +66,95 @@ func invsqrt_p3mod4*(r: var Fp, a: Fp) =
## The square root, if it exist is multivalued, ## The square root, if it exist is multivalued,
## i.e. both x² == (-x)² ## i.e. both x² == (-x)²
## This procedure returns a deterministic result ## This procedure returns a deterministic result
# TODO: deterministic sign
#
# Algorithm # Algorithm
# #
# #
# From Euler's criterion: a^((p-1)/2)) ≡ 1 (mod p) if square # From Euler's criterion:
# 𝛘(a) = a^((p-1)/2)) ≡ 1 (mod p) if square
# a^((p-1)/2)) * a^-1 ≡ 1/a (mod p) # a^((p-1)/2)) * a^-1 ≡ 1/a (mod p)
# a^((p-3)/2)) ≡ 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) # a^((p-3)/4)) ≡ 1/√a (mod p) # Requires p ≡ 3 (mod 4)
static: doAssert BaseType(Fp.C.Mod.limbs[0]) mod 4 == 3 static: doAssert Fp.C.hasP3mod4_primeModulus()
r = a r = a
r.powUnsafeExponent(Fp.getPrimeMinus3div4_BE()) r.powUnsafeExponent(Fp.getPrimeMinus3div4_BE())
# Specialized routine for p ≡ 5 (mod 8)
# ------------------------------------------------------------
func hasP5mod8_primeModulus(C: static Curve): static bool =
## Returns true iff p ≡ 5 (mod 8)
(BaseType(C.Mod.limbs[0]) and 7) == 5
func invsqrt_p5mod8*(r: var Fp, a: Fp) =
## Compute the inverse square root of ``a``
##
## This requires ``a`` to be a square
## and the prime field modulus ``p``: p ≡ 5 (mod 8)
##
## The result is undefined otherwise
##
## The square root, if it exist is multivalued,
## i.e. both x² == (-x)²
## This procedure returns a deterministic result
#
# Intuition: Branching algorithm, that requires √-1 (mod p) precomputation
#
# From Euler's criterion:
# 𝛘(a) = a^((p-1)/2)) ≡ 1 (mod p) if square
# a^((p-1)/4))² ≡ 1 (mod p)
# if a is square, a^((p-1)/4)) ≡ ±1 (mod p)
#
# Case a^((p-1)/4)) ≡ 1 (mod p)
# a^((p-1)/4)) * a⁻¹ ≡ 1/a (mod p)
# a^((p-5)/4)) ≡ 1/a (mod p)
# a^((p-5)/8)) ≡ ±1/√a (mod p) # Requires p ≡ 5 (mod 8)
#
# Case a^((p-1)/4)) ≡ -1 (mod p)
# a^((p-1)/4)) * a⁻¹ ≡ -1/a (mod p)
# a^((p-5)/4)) ≡ -1/a (mod p)
# a^((p-5)/8)) ≡ ± √-1/√a (mod p)
# as p ≡ 5 (mod 8), hence 𝑖 ∈ Fp with 𝑖² ≡ 1 (mod p)
# a^((p-5)/8)) * 𝑖 ≡ ± 1/√a (mod p)
#
# Atkin Algorithm: branchless, no precomputation
# Atkin, 1992, http://algo.inria.fr/seminars/sem91-92/atkin.pdf
# Gora Adj 2012, https://eprint.iacr.org/2012/685
# Rotaru, 2013, https://profs.info.uaic.ro/~siftene/fi125(1)04.pdf
#
# We express √a = αa(β 1) where β² = 1 and 2aα² = β
# confirm that (αa(β 1))² = α²a²(β²-2β+1) = α²a²β² - 2a²α²β - a²α²
# Which simplifies to (αa(β 1))² = -aβ² = a
#
# 𝛘(2) = 2^((p-1)/2) ≡ (-1)^((p²-1)/8) (mod p) hence 2 is QR iff p ≡ ±1 (mod 8)
# Here p ≡ 5 (mod 8), so 2 is a QNR, hence 2^((p-1)/2) ≡ -1 (mod 8)
#
# The product of a quadratic non-residue with quadratic residue is a QNR
# as 𝛘(QNR*QR) = 𝛘(QNR).𝛘(QR) = -1*1 = -1, hence:
# (2a)^((p-1)/2) ≡ -1 (mod p)
# (2a)^((p-1)/4) ≡ ± √-1 (mod p)
#
# Hence we set β = (2a)^((p-1)/4)
# and α = (β/2a)⁽¹⸍²⁾= (2a)^(((p-1)/4 - 1)/2) = (2a)^((p-5)/8)
static: doAssert Fp.C.hasP5mod8_primeModulus()
var alpha{.noInit.}, beta{.noInit.}: Fp
# α = (2a)^((p-5)/8)
alpha.double(a)
beta = alpha
alpha.powUnsafeExponent(Fp.getPrimeMinus5div8_BE())
# Note: if r aliases a, for inverse square root we don't use `a` again
# β = 2aα²
r.square(alpha)
beta *= r
# √a = αa(β 1), so 1/√a = α 1)
r.setOne()
beta -= r
r.prod(alpha, beta)
# Specialized routines for addchain-based square roots # Specialized routines for addchain-based square roots
# ------------------------------------------------------------ # ------------------------------------------------------------
@ -192,6 +269,8 @@ func invsqrt*[C](r: var Fp[C], a: Fp[C]) =
r.invsqrt_addchain(a) r.invsqrt_addchain(a)
elif C.hasP3mod4_primeModulus(): elif C.hasP3mod4_primeModulus():
r.invsqrt_p3mod4(a) r.invsqrt_p3mod4(a)
elif C.hasP5mod8_primeModulus():
r.invsqrt_p5mod8(a)
else: else:
r.invsqrt_tonelli_shanks(a, useAddChain = C.hasTonelliShanksAddchain()) r.invsqrt_tonelli_shanks(a, useAddChain = C.hasTonelliShanksAddchain())
@ -262,4 +341,88 @@ func invsqrt_if_square*[C](r: var Fp[C], a: Fp[C]): SecretBool =
result = sqrt_invsqrt_if_square(sqrt, r, a) result = sqrt_invsqrt_if_square(sqrt, r, a)
{.pop.} # inline {.pop.} # inline
# Fused routines
# ------------------------------------------------------------
func sqrt_ratio_if_square_p5mod8(r: var Fp, u, v: Fp): SecretBool =
## If u/v is a square, compute √(u/v)
## if not, the result is undefined
##
## Requires p ≡ 5 (mod 8)
## r must not alias u or v
##
## The square root, if it exist is multivalued,
## i.e. both (u/v)² == (-u/v)²
## This procedure returns a deterministic result
## This procedure is constant-time
# References:
# - High-Speed High-Security Signature, Bernstein et al, p15 "Fast decompression", https://ed25519.cr.yp.to/ed25519-20110705.pdf
# - IETF Hash-to-Curve: https://github.com/cfrg/draft-irtf-cfrg-hash-to-curve/blob/9939a07/draft-irtf-cfrg-hash-to-curve.md#optimized-sqrt_ratio-for-q--5-mod-8
# - Pasta curves divsqrt: https://github.com/zcash/pasta/blob/f0f7068/squareroottab.sage#L139-L193
#
# p ≡ 5 (mod 8), hence 𝑖 ∈ Fp with 𝑖² ≡ 1 (mod p)
# if α is a square, with β ≡ α^((p+3)/8) (mod p)
# - either β² ≡ α (mod p), hence √α ≡ ± β (mod p)
# - or β² ≡ -α (mod p), hence √α ≡ ± 𝑖β (mod p)
# (see explanation in invsqrt_p5mod8)
#
# In our fused division and sqrt case we have
# β = (u/v)^((p+3)/8)
# = u^((p+3)/8).v^(p1(p+3)/8) via Fermat's little theorem
# = u^((p+3)/8).v^((7p11)/8)
# = u.u^((p-5)/8).v³.v^((7p35)/8)
# = uv³.u^((p-5)/8).v^(7(p-5)/8)
# = uv³(uv⁷)^((p5)/8)
#
# We can check if β² ≡ -α (mod p)
# by checking vβ² ≡ -u (mod p), and then multiply by 𝑖
# and if it's neither u or -u it wasn't a square.
static: doAssert Fp.C.hasP5mod8_primeModulus()
var t {.noInit.}: Fp
t.square(v)
t *= v
# r = uv³
r.prod(u, t)
# t = (uv⁷)^((p5)/8)
t *= r
t *= v
t.powUnsafeExponent(Fp.getPrimeMinus5div8_BE())
# r = β = uv³(uv⁷)^((p5)/8)
r *= t
# Check candidate square roots
t.square(r)
t *= v
block:
result = t == u
block:
t.neg()
let isSol = t == u
result = result or isSol
t.prod(r, Fp.C.sqrt_minus_one())
r.ccopy(t, isSol)
func sqrt_ratio_if_square*(r: var Fp, u, v: Fp): SecretBool {.inline.} =
## If u/v is a square, compute √(u/v)
## if not, the result is undefined
##
## r must not alias u or v
##
## The square root, if it exist is multivalued,
## i.e. both (u/v)² == (-u/v)²
## This procedure returns a deterministic result
## This procedure is constant-time
when Fp.C.hasP5mod8_primeModulus():
sqrt_ratio_if_square_p5mod8(r, u, v)
else:
# TODO: Fuse inversion and tonelli-shanks and legendre symbol
r.inv(v)
r *= u
result = r.sqrt_if_square()
{.pop.} # raises no exceptions {.pop.} # raises no exceptions

View File

@ -6,5 +6,5 @@
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0). # * 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. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import curves_prop_core, curves_prop_derived import curves_prop_field_core, curves_prop_field_derived, curves_prop_curve
export curves_prop_core, curves_prop_derived export curves_prop_field_core, curves_prop_field_derived, curves_prop_curve

View File

@ -8,9 +8,9 @@
import import
# Internal # Internal
./curves_parser ./curves_parser_field
export CurveFamily export CurveFamily, SexticTwist
# ############################################################ # ############################################################
# #
@ -108,9 +108,66 @@ declareCurves:
embedding_degree: 12 embedding_degree: 12
sexticTwist: D_Twist sexticTwist: D_Twist
curve BabyJubjub: # Curve embedded in BN254_Snarks scalar field
# https://iden3-docs.readthedocs.io/en/latest/_downloads/33717d75ab84e11313cc0d8a090b636f/Baby-Jubjub.pdf
bitwidth: 254
modulus: "0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001"
# Montgomery form: y² = x³ + 168698x² + x
# Edwards form: x² + y² = 1 + dx²y² with d=168696/168700
order: "0x60c89ce5c263405370a08b6d0302b0bab3eedb83920ee0a677297dc392126f1"
orderBitwidth: 251
cofactor: 8
# eq_form: Edwards
coef_d: "0x1575bd81821016c07a5fd2dee78446612498beee8e01a829736c2b06fb281473"
curve Jubjub: # Zcash Sapling curve embedded in BLS12-381 scalar field
# https://z.cash/technology/jubjub/
bitwidth: 255
modulus: "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"
# Montgomery form: y² = x³ + 40962x² + x
# Twisted Edwards: ax² + y² = 1+dx²y² with a = -1 d=-10240/10241
order: "0xe7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7"
orderBitwidth: 252
cofactor: 8
eq_form: TwistedEdwards
coef_a: -1
coef_d: "0x2a9318e74bfa2b48f5fd9207e6bd7fd4292d7f6d37579d2601065fd6d6343eb1"
curve Bandersnatch: # Anoma curve embedded in BLS12-381 scalar field
# https://eprint.iacr.org/2021/1152
bitwidth: 255
modulus: "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"
# Weierstrass form: y² = x³ 3763200000x 7867596800000
# Mongomery form: By² = x³ + Ax² + x
# B=0x300c3385d13bedb7c9e229e185c4ce8b1dd3b71366bb97c30855c0aa41d62727
# A=0x4247698f4e32ad45a293959b4ca17afa4a2d2317e4c6ce5023e1f
# Twisted Edwards form: 5x² + y² = 1 + dx²y²
# d = 138827208126141220649022263972958607803 / 171449701953573178309673572579671231137
order: "0x1cfb69d4ca675f520cce760202687600ff8f87007419047174fd06b52876e7e1"
orderBitwidth: 253
cofactor: 4
eq_form: TwistedEdwards
coef_a: -5
coef_d: "6389c12633c267cbc66e3bf86be3b6d8cb66677177e54f92b369f2f5188d58e7"
curve Curve25519: # Bernstein curve curve Curve25519: # Bernstein curve
bitwidth: 255 bitwidth: 255
modulus: "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed" modulus: "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed"
# Montgomery form: y² = x³ + 486662x² + x
# Edwards form: x² + y² = 1+dx²y² with d = 121665/121666
# Twisted Edwards form: ax² + y² = 1+dx²y² with a = 121666 and d = 121665
# or for use in Hisil, Wong, Carter, and Dawson extended coordinates
# ax² + y² = 1+dx²y² with a = -1 d=-121665/121666
order: "0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed"
orderBItwidth: 253
cofactor: 8
eq_form: TwistedEdwards
coef_a: -1
coef_d: "0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3"
curve P256: # secp256r1 / NIST P-256 curve P256: # secp256r1 / NIST P-256
bitwidth: 256 bitwidth: 256
modulus: "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff" modulus: "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff"

View File

@ -123,6 +123,13 @@ macro genDerivedConstants*(mode: static DerivedConstantMode): untyped =
M M
) )
) )
# const MyCurve_PrimeMinus5div8_BE = primeMinus5div8_BE(MyCurve_Modulus)
result.add newConstStmt(
used(curve & ff & "_PrimeMinus5div8_BE"), newCall(
bindSym"primeMinus5div8_BE",
M
)
)
# const MyCurve_PrimePlus1div4_BE = primePlus1div4_BE(MyCurve_Modulus) # const MyCurve_PrimePlus1div4_BE = primePlus1div4_BE(MyCurve_Modulus)
result.add newConstStmt( result.add newConstStmt(
used(curve & ff & "_PrimePlus1div4_BE"), newCall( used(curve & ff & "_PrimePlus1div4_BE"), newCall(

View File

@ -0,0 +1,126 @@
# 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,
../io/[io_bigints, io_fields],
./curves_declaration, ./curves_parser_field
export CurveFamily, Curve, SexticTwist
# ############################################################
#
# Curve properties generator
#
# ############################################################
template getCoef(c: CurveCoef, curveName: untyped): untyped {.dirty.}=
case c.kind
of NoCoef:
error "Unreachable"
nnkDiscardStmt.newTree(newLit "Dummy")
of Small:
newLit c.coef
of Large:
newCall(
bindSym"fromHex",
nnkBracketExpr.newTree(bindSym"Fp", curveName),
newLit c.coefHex
)
proc genCurveConstants(defs: seq[CurveParams]): NimNode =
## Generate curves main constants
# MapCurveBitWidth & MapCurveOrderBitWidth
# are workaround for https://github.com/nim-lang/Nim/issues/16774
var MapCurveFamily = nnkBracket.newTree()
var curveEllipticStmts = newStmtList()
for curveDef in defs:
curveDef.name.expectKind(nnkIdent)
curveDef.bitWidth.expectKind(nnkIntLit)
curveDef.modulus.expectKind(nnkStrLit)
let curve = curveDef.name
let family = curveDef.family
MapCurveFamily.add nnkExprColonExpr.newTree(
curve, newLit(family)
)
# Curve equation
# -----------------------------------------------
curveEllipticStmts.add newConstStmt(
exported($curve & "_equation_form"),
newLit curveDef.eq_form
)
if curveDef.eq_form == ShortWeierstrass and
curveDef.coef_A.kind != NoCoef and curveDef.coef_B.kind != NoCoef:
curveEllipticStmts.add newConstStmt(
exported($curve & "_coef_A"),
curveDef.coef_A.getCoef(curve)
)
curveEllipticStmts.add newConstStmt(
exported($curve & "_coef_B"),
curveDef.coef_B.getCoef(curve)
)
# Towering
# -----------------------------------------------
curveEllipticStmts.add newConstStmt(
exported($curve & "_nonresidue_fp"),
curveDef.nonresidue_fp
)
curveEllipticStmts.add newConstStmt(
exported($curve & "_nonresidue_fp2"),
curveDef.nonresidue_fp2
)
# Pairing
# -----------------------------------------------
curveEllipticStmts.add newConstStmt(
exported($curve & "_embedding_degree"),
newLit curveDef.embedding_degree
)
curveEllipticStmts.add newConstStmt(
exported($curve & "_sexticTwist"),
newLit curveDef.sexticTwist
)
if curveDef.eq_form == TwistedEdwards and
curveDef.coef_A.kind != NoCoef and curveDef.coef_D.kind != NoCoef:
curveEllipticStmts.add newConstStmt(
exported($curve & "_coef_A"),
curveDef.coef_A.getCoef(curve)
)
curveEllipticStmts.add newConstStmt(
exported($curve & "_coef_D"),
curveDef.coef_D.getCoef(curve)
)
# end for ---------------------------------------------------
result = newStmtList()
# const CurveFamily: array[Curve, CurveFamily] = ...
result.add newConstStmt(
exported("CurveFamilies"), MapCurveFamily
)
result.add curveEllipticStmts
# echo result.toStrLit()
macro setupCurves(): untyped =
result = genCurveConstants(curvesDefinitions)
setupCurves()

View File

@ -43,13 +43,14 @@ type
Large Large
CurveCoef* = object CurveCoef* = object
case kind: CurveCoefKind case kind*: CurveCoefKind
of NoCoef: discard of NoCoef: discard
of Small: coef: int of Small: coef*: int
of Large: coefHex: string of Large: coefHex*: string
CurveEquationForm* = enum CurveEquationForm* = enum
ShortWeierstrass ShortWeierstrass
TwistedEdwards
SexticTwist* = enum SexticTwist* = enum
## The sextic twist type of the current elliptic curve ## The sextic twist type of the current elliptic curve
@ -84,36 +85,37 @@ type
D_Twist D_Twist
M_Twist M_Twist
CurveParams = object CurveParams* = object
## All the curve parameters that may be defined ## All the curve parameters that may be defined
# Note: we don't use case object here, the transition is annoying # Note: we don't use case object here, the transition is annoying
# and would force use to scan all "kind" field (eq_form, family, ...) # and would force use to scan all "kind" field (eq_form, family, ...)
# before instantiating the object. # before instantiating the object.
name: NimNode name*: NimNode
# Field parameters # Field parameters
bitWidth: NimNode # nnkIntLit bitWidth*: NimNode # nnkIntLit
modulus: NimNode # nnkStrLit (hex) modulus*: NimNode # nnkStrLit (hex)
# Towering # Towering
nonresidue_fp: NimNode # nnkIntLit nonresidue_fp*: NimNode # nnkIntLit
nonresidue_fp2: NimNode # nnkPar(nnkIntLit, nnkIntLit) nonresidue_fp2*: NimNode # nnkPar(nnkIntLit, nnkIntLit)
# Curve parameters # Curve parameters
eq_form: CurveEquationForm eq_form*: CurveEquationForm
coef_A: CurveCoef coef_A*: CurveCoef
coef_B: CurveCoef coef_B*: CurveCoef
order: NimNode # nnkStrLit (hex) coef_D*: CurveCoef
orderBitwidth: NimNode # nnkIntLit order*: NimNode # nnkStrLit (hex)
orderBitwidth*: NimNode # nnkIntLit
embedding_degree: int embedding_degree*: int
sexticTwist: SexticTwist sexticTwist*: SexticTwist
family: CurveFamily family*: CurveFamily
var curvesDefinitions {.compileTime.}: seq[CurveParams] var curvesDefinitions* {.compileTime.}: seq[CurveParams]
proc parseCurveDecls(defs: var seq[CurveParams], curves: NimNode) = proc parseCurveDecls*(defs: var seq[CurveParams], curves: NimNode) =
## Parse the curve declarations and store them in the curve definitions ## Parse the curve declarations and store them in the curve definitions
curves.expectKind(nnkStmtList) curves.expectKind(nnkStmtList)
@ -180,6 +182,10 @@ proc parseCurveDecls(defs: var seq[CurveParams], curves: NimNode) =
elif sectionId.eqIdent"coef_a": elif sectionId.eqIdent"coef_a":
if sectionVal.kind == nnkIntLit: if sectionVal.kind == nnkIntLit:
params.coef_A = CurveCoef(kind: Small, coef: sectionVal.intVal.int) params.coef_A = CurveCoef(kind: Small, coef: sectionVal.intVal.int)
elif sectionVal.kind == nnkPrefix: # Got -1
sectionVal[0].expectIdent"-"
sectionVal[1].expectKind(nnkIntLit)
params.coef_A = CurveCoef(kind: Small, coef: -sectionVal[1].intVal.int)
else: else:
params.coef_A = CurveCoef(kind: Large, coefHex: sectionVal.strVal) params.coef_A = CurveCoef(kind: Large, coefHex: sectionVal.strVal)
elif sectionId.eqIdent"coef_b": elif sectionId.eqIdent"coef_b":
@ -191,6 +197,15 @@ proc parseCurveDecls(defs: var seq[CurveParams], curves: NimNode) =
params.coef_B = CurveCoef(kind: Small, coef: -sectionVal[1].intVal.int) params.coef_B = CurveCoef(kind: Small, coef: -sectionVal[1].intVal.int)
else: else:
params.coef_B = CurveCoef(kind: Large, coefHex: sectionVal.strVal) params.coef_B = CurveCoef(kind: Large, coefHex: sectionVal.strVal)
elif sectionId.eqIdent"coef_d":
if sectionVal.kind == nnkIntLit:
params.coef_D = CurveCoef(kind: Small, coef: sectionVal.intVal.int)
elif sectionVal.kind == nnkPrefix: # Got -1
sectionVal[0].expectIdent"-"
sectionVal[1].expectKind(nnkIntLit)
params.coef_D = CurveCoef(kind: Small, coef: -sectionVal[1].intVal.int)
else:
params.coef_D = CurveCoef(kind: Large, coefHex: sectionVal.strVal)
elif sectionId.eqIdent"order": elif sectionId.eqIdent"order":
params.order = sectionVal params.order = sectionVal
elif sectionId.eqIdent"orderBitwidth": elif sectionId.eqIdent"orderBitwidth":
@ -211,28 +226,14 @@ proc parseCurveDecls(defs: var seq[CurveParams], curves: NimNode) =
defs.add params defs.add params
proc exported(id: string): NimNode = proc exported*(id: string): NimNode =
nnkPostfix.newTree( nnkPostfix.newTree(
ident"*", ident"*",
ident(id) ident(id)
) )
template getCoef(c: CurveCoef, width: NimNode): untyped {.dirty.}= proc genFieldsConstants(defs: seq[CurveParams]): NimNode =
case c.kind ## Generate fields main constants
of NoCoef:
error "Unreachable"
nnkDiscardStmt.newTree(newLit "Dummy")
of Small:
newLit c.coef
of Large:
newCall(
bindSym"fromHex",
nnkBracketExpr.newTree(bindSym"BigInt", width),
newLit c.coefHex
)
proc genMainConstants(defs: var seq[CurveParams]): NimNode =
## Generate curves and fields main constants
# MapCurveBitWidth & MapCurveOrderBitWidth # MapCurveBitWidth & MapCurveOrderBitWidth
# are workaround for https://github.com/nim-lang/Nim/issues/16774 # are workaround for https://github.com/nim-lang/Nim/issues/16774
@ -240,12 +241,10 @@ proc genMainConstants(defs: var seq[CurveParams]): NimNode =
var Curves: seq[NimNode] var Curves: seq[NimNode]
var MapCurveBitWidth = nnkBracket.newTree() var MapCurveBitWidth = nnkBracket.newTree()
var MapCurveOrderBitWidth = nnkBracket.newTree() var MapCurveOrderBitWidth = nnkBracket.newTree()
var MapCurveFamily = nnkBracket.newTree()
var curveModStmts = newStmtList() var curveModStmts = newStmtList()
var curveEllipticStmts = newStmtList()
var curveExtraStmts = newStmtList()
for curveDef in defs: for curveDef in defs:
curveDef.name.expectKind(nnkIdent) curveDef.name.expectKind(nnkIdent)
curveDef.bitWidth.expectKind(nnkIntLit) curveDef.bitWidth.expectKind(nnkIntLit)
curveDef.modulus.expectKind(nnkStrLit) curveDef.modulus.expectKind(nnkStrLit)
@ -253,9 +252,10 @@ proc genMainConstants(defs: var seq[CurveParams]): NimNode =
let curve = curveDef.name let curve = curveDef.name
let bitWidth = curveDef.bitWidth let bitWidth = curveDef.bitWidth
let modulus = curveDef.modulus let modulus = curveDef.modulus
let family = curveDef.family
Curves.add curve Curves.add curve
# Field Fp
# "BN254_Snarks: 254" array construction expression # "BN254_Snarks: 254" array construction expression
MapCurveBitWidth.add nnkExprColonExpr.newTree( MapCurveBitWidth.add nnkExprColonExpr.newTree(
curve, bitWidth curve, bitWidth
@ -271,19 +271,10 @@ proc genMainConstants(defs: var seq[CurveParams]): NimNode =
) )
) )
MapCurveFamily.add nnkExprColonExpr.newTree( # Field Fr
curve, newLit(family)
)
# Curve equation
# -----------------------------------------------
curveEllipticStmts.add newConstStmt(
exported($curve & "_equation_form"),
newLit curveDef.eq_form
)
if not curveDef.order.isNil: if not curveDef.order.isNil:
curveDef.orderBitwidth.expectKind(nnkIntLit) curveDef.orderBitwidth.expectKind(nnkIntLit)
curveEllipticStmts.add newConstStmt( curveModStmts.add newConstStmt(
exported($curve & "_Order"), exported($curve & "_Order"),
newCall( newCall(
bindSym"fromHex", bindSym"fromHex",
@ -295,7 +286,7 @@ proc genMainConstants(defs: var seq[CurveParams]): NimNode =
curve, curveDef.orderBitwidth curve, curveDef.orderBitwidth
) )
else: # Dummy else: # Dummy
curveEllipticStmts.add newConstStmt( curveModStmts.add newConstStmt(
exported($curve & "_Order"), exported($curve & "_Order"),
newCall( newCall(
bindSym"fromHex", bindSym"fromHex",
@ -307,38 +298,6 @@ proc genMainConstants(defs: var seq[CurveParams]): NimNode =
curve, newLit 1 curve, newLit 1
) )
if curveDef.coef_A.kind != NoCoef and curveDef.coef_B.kind != NoCoef:
curveEllipticStmts.add newConstStmt(
exported($curve & "_coef_A"),
curveDef.coef_A.getCoef(bitWidth)
)
curveEllipticStmts.add newConstStmt(
exported($curve & "_coef_B"),
curveDef.coef_B.getCoef(bitWidth)
)
# Towering
# -----------------------------------------------
curveEllipticStmts.add newConstStmt(
exported($curve & "_nonresidue_fp"),
curveDef.nonresidue_fp
)
curveEllipticStmts.add newConstStmt(
exported($curve & "_nonresidue_fp2"),
curveDef.nonresidue_fp2
)
# Pairing
# -----------------------------------------------
curveEllipticStmts.add newConstStmt(
exported($curve & "_embedding_degree"),
newLit curveDef.embedding_degree
)
curveEllipticStmts.add newConstStmt(
exported($curve & "_sexticTwist"),
newLit curveDef.sexticTwist
)
# end for --------------------------------------------------- # end for ---------------------------------------------------
result = newStmtList() result = newStmtList()
@ -356,19 +315,12 @@ proc genMainConstants(defs: var seq[CurveParams]): NimNode =
result.add newConstStmt( result.add newConstStmt(
exported("CurveBitWidth"), MapCurveBitWidth exported("CurveBitWidth"), MapCurveBitWidth
) )
# const CurveFamily: array[Curve, CurveFamily] = ... result.add curveModStmts
result.add newConstStmt(
exported("CurveFamilies"), MapCurveFamily
)
# const CurveOrderBitSize: array[Curve, int] = ... # const CurveOrderBitSize: array[Curve, int] = ...
result.add newConstStmt( result.add newConstStmt(
exported("CurveOrderBitWidth"), MapCurveOrderBitWidth exported("CurveOrderBitWidth"), MapCurveOrderBitWidth
) )
result.add curveModStmts
result.add curveEllipticStmts
result.add curveExtraStmts
# echo result.toStrLit() # echo result.toStrLit()
macro declareCurves*(curves: untyped): untyped = macro declareCurves*(curves: untyped): untyped =
@ -389,4 +341,4 @@ macro declareCurves*(curves: untyped): untyped =
curves.expectKind(nnkStmtList) curves.expectKind(nnkStmtList)
curvesDefinitions.parseCurveDecls(curves) curvesDefinitions.parseCurveDecls(curves)
result = curvesDefinitions.genMainConstants() result = curvesDefinitions.genFieldsConstants()

View File

@ -1,4 +1,3 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH # Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy # Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of # Licensed and distributed under either of
@ -10,44 +9,19 @@ import
# Standard library # Standard library
std/macros, std/macros,
# Internal # Internal
./type_bigint, ./common, ./type_bigint,
./curves_declaration, ./curves_parser ./curves_declaration, ./curves_parser_curve
export CurveFamily, Curve, SexticTwist 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 # Curve properties
# #
# ############################################################ # ############################################################
{.experimental: "dynamicBindSym".}
macro getCurveOrder*(C: static Curve): untyped = macro getCurveOrder*(C: static Curve): untyped =
## Get the curve order `r` ## Get the curve order `r`
## i.e. the number of points on the elliptic curve ## i.e. the number of points on the elliptic curve
@ -65,6 +39,9 @@ template matchingOrderBigInt*(C: static Curve): untyped =
# Workaround: https://github.com/nim-lang/Nim/issues/16774 # Workaround: https://github.com/nim-lang/Nim/issues/16774
BigInt[CurveOrderBitWidth[C]] BigInt[CurveOrderBitWidth[C]]
template family*(C: Curve): CurveFamily =
CurveFamilies[C]
macro getEquationForm*(C: static Curve): untyped = macro getEquationForm*(C: static Curve): untyped =
## Returns the equation form ## Returns the equation form
## (ShortWeierstrass, Montgomery, Twisted Edwards, Weierstrass, ...) ## (ShortWeierstrass, Montgomery, Twisted Edwards, Weierstrass, ...)
@ -82,6 +59,12 @@ macro getCoefB*(C: static Curve): untyped =
## or a bigInt depending on the curve ## or a bigInt depending on the curve
result = bindSym($C & "_coef_B") result = bindSym($C & "_coef_B")
macro getCoefD*(C: static Curve): untyped =
## Returns the D 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_D")
macro getNonResidueFp*(C: static Curve): untyped = macro getNonResidueFp*(C: static Curve): untyped =
## Returns the tower extension (and twist) non-residue for 𝔽p ## Returns the tower extension (and twist) non-residue for 𝔽p
## Depending on the curve it might be: ## Depending on the curve it might be:

View File

@ -0,0 +1,39 @@
# 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
export Curve
# ############################################################
#
# 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 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

View File

@ -11,7 +11,7 @@ import
std/macros, std/macros,
# Internal # Internal
./type_bigint, ./type_ff, ./common, ./type_bigint, ./type_ff, ./common,
./curves_declaration, ./curves_prop_core, ./curves_derived ./curves_declaration, ./curves_prop_field_core, ./curves_derived
# ############################################################ # ############################################################
# #
@ -108,9 +108,13 @@ macro getPrimeMinus1div2_BE*(ff: type FF): untyped =
result = bindConstant(ff, "PrimeMinus1div2_BE") result = bindConstant(ff, "PrimeMinus1div2_BE")
macro getPrimeMinus3div4_BE*(ff: type FF): untyped = macro getPrimeMinus3div4_BE*(ff: type FF): untyped =
## Get (P-3) / 2 in big-endian serialized format ## Get (P-3) / 4 in big-endian serialized format
result = bindConstant(ff, "PrimeMinus3div4_BE") result = bindConstant(ff, "PrimeMinus3div4_BE")
macro getPrimeMinus5div8_BE*(ff: type FF): untyped =
## Get (P-5) / 8 in big-endian serialized format
result = bindConstant(ff, "PrimeMinus5div8_BE")
macro getPrimePlus1div4_BE*(ff: type FF): untyped = macro getPrimePlus1div4_BE*(ff: type FF): untyped =
## Get (P+1) / 4 for an odd prime in big-endian serialized format ## Get (P+1) / 4 for an odd prime in big-endian serialized format
result = bindConstant(ff, "PrimePlus1div4_BE") result = bindConstant(ff, "PrimePlus1div4_BE")

View File

@ -236,7 +236,9 @@ func checkValidModulus(M: BigInt) =
const expectedMsb = M.bits-1 - WordBitWidth * (M.limbs.len - 1) const expectedMsb = M.bits-1 - WordBitWidth * (M.limbs.len - 1)
let msb = log2(BaseType(M.limbs[^1])) let msb = log2(BaseType(M.limbs[^1]))
doAssert msb == expectedMsb, "Internal Error: the modulus must use all declared bits and only those" doAssert msb == expectedMsb, "Internal Error: the modulus must use all declared bits and only those:\n" &
" Modulus '" & M.toHex() & "' is declared with " & $M.bits &
" bits but uses " & $(msb + WordBitWidth * (M.limbs.len - 1)) & " bits."
func countSpareBits*(M: BigInt): int = func countSpareBits*(M: BigInt): int =
## Count the number of extra bits ## Count the number of extra bits
@ -434,6 +436,25 @@ func primeMinus3div4_BE*[bits: static int](
result.exportRawUint(tmp, bigEndian) result.exportRawUint(tmp, bigEndian)
func primeMinus5div8_BE*[bits: static int](
P: BigInt[bits]
): array[(bits+7) div 8, byte] {.noInit.} =
## For an input prime `p`, compute (p-5)/8
## and return the result as a canonical byte array / octet string
## For use to check if a number is a square (quadratic residue)
## and if so compute the square root in a fused manner
##
# Output size:
# - (bits + 7) div 8: bits => byte conversion rounded up
# - (bits + 7 - 3): dividing by 8 means 3 bits is unused
# => TODO: reduce the output size (to potentially save a byte and corresponding multiplication/squarings)
var tmp = P
discard tmp.sub(5)
tmp.shiftRight(3)
result.exportRawUint(tmp, bigEndian)
func primePlus1Div4_BE*[bits: static int]( func primePlus1Div4_BE*[bits: static int](
P: BigInt[bits] P: BigInt[bits]
): array[(bits+7) div 8, byte] {.noInit.} = ): array[(bits+7) div 8, byte] {.noInit.} =

View File

@ -9,7 +9,7 @@
import import
./common, ./common,
./curves_declaration, ./curves_declaration,
./curves_prop_core ./curves_prop_field_core
export matchingBigInt export matchingBigInt

View File

@ -0,0 +1,19 @@
# 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
../config/[curves, type_bigint, type_ff],
../io/[io_bigints, io_fields],
../arithmetic/finite_fields
const
# with e = 2adicity
# p == s * 2^e + 1
# root_of_unity = smallest_quadratic_nonresidue^s
# exponent = (p-1-2^e)/2^e / 2
Bandersnatch_TonelliShanks_exponent* = BigInt[222].fromHex"0x39f6d3a994cebea4199cec0404d0ec02a9ded2017fff2dff7fffffff"
Bandersnatch_TonelliShanks_twoAdicity* = 32
Bandersnatch_TonelliShanks_root_of_unity* = Fp[Bandersnatch].fromHex"0x212d79e5b416b6f0fd56dc8d168d6c0c4024ff270b3e0941b788f500b912f1f"

View File

@ -0,0 +1,27 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../config/[curves, type_bigint, type_ff],
../io/[io_bigints, io_fields],
../arithmetic/finite_fields
# p ≡ 5 (mod 8), hence 𝑖 ∈ Fp with 𝑖² ≡ 1 (mod p)
# Hence if α is a square
# with β ≡ α^((p+3)/8) (mod p)
# - either β² ≡ α (mod p), hence √α ≡ ±β (mod p)
# - or β² ≡ -α (mod p), hence √α ≡ ±𝑖β (mod p)
# Sage:
# p = Integer('0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed')
# Fp = GF(p)
# sqrt_minus1 = Fp(-1).sqrt()
# print(Integer(sqrt_minus1).hex())
const Curve25519_sqrt_minus_one* = Fp[Curve25519].fromHex(
"0x2b8324804fc1df0b2b4d00993dfbd7a72f431806ad2fe478c4ee1b274a0ea0b0"
)

View File

@ -0,0 +1,19 @@
# 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
../config/[curves, type_bigint, type_ff],
../io/[io_bigints, io_fields],
../arithmetic/finite_fields
const
# with e = 2adicity
# p == s * 2^e + 1
# root_of_unity = smallest_quadratic_nonresidue^s
# exponent = (p-1-2^e)/2^e / 2
Jubjub_TonelliShanks_exponent* = BigInt[222].fromHex"0x39f6d3a994cebea4199cec0404d0ec02a9ded2017fff2dff7fffffff"
Jubjub_TonelliShanks_twoAdicity* = 32
Jubjub_TonelliShanks_root_of_unity* = Fp[Jubjub].fromHex"0x212d79e5b416b6f0fd56dc8d168d6c0c4024ff270b3e0941b788f500b912f1f"

View File

@ -13,14 +13,18 @@ import
./bls12_381_sqrt, ./bls12_381_sqrt,
./bn254_nogami_sqrt, ./bn254_nogami_sqrt,
./bn254_snarks_sqrt, ./bn254_snarks_sqrt,
./bw6_761_sqrt ./bw6_761_sqrt,
./curve25519_sqrt,
./jubjub_sqrt,
./bandersnatch_sqrt
export export
bls12_377_sqrt, bls12_377_sqrt,
bls12_381_sqrt, bls12_381_sqrt,
bn254_nogami_sqrt, bn254_nogami_sqrt,
bn254_snarks_sqrt, bn254_snarks_sqrt,
bw6_761_sqrt bw6_761_sqrt,
curve25519_sqrt
func hasSqrtAddchain*(C: static Curve): static bool = func hasSqrtAddchain*(C: static Curve): static bool =
when C in {BLS12_381, BN254_Nogami, BN254_Snarks, BW6_761}: when C in {BLS12_381, BN254_Nogami, BN254_Snarks, BW6_761}:
@ -39,3 +43,7 @@ func hasTonelliShanksAddchain*(C: static Curve): static bool =
true true
else: else:
false false
macro sqrt_minus_one*(C: static Curve): untyped =
## Return 𝑖 ∈ Fp with 𝑖² ≡ 1 (mod p)
return bindSym($C & "_sqrt_minus_one")

View File

@ -17,7 +17,7 @@ import
# ############################################################ # ############################################################
# #
# Elliptic Curve in Short Weierstrass form # Elliptic Curve in Short Weierstrass form
# with Projective Coordinates # with Affine Coordinates
# #
# ############################################################ # ############################################################
@ -57,10 +57,10 @@ func curve_eq_rhs*[F](y2: var F, x: F, Tw: static Twisted) =
when Tw == NotOnTwist: when Tw == NotOnTwist:
when F.C.getCoefB() >= 0: when F.C.getCoefB() >= 0:
y2.fromInt F.C.getCoefB() y2.fromUint uint F.C.getCoefB()
y2 += t y2 += t
else: else:
y2.fromInt -F.C.getCoefB() y2.fromUint uint -F.C.getCoefB()
y2.diff(t, y2) y2.diff(t, y2)
else: else:
y2.sum(F.C.getCoefB_G2, t) y2.sum(F.C.getCoefB_G2, t)
@ -86,7 +86,6 @@ func trySetFromCoordX*[F, Tw](
## Try to create a point the elliptic curve ## Try to create a point the elliptic curve
## y² = x³ + a x + b (affine coordinate) ## y² = x³ + a x + b (affine coordinate)
## ##
## The `Z` coordinates is set to 1
## ##
## return true and update `P` if `x` leads to a valid point ## return true and update `P` if `x` leads to a valid point
## return false otherwise, in that case `P` is undefined. ## return false otherwise, in that case `P` is undefined.

View File

@ -157,6 +157,7 @@ func sum*[F; Tw: static Twisted](
## y² = x³ + a x + b ## y² = x³ + a x + b
## ##
## ``r`` is initialized/overwritten with the sum ## ``r`` is initialized/overwritten with the sum
## ``r`` may alias P
## ##
## Implementation is constant-time, in particular it will not expose ## Implementation is constant-time, in particular it will not expose
## that P == Q or P == -Q or P or Q are the infinity points ## that P == Q or P == -Q or P or Q are the infinity points
@ -252,6 +253,8 @@ func madd*[F; Tw: static Twisted](
## with p in Projective coordinates and Q in affine coordinates ## with p in Projective coordinates and Q in affine coordinates
## ##
## R = P + Q ## R = P + Q
##
## ``r`` may alias P
# TODO: static doAssert odd order # TODO: static doAssert odd order
@ -321,6 +324,7 @@ func double*[F; Tw: static Twisted](
## y² = x³ + a x + b ## y² = x³ + a x + b
## ##
## ``r`` is initialized/overwritten with the sum ## ``r`` is initialized/overwritten with the sum
## ``r`` may alias P
## ##
## Implementation is constant-time, in particular it will not expose ## Implementation is constant-time, in particular it will not expose
## that `P` is an infinity point. ## that `P` is an infinity point.

View File

@ -0,0 +1,149 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../primitives,
../config/[common, curves],
../arithmetic,
../towers,
../io/[io_fields, io_towers]
# ############################################################
#
# Elliptic Curve in Twisted Edwards form
# with Affine Coordinates
#
# ############################################################
type ECP_TwEdwards_Aff*[F] = object
## Elliptic curve point for a curve in Twisted Edwards form
## ax²+y²=1+dx²y²
## with a, d ≠ 0 and a ≠ d
##
## over a field F
x*, y*: F
func `==`*(P, Q: ECP_TwEdwards_Aff): SecretBool =
## Constant-time equality check
result = P.x == Q.x
result = result and (P.y == Q.y)
func isInf*(P: ECP_TwEdwards_Aff): SecretBool =
## Returns true if P is an infinity point
## and false otherwise
result = P.x.isZero() and P.y.isOne()
func isOnCurve*[F](x, y: F): SecretBool =
## Returns true if the (x, y) coordinates
## represents a point of the Twisted Edwards elliptic curve
## with equation ax²+y²=1+dx²y²
var t0{.noInit.}, t1{.noInit.}, t2{.noInit.}: F
t0.square(x)
t1.square(y)
# ax²+y²
when F.C.getCoefA() is int:
when F.C.getCoefA() == -1:
t2.diff(t1, t0)
else:
t2.prod(t0, F.C.getCoefA())
t2 += t1
else:
t2.prod(F.C.getCoefA(), t0)
t2 += t1
# dx²y²
t0 *= t1
when F.C.getCoefD() is int:
when F.C.getCoefD >= 0:
t1.fromUint uint F.C.getCoefD()
t0 *= t1
else:
t1.fromUint uint F.C.getCoefD()
t0 *= t1
t0.neg()
else:
t0 *= F.C.getCoefD()
# ax²+y² - dx²y² =? 1
t2 -= t0
return t2.isOne()
func trySetFromCoordY*[F](P: var ECP_TwEdwards_Aff[F], y: F): SecretBool =
## Try to create a point the elliptic curve
## ax²+y²=1+dx²y² (affine coordinate)
##
##
## return true and update `P` if `y` leads to a valid point
## return false otherwise, in that case `P` is undefined.
##
## Note: Dedicated robust procedures for hashing-to-curve
## will be provided, this is intended for testing purposes.
##
## For **test case generation only**,
## 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
# https://eprint.iacr.org/2015/677.pdf
# p2: Encoding and parsing curve points.
# x² = (y² 1)/(dy² a)
var t {.noInit.}: F
t.square(y)
# (dy² a)
when F.C.getCoefD() is int:
when F.C.getCoefD() >= 0:
P.y.fromUint uint F.C.getCoefD()
else:
P.y.fromUint uint -F.C.getCoefD()
P.y.neg()
else:
P.y = F.C.getCoefD()
P.y *= t
when F.C.getCoefA() is int:
when F.C.getCoefA == -1:
P.x.setOne()
P.y += P.x
elif F.C.getCoefA >= 0:
P.x.fromUint uint F.C.getCoefA()
P.y -= P.x
else:
P.x.fromUint uint -F.C.getCoefA()
P.y += P.x
else:
P.y -= F.C.getCoefA()
# y² 1
P.x.setMinusOne()
P.x += t
# √((y² 1)/(dy² a))
result = sqrt_ratio_if_square(t, P.x, P.y)
P.x = t
P.y = y
func neg*(P: var ECP_TwEdwards_Aff, Q: ECP_TwEdwards_Aff) =
## Negate ``P``
P.x.neg(Q.x)
P.y = Q.y
func neg*(P: var ECP_TwEdwards_Aff) =
## Negate ``P``
P.x.neg()
func cneg*(P: var ECP_TwEdwards_Aff, ctl: CTBool) =
## Conditional negation.
## Negate if ``ctl`` is true
P.x.cneg(ctl)

View File

@ -0,0 +1,306 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
../primitives,
../config/[common, curves],
../arithmetic,
../towers,
./ec_twistededwards_affine
# ############################################################
#
# Elliptic Curve in Twisted Edwards form
# with Projective Coordinates
#
# ############################################################
type ECP_TwEdwards_Prj*[F] = object
## Elliptic curve point for a curve in Twisted Edwards form
## ax²+y²=1+dx²y²
## with a, d ≠ 0 and a ≠ d
##
## over a field F
##
## in projective coordinate (X, Y, Z)
## with x = X/Z and y = Y/Z
## hence (aX² + Y²)Z² = Z⁴ + dX²Y²
x*, y*, z*: F
func `==`*(P, Q: ECP_TwEdwards_Prj): SecretBool =
## Constant-time equality check
## This is a costly operation
# Reminder: the representation is not unique
var a{.noInit.}, b{.noInit.}: ECP_TwEdwards_Prj.F
a.prod(P.x, Q.z)
b.prod(Q.x, P.z)
result = a == b
a.prod(P.y, Q.z)
b.prod(Q.y, P.z)
result = result and a == b
func isInf*(P: ECP_TwEdwards_Prj): SecretBool {.inline.} =
## Returns true if P is an infinity point
## and false otherwise
result = P.x.isZero() and (P.y == P.z)
func setInf*(P: var ECP_TwEdwards_Prj) {.inline.} =
## Set ``P`` to infinity
P.x.setZero()
P.y.setOne()
P.z.setOne()
func ccopy*(P: var ECP_TwEdwards_Prj, Q: ECP_TwEdwards_Prj, ctl: SecretBool) {.inline.} =
## Constant-time conditional copy
## If ctl is true: Q is copied into P
## if ctl is false: Q is not copied and P is unmodified
## Time and memory accesses are the same whether a copy occurs or not
for fP, fQ in fields(P, Q):
ccopy(fP, fQ, ctl)
func trySetFromCoordY*[F](
P: var ECP_TwEdwards_Prj[F],
y: F): SecretBool =
## Try to create a point the elliptic curve
## ax²+y²=1+dx²y² (affine coordinate)
##
## The `Z` coordinates is set to 1
##
## return true and update `P` if `y` leads to a valid point
## return false otherwise, in that case `P` is undefined.
##
## Note: Dedicated robust procedures for hashing-to-curve
## will be provided, this is intended for testing purposes.
##
## For **test case generation only**,
## 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
var Q{.noInit.}: ECP_TwEdwards_Aff[F]
result = Q.trySetFromCoordY(y)
P.x = Q.x
P.y = Q.y
P.z.setOne()
func trySetFromCoordsYandZ*[F](
P: var ECP_TwEdwards_Prj[F],
y, z: F): SecretBool =
## Try to create a point the elliptic curve
## ax²+y²=1+dx²y² (affine coordinate)
##
## return true and update `P` if `y` leads to a valid point
## return false otherwise, in that case `P` is undefined.
##
## Note: Dedicated robust procedures for hashing-to-curve
## will be provided, this is intended for testing purposes.
##
## For **test case generation only**,
## 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
var Q{.noInit.}: ECP_TwEdwards_Aff[F]
result = Q.trySetFromCoordY(y)
P.x.prod(Q.x, z)
P.y.prod(Q.y, z)
P.z = z
func neg*(P: var ECP_TwEdwards_Prj, Q: ECP_TwEdwards_Prj) {.inline.} =
## Negate ``P``
P.x.neg(Q.x)
P.y = Q.y
P.z = Q.z
func neg*(P: var ECP_TwEdwards_Prj) {.inline.} =
## Negate ``P``
P.x.neg()
func cneg*(P: var ECP_TwEdwards_Prj, ctl: CTBool) {.inline.} =
## Conditional negation.
## Negate if ``ctl`` is true
P.x.cneg(ctl)
func sum*[Field](
r: var ECP_TwEdwards_Prj[Field],
P, Q: ECP_TwEdwards_Prj[Field]
) =
## Elliptic curve point addition for Twisted Edwards curves in projective coordinates
##
## R = P + Q
##
## Twisted Edwards curves have the following equation in projective coordinates
## (aX² + Y²)Z² = Z⁴ + dX²Y²
## from the affine equation
## ax²+y²=1+dx²y²
##
## ``r`` is initialized/overwritten with the sum
## ``r`` may alias P
##
## Implementation is constant-time, in particular it will not expose
## that P == Q or P == -Q or P or Q are the infinity points
## to simple side-channel attacks (SCA)
## This is done by using a "complete" or "exception-free" addition law.
#
# https://www.hyperelliptic.org/EFD/g1p/auto-twisted-projective.html#addition-add-2008-bbjlp
# Cost: 10M + 1S + 1*a + 1*d + 7add.
# A = Z1*Z2
# B = A²
# C = X1*X2
# D = Y1*Y2
# E = d*C*D
# F = B-E
# G = B+E
# X3 = A*F*((X1+Y1)*(X2+Y2)-C-D)
# Y3 = A*G*(D-a*C)
# Z3 = F*G
var
A{.noInit.}, B{.noInit.}, C{.noInit.}: Field
D{.noInit.}, E{.noInit.}, F{.noInit.}: Field
G{.noInit.}: Field
A.prod(P.z, Q.z)
B.square(A)
C.prod(P.x, Q.x)
D.prod(P.y, Q.y)
E.prod(C, D)
when Field.C.getCoefD() is int:
# conversion at compile-time
const coefD = block:
var d: Field
d.fromInt F.C.getCoefD()
d
E *= coefD
else:
E *= Field.C.getCoefD()
F.diff(B, E)
G.sum(B, E)
# Aliasing: B and E are unused
# We store (P.x+P.y)*(Q.x+Q.y)
# so that using r.x or r.y is safe even in case of aliasing
B.sum(P.x, P.y)
E.sum(Q.x, Q.y)
B *= E # B = (X1+Y1)*(X2+Y2)
E.sum(C, D) # E = C+D
# Y3 = A*G*(D-a*C)
when Field.C.getCoefA() == -1:
r.y = E # (D-a*C) = D+C
else:
r.y.prod(C, Field.C.getCoefA())
r.y.diff(D, r.y)
r.y *= A
r.y *= G
# X3 = A*F*((X1+Y1)*(X2+Y2)-C-D)
B -= E
r.x.prod(A, F)
r.x *= B
# Z3 = F*G
r.z.prod(F, G)
func double*[Field](
r: var ECP_TwEdwards_Prj[Field],
P: ECP_TwEdwards_Prj[Field]
) =
## Elliptic curve point doubling for Twisted Edwards curves in projective coordinates
##
## R = [2] P
##
## Twisted Edwards curves have the following equation in projective coordinates
## (aX² + Y²)Z² = Z⁴ + dX²Y²
## from the affine equation
## ax²+y²=1+dx²y²
##
## ``r`` is initialized/overwritten with the sum
## ``r`` may alias P
##
## Implementation is constant-time, in particular it will not expose
## that P == Q or P == -Q or P or Q are the infinity points
## to simple side-channel attacks (SCA)
## This is done by using a "complete" or "exception-free" addition law.
#
# https://www.hyperelliptic.org/EFD/g1p/auto-twisted-projective.html#addition-add-2008-bbjlp
# Cost: 3M + 4S + 1*a + 6add + 1*2.
# B = (X1+Y1)²
# C = X1²
# D = Y1²
# E = a*C
# F = E+D
# H = Z1²
# J = F-2*H
# X3 = (B-C-D)*J
# Y3 = F*(E-D)
# Z3 = F*J
var
D{.noInit.}, E{.noInit.}: Field
H{.noInit.}, J{.noInit.}: FIeld
# (B-C-D) => 2X1Y1, but With squaring and 2 substractions instead of mul + addition
# In practice, squaring is not cheap enough to compasate the extra substraction cost.
r.x.prod(P.x, P.y)
r.x.double()
D.square(P.y)
E.square(P.x)
E *= Field.C.getCoefA()
r.y.sum(E, D) # Ry stores F = E+D
H.square(P.z)
H.double()
J.diff(r.y, H) # J = F-2H
r.x *= J # X3 = (B-C-D)*J
r.z.prod(r.y, J) # Z3 = F*J
E -= D # C stores E-D
r.y *= E
func double*(P: var ECP_TwEdwards_Prj) {.inline.} =
## In-place EC doubling
P.double(P)
func diff*(r: var ECP_TwEdwards_Prj,
P, Q: ECP_TwEdwards_Prj
) {.inline.} =
## r = P - Q
## Can handle r and Q aliasing
var nQ {.noInit.}: typeof(Q)
nQ.neg(Q)
r.sum(P, nQ)
func affineFromProjective*[F](
aff: var ECP_TwEdwards_Prj[F],
proj: ECP_TwEdwards_Prj[F]) =
var invZ {.noInit.}: F
invZ.inv(proj.z)
aff.x.prod(proj.x, invZ)
aff.y.prod(proj.y, invZ)
func projectiveFromAffine*[F](
proj: var ECP_TwEdwards_Prj[F],
aff: ECP_TwEdwards_Prj[F]) {.inline.} =
proj.x = aff.x
proj.y = aff.y
proj.z.setOne()

View File

@ -8,7 +8,7 @@
import import
./io_bigints, ./io_bigints,
../config/[common, curves], ../config/[common, type_ff],
../arithmetic/finite_fields, ../arithmetic/finite_fields,
../primitives ../primitives
@ -45,6 +45,7 @@ func fromInt*(dst: var FF,
let src = isNeg.mux(SecretWord -src, SecretWord src) let src = isNeg.mux(SecretWord -src, SecretWord src)
let raw {.noinit.} = (type dst.mres).fromRawUint(cast[array[sizeof(src), byte]](src), cpuEndian) let raw {.noinit.} = (type dst.mres).fromRawUint(cast[array[sizeof(src), byte]](src), cpuEndian)
dst.fromBig(raw) dst.fromBig(raw)
dst.cneg(isNeg)
func exportRawUint*(dst: var openarray[byte], func exportRawUint*(dst: var openarray[byte],
src: FF, src: FF,

View File

@ -13,7 +13,9 @@ import
../constantine/elliptic/[ ../constantine/elliptic/[
ec_shortweierstrass_affine, ec_shortweierstrass_affine,
ec_shortweierstrass_projective, ec_shortweierstrass_projective,
ec_shortweierstrass_jacobian], ec_shortweierstrass_jacobian,
ec_twistededwards_affine,
ec_twistededwards_projective],
../constantine/io/io_bigints, ../constantine/io/io_bigints,
../constantine/tower_field_extensions/extension_fields ../constantine/tower_field_extensions/extension_fields
@ -233,7 +235,24 @@ func random_long01Seq(rng: var RngState, a: var ExtensionField) =
# Elliptic curves # Elliptic curves
# ------------------------------------------------------------ # ------------------------------------------------------------
func random_unsafe(rng: var RngState, a: var (ECP_ShortW_Prj or ECP_ShortW_Aff or ECP_ShortW_Jac)) = type ECP = ECP_ShortW_Aff or ECP_ShortW_Prj or ECP_ShortW_Jac or
ECP_TwEdwards_Aff or ECP_TwEdwards_Prj
type ECP_ext = ECP_ShortW_Prj or ECP_ShortW_Jac or
ECP_TwEdwards_Prj
template trySetFromCoord[F](a: ECP, fieldElem: F): SecretBool =
when a is (ECP_ShortW_Aff or ECP_ShortW_Prj or ECP_ShortW_Jac):
trySetFromCoordX(a, fieldElem)
else:
trySetFromCoordY(a, fieldElem)
template trySetFromCoords[F](a: ECP, fieldElem, scale: F): SecretBool =
when a is (ECP_ShortW_Prj or ECP_ShortW_Jac):
trySetFromCoordsXandZ(a, fieldElem, scale)
else:
trySetFromCoordsYandZ(a, fieldElem, scale)
func random_unsafe(rng: var RngState, a: var ECP) =
## Initialize a random curve point with Z coordinate == 1 ## Initialize a random curve point with Z coordinate == 1
## Unsafe: for testing and benchmarking purposes only ## Unsafe: for testing and benchmarking purposes only
var fieldElem {.noInit.}: a.F var fieldElem {.noInit.}: a.F
@ -243,9 +262,9 @@ func random_unsafe(rng: var RngState, a: var (ECP_ShortW_Prj or ECP_ShortW_Aff o
# Euler's criterion: there are (p-1)/2 squares in a field with modulus `p` # 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 # so we have a probability of ~0.5 to get a good point
rng.random_unsafe(fieldElem) rng.random_unsafe(fieldElem)
success = trySetFromCoordX(a, fieldElem) success = trySetFromCoord(a, fieldElem)
func random_unsafe_with_randZ(rng: var RngState, a: var (ECP_ShortW_Prj or ECP_ShortW_Jac)) = func random_unsafe_with_randZ(rng: var RngState, a: var ECP_ext) =
## Initialize a random curve point with Z coordinate being random ## Initialize a random curve point with Z coordinate being random
## Unsafe: for testing and benchmarking purposes only ## Unsafe: for testing and benchmarking purposes only
var Z{.noInit.}: a.F var Z{.noInit.}: a.F
@ -256,9 +275,9 @@ func random_unsafe_with_randZ(rng: var RngState, a: var (ECP_ShortW_Prj or ECP_S
while not bool(success): while not bool(success):
rng.random_unsafe(fieldElem) rng.random_unsafe(fieldElem)
success = trySetFromCoordsXandZ(a, fieldElem, Z) success = trySetFromCoords(a, fieldElem, Z)
func random_highHammingWeight(rng: var RngState, a: var (ECP_ShortW_Prj or ECP_ShortW_Aff or ECP_ShortW_Jac)) = func random_highHammingWeight(rng: var RngState, a: var ECP) =
## Initialize a random curve point with Z coordinate == 1 ## Initialize a random curve point with Z coordinate == 1
## This will be generated with a biaised RNG with high Hamming Weight ## This will be generated with a biaised RNG with high Hamming Weight
## to trigger carry bugs ## to trigger carry bugs
@ -269,9 +288,9 @@ func random_highHammingWeight(rng: var RngState, a: var (ECP_ShortW_Prj or ECP_S
# Euler's criterion: there are (p-1)/2 squares in a field with modulus `p` # 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 # so we have a probability of ~0.5 to get a good point
rng.random_highHammingWeight(fieldElem) rng.random_highHammingWeight(fieldElem)
success = trySetFromCoordX(a, fieldElem) success = trySetFromCoord(a, fieldElem)
func random_highHammingWeight_with_randZ(rng: var RngState, a: var (ECP_ShortW_Prj or ECP_ShortW_Jac)) = func random_highHammingWeight_with_randZ(rng: var RngState, a: var ECP_ext) =
## Initialize a random curve point with Z coordinate == 1 ## Initialize a random curve point with Z coordinate == 1
## This will be generated with a biaised RNG with high Hamming Weight ## This will be generated with a biaised RNG with high Hamming Weight
## to trigger carry bugs ## to trigger carry bugs
@ -283,9 +302,9 @@ func random_highHammingWeight_with_randZ(rng: var RngState, a: var (ECP_ShortW_P
while not bool(success): while not bool(success):
rng.random_highHammingWeight(fieldElem) rng.random_highHammingWeight(fieldElem)
success = trySetFromCoordsXandZ(a, fieldElem, Z) success = trySetFromCoords(a, fieldElem, Z)
func random_long01Seq(rng: var RngState, a: var (ECP_ShortW_Prj or ECP_ShortW_Aff or ECP_ShortW_Jac)) = func random_long01Seq(rng: var RngState, a: var ECP) =
## Initialize a random curve point with Z coordinate == 1 ## Initialize a random curve point with Z coordinate == 1
## This will be generated with a biaised RNG ## This will be generated with a biaised RNG
## that produces long bitstrings of 0 and 1 ## that produces long bitstrings of 0 and 1
@ -297,9 +316,9 @@ func random_long01Seq(rng: var RngState, a: var (ECP_ShortW_Prj or ECP_ShortW_Af
# Euler's criterion: there are (p-1)/2 squares in a field with modulus `p` # 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 # so we have a probability of ~0.5 to get a good point
rng.random_long01Seq(fieldElem) rng.random_long01Seq(fieldElem)
success = trySetFromCoordX(a, fieldElem) success = trySetFromCoord(a, fieldElem)
func random_long01Seq_with_randZ(rng: var RngState, a: var (ECP_ShortW_Prj or ECP_ShortW_Jac)) = func random_long01Seq_with_randZ(rng: var RngState, a: var ECP_ext) =
## Initialize a random curve point with Z coordinate == 1 ## Initialize a random curve point with Z coordinate == 1
## This will be generated with a biaised RNG ## This will be generated with a biaised RNG
## that produces long bitstrings of 0 and 1 ## that produces long bitstrings of 0 and 1
@ -312,7 +331,7 @@ func random_long01Seq_with_randZ(rng: var RngState, a: var (ECP_ShortW_Prj or EC
while not bool(success): while not bool(success):
rng.random_long01Seq(fieldElem) rng.random_long01Seq(fieldElem)
success = trySetFromCoordsXandZ(a, fieldElem, Z) success = trySetFromCoords(a, fieldElem, Z)
# Generic over any Constantine type # Generic over any Constantine type
# ------------------------------------------------------------ # ------------------------------------------------------------
@ -320,7 +339,7 @@ func random_long01Seq_with_randZ(rng: var RngState, a: var (ECP_ShortW_Prj or EC
func random_unsafe*(rng: var RngState, T: typedesc): T = func random_unsafe*(rng: var RngState, T: typedesc): T =
## Create a random Field or Extension Field or Curve Element ## Create a random Field or Extension Field or Curve Element
## Unsafe: for testing and benchmarking purposes only ## Unsafe: for testing and benchmarking purposes only
when T is (ECP_ShortW_Prj or ECP_ShortW_Aff or ECP_ShortW_Jac): when T is ECP:
rng.random_unsafe(result) rng.random_unsafe(result)
elif T is SomeNumber: elif T is SomeNumber:
cast[T](rng.next()) # TODO: Rely on casting integer actually converting in C (i.e. uint64->uint32 is valid) cast[T](rng.next()) # TODO: Rely on casting integer actually converting in C (i.e. uint64->uint32 is valid)
@ -329,7 +348,7 @@ func random_unsafe*(rng: var RngState, T: typedesc): T =
else: # Fields else: # Fields
rng.random_unsafe(result) rng.random_unsafe(result)
func random_unsafe_with_randZ*(rng: var RngState, T: typedesc[ECP_ShortW_Prj or ECP_ShortW_Jac]): T = func random_unsafe_with_randZ*(rng: var RngState, T: typedesc[ECP_ext]): T =
## Create a random curve element with a random Z coordinate ## Create a random curve element with a random Z coordinate
## Unsafe: for testing and benchmarking purposes only ## Unsafe: for testing and benchmarking purposes only
rng.random_unsafe_with_randZ(result) rng.random_unsafe_with_randZ(result)
@ -337,7 +356,7 @@ func random_unsafe_with_randZ*(rng: var RngState, T: typedesc[ECP_ShortW_Prj or
func random_highHammingWeight*(rng: var RngState, T: typedesc): T = func random_highHammingWeight*(rng: var RngState, T: typedesc): T =
## Create a random Field or Extension Field or Curve Element ## Create a random Field or Extension Field or Curve Element
## Skewed towards high Hamming Weight ## Skewed towards high Hamming Weight
when T is (ECP_ShortW_Prj or ECP_ShortW_Aff or ECP_ShortW_Jac): when T is ECP:
rng.random_highHammingWeight(result) rng.random_highHammingWeight(result)
elif T is SomeNumber: elif T is SomeNumber:
cast[T](rng.next()) # TODO: Rely on casting integer actually converting in C (i.e. uint64->uint32 is valid) cast[T](rng.next()) # TODO: Rely on casting integer actually converting in C (i.e. uint64->uint32 is valid)
@ -346,7 +365,7 @@ func random_highHammingWeight*(rng: var RngState, T: typedesc): T =
else: # Fields else: # Fields
rng.random_highHammingWeight(result) rng.random_highHammingWeight(result)
func random_highHammingWeight_with_randZ*(rng: var RngState, T: typedesc[ECP_ShortW_Prj or ECP_ShortW_Jac]): T = func random_highHammingWeight_with_randZ*(rng: var RngState, T: typedesc[ECP_ext]): T =
## Create a random curve element with a random Z coordinate ## Create a random curve element with a random Z coordinate
## Skewed towards high Hamming Weight ## Skewed towards high Hamming Weight
rng.random_highHammingWeight_with_randZ(result) rng.random_highHammingWeight_with_randZ(result)
@ -354,7 +373,7 @@ func random_highHammingWeight_with_randZ*(rng: var RngState, T: typedesc[ECP_Sho
func random_long01Seq*(rng: var RngState, T: typedesc): T = func random_long01Seq*(rng: var RngState, T: typedesc): T =
## Create a random Field or Extension Field or Curve Element ## Create a random Field or Extension Field or Curve Element
## Skewed towards long bitstrings of 0 or 1 ## Skewed towards long bitstrings of 0 or 1
when T is (ECP_ShortW_Prj or ECP_ShortW_Aff or ECP_ShortW_Jac): when T is ECP:
rng.random_long01Seq(result) rng.random_long01Seq(result)
elif T is SomeNumber: elif T is SomeNumber:
cast[T](rng.next()) # TODO: Rely on casting integer actually converting in C (i.e. uint64->uint32 is valid) cast[T](rng.next()) # TODO: Rely on casting integer actually converting in C (i.e. uint64->uint32 is valid)
@ -363,7 +382,7 @@ func random_long01Seq*(rng: var RngState, T: typedesc): T =
else: # Fields else: # Fields
rng.random_long01Seq(result) rng.random_long01Seq(result)
func random_long01Seq_with_randZ*(rng: var RngState, T: typedesc[ECP_ShortW_Prj or ECP_ShortW_Jac]): T = func random_long01Seq_with_randZ*(rng: var RngState, T: typedesc[ECP_ext]): T =
## Create a random curve element with a random Z coordinate ## Create a random curve element with a random Z coordinate
## Skewed towards long bitstrings of 0 or 1 ## Skewed towards long bitstrings of 0 or 1
rng.random_long01Seq_with_randZ(result) rng.random_long01Seq_with_randZ(result)

View File

@ -14,7 +14,7 @@ import
./t_ec_template ./t_ec_template
const const
Iters = 1 Iters = 8
run_EC_addition_tests( run_EC_addition_tests(
ec = ECP_ShortW_Prj[Fp[BN254_Snarks], NotOnTwist], ec = ECP_ShortW_Prj[Fp[BN254_Snarks], NotOnTwist],

View File

@ -23,6 +23,8 @@ import
ec_shortweierstrass_affine, ec_shortweierstrass_affine,
ec_shortweierstrass_jacobian, ec_shortweierstrass_jacobian,
ec_shortweierstrass_projective, ec_shortweierstrass_projective,
ec_twistededwards_affine,
ec_twistededwards_projective,
ec_scalar_mul], ec_scalar_mul],
../constantine/io/[io_bigints, io_fields], ../constantine/io/[io_bigints, io_fields],
# Test utilities # Test utilities
@ -51,6 +53,15 @@ func random_point*(rng: var RngState, EC: typedesc, randZ: bool, gen: RandomGen)
else: else:
result = rng.random_long01Seq_with_randZ(EC) result = rng.random_long01Seq_with_randZ(EC)
template pairingGroup(EC: typedesc): string =
when EC is (ECP_ShortW_Aff or ECP_ShortW_Prj or ECP_ShortW_Jac):
when EC.Tw == NotOnTwist:
"G1"
else:
"G2"
else:
""
proc run_EC_addition_tests*( proc run_EC_addition_tests*(
ec: typedesc, ec: typedesc,
Iters: static int, Iters: static int,
@ -64,12 +75,9 @@ proc run_EC_addition_tests*(
echo "\n------------------------------------------------------\n" echo "\n------------------------------------------------------\n"
echo moduleName, " xoshiro512** seed: ", seed echo moduleName, " xoshiro512** seed: ", seed
when ec.Tw == NotOnTwist: const G1_or_G2 = pairingGroup(ec)
const G1_or_G2 = "G1"
else:
const G1_or_G2 = "G2"
const testSuiteDesc = "Elliptic curve in Short Weierstrass form with projective coordinates" const testSuiteDesc = "Elliptic curve in " & $ec.F.C.getEquationForm() & " form with projective coordinates"
suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]": suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]":
test "The infinity point is the neutral element w.r.t. to EC " & G1_or_G2 & " addition": test "The infinity point is the neutral element w.r.t. to EC " & G1_or_G2 & " addition":
@ -215,12 +223,9 @@ proc run_EC_mul_sanity_tests*(
echo "\n------------------------------------------------------\n" echo "\n------------------------------------------------------\n"
echo moduleName, " xoshiro512** seed: ", seed echo moduleName, " xoshiro512** seed: ", seed
when ec.Tw == NotOnTwist: const G1_or_G2 = pairingGroup(ec)
const G1_or_G2 = "G1"
else:
const G1_or_G2 = "G2"
const testSuiteDesc = "Elliptic curve in Short Weierstrass form" const testSuiteDesc = "Elliptic curve in " & $ec.F.C.getEquationForm() & " form"
suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]": suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]":
test "EC " & G1_or_G2 & " mul [0]P == Inf": test "EC " & G1_or_G2 & " mul [0]P == Inf":
@ -313,12 +318,9 @@ proc run_EC_mul_distributive_tests*(
echo "\n------------------------------------------------------\n" echo "\n------------------------------------------------------\n"
echo moduleName, " xoshiro512** seed: ", seed echo moduleName, " xoshiro512** seed: ", seed
when ec.Tw == NotOnTwist: const G1_or_G2 = pairingGroup(ec)
const G1_or_G2 = "G1"
else:
const G1_or_G2 = "G2"
const testSuiteDesc = "Elliptic curve in Short Weierstrass form" const testSuiteDesc = "Elliptic curve in " & $ec.F.C.getEquationForm() & " form"
suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]": suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]":
@ -383,12 +385,9 @@ proc run_EC_mul_vs_ref_impl*(
echo "\n------------------------------------------------------\n" echo "\n------------------------------------------------------\n"
echo moduleName, " xoshiro512** seed: ", seed echo moduleName, " xoshiro512** seed: ", seed
when ec.Tw == NotOnTwist: const G1_or_G2 = pairingGroup(ec)
const G1_or_G2 = "G1"
else:
const G1_or_G2 = "G2"
const testSuiteDesc = "Elliptic curve in Short Weierstrass form" const testSuiteDesc = "Elliptic curve in " & $ec.F.C.getEquationForm() & " form"
suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]": suite testSuiteDesc & " - " & $ec & " - [" & $WordBitwidth & "-bit mode]":
test "EC " & G1_or_G2 & " mul constant-time is equivalent to a simple double-and-add algorithm": test "EC " & G1_or_G2 & " mul constant-time is equivalent to a simple double-and-add algorithm":

View File

@ -0,0 +1,35 @@
# 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/[type_ff, curves],
../constantine/elliptic/ec_twistededwards_projective,
# Test utilities
./t_ec_template
const
Iters = 8
run_EC_addition_tests(
ec = ECP_TwEdwards_Prj[Fp[Curve25519]],
Iters = Iters,
moduleName = "test_ec_twistededwards_projective_add_double_" & $Curve25519
)
run_EC_addition_tests(
ec = ECP_TwEdwards_Prj[Fp[Jubjub]],
Iters = Iters,
moduleName = "test_ec_twistededwards_projective_add_double_" & $Jubjub
)
run_EC_addition_tests(
ec = ECP_TwEdwards_Prj[Fp[Bandersnatch]],
Iters = Iters,
moduleName = "test_ec_twistededwards_projective_add_double_" & $Bandersnatch
)

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
# Internals
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_twistededwards_projective,
# Test utilities
./t_ec_template
const
Iters = 12
ItersMul = Iters div 4
run_EC_mul_distributive_tests(
ec = ECP_TwEdwards_Prj[Fp[Curve25519]],
ItersMul = ItersMul,
moduleName = "test_ec_twistededwards_projective_mul_distributive_" & $Curve25519
)
run_EC_mul_distributive_tests(
ec = ECP_TwEdwards_Prj[Fp[Jubjub]],
ItersMul = ItersMul,
moduleName = "test_ec_twistededwards_projective_mul_distributive_" & $Jubjub
)
run_EC_mul_distributive_tests(
ec = ECP_TwEdwards_Prj[Fp[Bandersnatch]],
ItersMul = ItersMul,
moduleName = "test_ec_twistededwards_projective_mul_distributive_" & $Bandersnatch
)

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
# Internals
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_twistededwards_projective,
# Test utilities
./t_ec_template
const
Iters = 12
ItersMul = Iters div 4
run_EC_mul_sanity_tests(
ec = ECP_TwEdwards_Prj[Fp[Curve25519]],
ItersMul = ItersMul,
moduleName = "test_ec_twistededwards_projective_mul_sanity_" & $Curve25519
)
run_EC_mul_sanity_tests(
ec = ECP_TwEdwards_Prj[Fp[Jubjub]],
ItersMul = ItersMul,
moduleName = "test_ec_twistededwards_projective_mul_sanity_" & $Jubjub
)
run_EC_mul_sanity_tests(
ec = ECP_TwEdwards_Prj[Fp[Bandersnatch]],
ItersMul = ItersMul,
moduleName = "test_ec_twistededwards_projective_mul_sanity_" & $Bandersnatch
)

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
# Internals
../constantine/config/[type_ff, curves],
../constantine/elliptic/ec_twistededwards_projective,
# Test utilities
./t_ec_template
const
Iters = 12
ItersMul = Iters div 4
run_EC_mul_vs_ref_impl(
ec = ECP_TwEdwards_Prj[Fp[Curve25519]],
ItersMul = ItersMul,
moduleName = "test_ec_twistededwards_projective_mul_vs_ref_" & $Curve25519
)
run_EC_mul_vs_ref_impl(
ec = ECP_TwEdwards_Prj[Fp[Jubjub]],
ItersMul = ItersMul,
moduleName = "test_ec_twistededwards_projective_mul_vs_ref_" & $Jubjub
)
run_EC_mul_vs_ref_impl(
ec = ECP_TwEdwards_Prj[Fp[Bandersnatch]],
ItersMul = ItersMul,
moduleName = "test_ec_twistededwards_projective_mul_vs_ref_" & $Bandersnatch
)

View File

@ -81,7 +81,7 @@ proc exhaustiveCheck(C: static Curve, modulus: static int) =
bool not a.isSquare() bool not a.isSquare()
bool not a.sqrt_if_square() bool not a.sqrt_if_square()
template testImpl(a: untyped): untyped {.dirty.} = template testSqrtImpl(a: untyped): untyped {.dirty.} =
var na{.noInit.}: typeof(a) var na{.noInit.}: typeof(a)
na.neg(a) na.neg(a)
@ -103,17 +103,46 @@ template testImpl(a: untyped): untyped {.dirty.} =
proc randomSqrtCheck(C: static Curve) = proc randomSqrtCheck(C: static Curve) =
test "Random square root check for " & $Curve(C): test "Random square root check for " & $Curve(C):
for _ in 0 ..< 1: # Iters: for _ in 0 ..< Iters:
let a = rng.random_unsafe(Fp[C]) let a = rng.random_unsafe(Fp[C])
testImpl(a) testSqrtImpl(a)
for _ in 0 ..< Iters: for _ in 0 ..< Iters:
let a = rng.randomHighHammingWeight(Fp[C]) let a = rng.randomHighHammingWeight(Fp[C])
testImpl(a) testSqrtImpl(a)
for _ in 0 ..< Iters: for _ in 0 ..< Iters:
let a = rng.random_long01Seq(Fp[C]) let a = rng.random_long01Seq(Fp[C])
testImpl(a) testSqrtImpl(a)
template testSqrtRatioImpl(u, v: untyped): untyped {.dirty.} =
var u_over_v, r{.noInit.}: typeof(v)
u_over_v.inv(v)
u_over_v *= u
let qr = r.sqrt_ratio_if_square(u, v)
check: bool(qr) == bool(u_over_v.isSquare())
if bool(qr):
r.square()
check: bool(r == u_over_v)
proc randomSqrtRatioCheck(C: static Curve) =
test "Random square root check for " & $Curve(C):
for _ in 0 ..< Iters:
let u = rng.random_unsafe(Fp[C])
let v = rng.random_unsafe(Fp[C])
testSqrtRatioImpl(u, v)
for _ in 0 ..< Iters:
let u = rng.randomHighHammingWeight(Fp[C])
let v = rng.randomHighHammingWeight(Fp[C])
testSqrtRatioImpl(u, v)
for _ in 0 ..< Iters:
let u = rng.random_long01Seq(Fp[C])
let v = rng.random_long01Seq(Fp[C])
testSqrtRatioImpl(u, v)
proc main() = proc main() =
suite "Modular square root" & " [" & $WordBitwidth & "-bit mode]": suite "Modular square root" & " [" & $WordBitwidth & "-bit mode]":
@ -125,6 +154,14 @@ proc main() =
randomSqrtCheck BLS12_377 # p ≢ 3 (mod 4) randomSqrtCheck BLS12_377 # p ≢ 3 (mod 4)
randomSqrtCheck BLS12_381 randomSqrtCheck BLS12_381
randomSqrtCheck BW6_761 randomSqrtCheck BW6_761
randomSqrtCheck Curve25519
randomSqrtCheck Jubjub
randomSqrtCheck Bandersnatch
suite "Modular sqrt(u/v)" & " [" & $WordBitwidth & "-bit mode]":
randomSqrtRatioCheck Curve25519
randomSqrtRatioCheck Jubjub
randomSqrtRatioCheck Bandersnatch
suite "Modular square root - 32-bit bugs highlighted by property-based testing " & " [" & $WordBitwidth & "-bit mode]": suite "Modular square root - 32-bit bugs highlighted by property-based testing " & " [" & $WordBitwidth & "-bit mode]":
# test "FKM12_447 - #30": - Deactivated, we don't support the curve as no one uses it. # test "FKM12_447 - #30": - Deactivated, we don't support the curve as no one uses it.
@ -150,11 +187,11 @@ proc main() =
test "Fp[2^127 - 1] - #61": test "Fp[2^127 - 1] - #61":
var a: Fp[Mersenne127] var a: Fp[Mersenne127]
a.fromHex"0x75bfffefbfffffff7fd9dfd800000000" a.fromHex"0x75bfffefbfffffff7fd9dfd800000000"
testImpl(a) testSqrtImpl(a)
test "Fp[2^127 - 1] - #62": test "Fp[2^127 - 1] - #62":
var a: Fp[Mersenne127] var a: Fp[Mersenne127]
a.fromHex"0x7ff7ffffffffffff1dfb7fafc0000000" a.fromHex"0x7ff7ffffffffffff1dfb7fafc0000000"
testImpl(a) testSqrtImpl(a)
main() main()