Scalar mul tests (#28)

* Add sage script for BN254

* Implement (failing) scalar multiplication tests

* Add a first test against sagemath

* Finish the tests against SAGE for BN254

* Add significant test coverage of scalar multiplication with reference checks for BN254_Snarks and BLS12_381
This commit is contained in:
Mamy Ratsimbazafy 2020-06-04 20:37:29 +02:00 committed by GitHub
parent 71a2acc202
commit 82ceca6e3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 881 additions and 19 deletions

View File

@ -27,7 +27,7 @@ import
const Iters = 1_000_000 const Iters = 1_000_000
const InvIters = 1000 const MulIters = 1000
const AvailableCurves = [ const AvailableCurves = [
# P224, # P224,
# BN254_Nogami, # BN254_Nogami,
@ -51,10 +51,19 @@ proc main() =
separator() separator()
doublingBench(ECP_SWei_Proj[Fp[curve]], Iters) doublingBench(ECP_SWei_Proj[Fp[curve]], Iters)
separator() separator()
scalarMulBench(ECP_SWei_Proj[Fp[curve]], scratchSpaceSize = 1 shl 2, MulIters)
separator()
scalarMulBench(ECP_SWei_Proj[Fp[curve]], scratchSpaceSize = 1 shl 3, MulIters)
separator()
scalarMulBench(ECP_SWei_Proj[Fp[curve]], scratchSpaceSize = 1 shl 4, MulIters)
separator()
# scalarMulUnsafeDoubleAddBench(ECP_SWei_Proj[Fp[curve]], MulIters)
# separator()
separator()
main() main()
echo "Notes:" echo "\nNotes:"
echo " - GCC is significantly slower than Clang on multiprecision arithmetic." echo " - GCC is significantly slower than Clang on multiprecision arithmetic."
echo " - The simplest operations might be optimized away by the compiler." echo " - The simplest operations might be optimized away by the compiler."
echo " - Fast Squaring and Fast Multiplication are possible if there are spare bits in the prime representation (i.e. the prime uses 254 bits out of 256 bits)" echo " - Fast Squaring and Fast Multiplication are possible if there are spare bits in the prime representation (i.e. the prime uses 254 bits out of 256 bits)"

View File

@ -15,6 +15,8 @@
import import
# Internals # Internals
../constantine/config/curves, ../constantine/config/curves,
../constantine/arithmetic,
../constantine/io/io_bigints,
# Helpers # Helpers
../helpers/[prng_unsafe, static_for], ../helpers/[prng_unsafe, static_for],
./platforms, ./platforms,
@ -69,12 +71,12 @@ when SupportsGetTicks:
echo "\n=================================================================================================================\n" echo "\n=================================================================================================================\n"
proc separator*() = proc separator*() =
echo "-".repeat(132) echo "-".repeat(157)
proc report(op, elliptic: string, start, stop: MonoTime, startClk, stopClk: int64, iters: int) = proc report(op, elliptic: string, start, stop: MonoTime, startClk, stopClk: int64, iters: int) =
let ns = inNanoseconds((stop-start) div iters) let ns = inNanoseconds((stop-start) div iters)
let throughput = 1e9 / float64(ns) let throughput = 1e9 / float64(ns)
echo &"{op:<15} {elliptic:<40} {throughput:>15.3f} ops/s {ns:>9} ns/op {(stopClk - startClk) div iters:>9} CPU cycles (approx)" echo &"{op:<40} {elliptic:<40} {throughput:>15.3f} ops/s {ns:>9} ns/op {(stopClk - startClk) div iters:>9} CPU cycles (approx)"
macro fixEllipticDisplay(T: typedesc): untyped = macro fixEllipticDisplay(T: typedesc): untyped =
# At compile-time, enums are integers and their display is buggy # At compile-time, enums are integers and their display is buggy
@ -108,3 +110,35 @@ proc doublingBench*(T: typedesc, iters: int) =
let P = rng.random_unsafe(T) let P = rng.random_unsafe(T)
bench("EC Double G1", T, iters): bench("EC Double G1", T, iters):
r.double(P) r.double(P)
proc scalarMulBench*(T: typedesc, scratchSpaceSize: static int, iters: int) =
const bits = T.F.C.getCurveOrderBitwidth()
var r {.noInit.}: T
let P = rng.random_unsafe(T)
let exponent = rng.random_unsafe(BigInt[bits])
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
var scratchSpace{.noInit.}: array[scratchSpaceSize, T]
bench("EC ScalarMul G1 (scratchsize = " & $scratchSpaceSize & ')', T, iters):
r = P
r.scalarMul(exponentCanonical, scratchSpace)
# import ../tests/support/ec_reference_scalar_mult
#
# proc scalarMulUnsafeDoubleAddBench*(T: typedesc, iters: int) =
# const bits = T.F.C.getCurveOrderBitwidth()
#
# var r {.noInit.}: T
# let P = rng.random_unsafe(T)
#
# let exponent = rng.random_unsafe(BigInt[bits])
# var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
# exponentCanonical.exportRawUint(exponent, bigEndian)
#
# bench("EC ScalarMul G1 (unsafe DoubleAdd)", T, iters):
# r = P
# r.unsafe_ECmul_double_add(exponentCanonical)

View File

@ -67,6 +67,8 @@ task test, "Run all tests":
# Elliptic curve arithmetic # Elliptic curve arithmetic
test "", "tests/test_ec_weierstrass_projective_g1.nim" test "", "tests/test_ec_weierstrass_projective_g1.nim"
test "", "tests/test_ec_bn254.nim"
test "", "tests/test_ec_bls12_381.nim"
if sizeof(int) == 8: # 32-bit tests on 64-bit arch if sizeof(int) == 8: # 32-bit tests on 64-bit arch
# Primitives # Primitives
@ -95,6 +97,8 @@ task test, "Run all tests":
# Elliptic curve arithmetic # Elliptic curve arithmetic
test "-d:Constantine32", "tests/test_ec_weierstrass_projective_g1.nim" test "-d:Constantine32", "tests/test_ec_weierstrass_projective_g1.nim"
test "-d:Constantine32", "tests/test_ec_bn254.nim"
test "-d:Constantine32", "tests/test_ec_bls12_381.nim"
# Benchmarks compile and run # Benchmarks compile and run
# ignore Windows 32-bit for the moment # ignore Windows 32-bit for the moment
@ -131,6 +135,8 @@ task test_no_gmp, "Run tests that don't require GMP":
# Elliptic curve arithmetic # Elliptic curve arithmetic
test "", "tests/test_ec_weierstrass_projective_g1.nim" test "", "tests/test_ec_weierstrass_projective_g1.nim"
test "", "tests/test_ec_bn254.nim"
test "", "tests/test_ec_bls12_381.nim"
if sizeof(int) == 8: # 32-bit tests if sizeof(int) == 8: # 32-bit tests
# Primitives # Primitives
@ -155,6 +161,8 @@ task test_no_gmp, "Run tests that don't require GMP":
# Elliptic curve arithmetic # Elliptic curve arithmetic
test "-d:Constantine32", "tests/test_ec_weierstrass_projective_g1.nim" test "-d:Constantine32", "tests/test_ec_weierstrass_projective_g1.nim"
test "-d:Constantine32", "tests/test_ec_bn254.nim"
test "-d:Constantine32", "tests/test_ec_bls12_381.nim"
# Benchmarks compile and run # Benchmarks compile and run
# ignore Windows 32-bit for the moment # ignore Windows 32-bit for the moment

View File

@ -27,7 +27,7 @@ macro Mod*(C: static Curve): untyped =
## Get the Modulus associated to a curve ## Get the Modulus associated to a curve
result = bindSym($C & "_Modulus") result = bindSym($C & "_Modulus")
func getCurveBitSize*(C: static Curve): static int = func getCurveBitwidth*(C: static Curve): static int =
## Returns the number of bits taken by the curve modulus ## Returns the number of bits taken by the curve modulus
result = static(CurveBitWidth[C]) result = static(CurveBitWidth[C])
@ -43,8 +43,22 @@ func family*(C: static Curve): CurveFamily =
# #
# ############################################################ # ############################################################
macro getCurveOrder*(C: static Curve): untyped =
## Get the curve order `r`
## i.e. the number of points on the elliptic curve
result = bindSym($C & "_Order")
macro getCurveOrderBitwidth*(C: static Curve): untyped =
## Get the curve order `r`
## i.e. the number of points on the elliptic curve
result = nnkDotExpr.newTree(
getAST(getCurveOrder(C)),
ident"bits"
)
macro getEquationForm*(C: static Curve): untyped = macro getEquationForm*(C: static Curve): untyped =
## Returns the equation form ## Returns the equation form
## (ShortWeierstrass, Montgomery, Twisted Edwards, Weierstrass, ...)
result = bindSym($C & "_equation_form") result = bindSym($C & "_equation_form")
macro getCoefA*(C: static Curve): untyped = macro getCoefA*(C: static Curve): untyped =

View File

@ -94,6 +94,9 @@ declareCurves:
# G1 Equation: Y^2 = X^3 + 3 # G1 Equation: Y^2 = X^3 + 3
# G2 Equation: Y^2 = X^3 + 3/(9+𝑖) # G2 Equation: Y^2 = X^3 + 3/(9+𝑖)
order: "0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001"
orderBitwidth: 254
cofactor: 1
eq_form: ShortWeierstrass eq_form: ShortWeierstrass
coef_a: 0 coef_a: 0
coef_b: 3 coef_b: 3
@ -141,6 +144,9 @@ declareCurves:
# G1 Equation: y² = x³ + 4 # G1 Equation: y² = x³ + 4
# G2 Equation: y² = x³ + 4 (1+i) # G2 Equation: y² = x³ + 4 (1+i)
order: "0x73eda753299d7d483339d80809a1d80553bda402fffe5bfeffffffff00000001"
orderBitwidth: 255
cofactor: "0x396c8c005555e1568c00aaab0000aaab"
eq_form: ShortWeierstrass eq_form: ShortWeierstrass
coef_a: 0 coef_a: 0
coef_b: 4 coef_b: 4

View File

@ -102,6 +102,8 @@ type
eq_form: CurveEquationForm eq_form: CurveEquationForm
coef_A: CurveCoef coef_A: CurveCoef
coef_B: CurveCoef coef_B: CurveCoef
order: NimNode # nnkStrLit (hex)
orderBitwidth: NimNode # nnkIntLit
sexticTwist: SexticTwist sexticTwist: SexticTwist
sexticNonResidue_fp2: NimNode # nnkPar(nnkIntLit, nnkIntLit) sexticNonResidue_fp2: NimNode # nnkPar(nnkIntLit, nnkIntLit)
@ -191,6 +193,12 @@ proc parseCurveDecls(defs: var seq[CurveParams], curves: NimNode) =
params.coef_B = CurveCoef(kind: Small, coef: sectionVal.intVal.int) params.coef_B = CurveCoef(kind: Small, coef: sectionVal.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"order":
params.order = sectionVal
elif sectionId.eqIdent"orderBitwidth":
params.orderBitwidth = sectionVal
elif sectionId.eqIdent"cofactor":
discard "TODO"
elif sectionId.eqIdent"nonresidue_quad_fp": elif sectionId.eqIdent"nonresidue_quad_fp":
params.nonresidue_quad_fp = sectionVal params.nonresidue_quad_fp = sectionVal
elif sectionId.eqIdent"nonresidue_cube_fp2": elif sectionId.eqIdent"nonresidue_cube_fp2":
@ -269,6 +277,16 @@ proc genMainConstants(defs: var seq[CurveParams]): NimNode =
exported($curve & "_equation_form"), exported($curve & "_equation_form"),
newLit curveDef.eq_form newLit curveDef.eq_form
) )
if not curveDef.order.isNil:
curveDef.orderBitwidth.expectKind(nnkIntLit)
curveEllipticStmts.add newConstStmt(
exported($curve & "_Order"),
newCall(
bindSym"fromHex",
nnkBracketExpr.newTree(bindSym"BigInt", curveDef.orderBitwidth),
curveDef.order
)
)
if curveDef.coef_A.kind != NoCoef and curveDef.coef_B.kind != NoCoef: if curveDef.coef_A.kind != NoCoef and curveDef.coef_B.kind != NoCoef:
curveEllipticStmts.add newConstStmt( curveEllipticStmts.add newConstStmt(
exported($curve & "_coef_A"), exported($curve & "_coef_A"),

View File

@ -30,7 +30,7 @@ type ECP_SWei_Proj*[F] = object
## corresponding to (x, y) with X = xZ and Y = yZ ## corresponding to (x, y) with X = xZ and Y = yZ
## ##
## Note that projective coordinates are not unique ## Note that projective coordinates are not unique
x, y, z: F x*, y*, z*: F
func `==`*[F](P, Q: ECP_SWei_Proj[F]): SecretBool = func `==`*[F](P, Q: ECP_SWei_Proj[F]): SecretBool =
## Constant-time equality check ## Constant-time equality check
@ -62,6 +62,14 @@ func setInf*(P: var ECP_SWei_Proj) =
P.y.setOne() P.y.setOne()
P.z.setZero() P.z.setZero()
func ccopy*(P: var ECP_SWei_Proj, Q: ECP_SWei_Proj, ctl: SecretBool) =
## 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 trySetFromCoordsXandZ*[F](P: var ECP_SWei_Proj[F], x, z: F): SecretBool = func trySetFromCoordsXandZ*[F](P: var ECP_SWei_Proj[F], x, z: F): SecretBool =
## Try to create a point the elliptic curve ## Try to create a point the elliptic curve
## Y²Z = X³ + aXZ² + bZ³ (projective coordinates) ## Y²Z = X³ + aXZ² + bZ³ (projective coordinates)
@ -319,7 +327,7 @@ func scalarMulPrologue(
): uint = ): uint =
## Setup the scratchspace ## Setup the scratchspace
## Returns the fixed-window size for scalar mul with window optimization ## Returns the fixed-window size for scalar mul with window optimization
result = result.scratchspace.len.getWindowLen() result = scratchspace.len.getWindowLen()
# Precompute window content, special case for window = 1 # Precompute window content, special case for window = 1
# (i.e scratchspace has only space for 2 temporaries) # (i.e scratchspace has only space for 2 temporaries)
# The content scratchspace[2+k] is set at [k]P # The content scratchspace[2+k] is set at [k]P
@ -341,11 +349,11 @@ func scalarMulDoubling(
window: uint, window: uint,
acc, acc_len: var uint, acc, acc_len: var uint,
e: var int e: var int
) = ): tuple[k, bits: uint] {.inline.} =
## Doubling steps of doubling and add for scalar multiplication ## Doubling steps of doubling and add for scalar multiplication
## Get the next k bits in range [1, window) ## Get the next k bits in range [1, window)
## and double k times ## and double k times
## Returns the niumber of doubling done and the corresponding bits. ## Returns the number of doubling done and the corresponding bits.
## ##
## Updates iteration variables and accumulators ## Updates iteration variables and accumulators
# #

57
constantine/io/io_ec.nim Normal file
View File

@ -0,0 +1,57 @@
# 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
./io_bigints, ./io_fields,
../config/curves,
../arithmetic/[bigints, finite_fields],
../elliptic/[
ec_weierstrass_affine,
ec_weierstrass_projective
]
# No exceptions allowed
{.push raises: [].}
{.push inline.}
# ############################################################
#
# Parsing from canonical inputs to internal representation
#
# ############################################################
func toHex*(P: ECP_SWei_Proj): string =
## Stringify an elliptic curve point to Hex
## Note. Leading zeros are not removed.
## Result is prefixed with 0x
##
## Output will be padded with 0s to maintain constant-time.
##
## CT:
## - no leaks
##
## TODO: only normalize and don't display the Z coordinate
##
## This proc output may change format in the future
result = $P.F.C & "(x: "
result &= P.x.tohex(bigEndian)
result &= ", y: "
result &= P.y.tohex(bigEndian)
result &= ", z: "
result &= P.y.tohex(bigEndian)
result &= ')'
func fromHex*(dst: var ECP_SWei_Proj, x, y: string): bool {.raises: [ValueError].}=
## Convert hex strings to a curve point
## Returns `false`
## if there is no point with coordinates (`x`, `y`) on the curve
## In that case, `dst` content is undefined.
dst.x.fromHex(x)
dst.y.fromHex(y)
dst.z.setOne()
return bool(isOnCurve(dst.x, dst.y))

View File

@ -82,8 +82,8 @@ func next(rng: var RngState): uint64 =
# BigInts and Fields # BigInts and Fields
# ------------------------------------------------------------ # ------------------------------------------------------------
func random_unsafe[T](rng: var RngState, a: var T, C: static Curve) {.noInit.}= func random_unsafe[T](rng: var RngState, a: var T, C: static Curve) =
## Recursively initialize a BigInt or Field element ## Recursively initialize a BigInt (part of a field) or Field element
## Unsafe: for testing and benchmarking purposes only ## Unsafe: for testing and benchmarking purposes only
when T is BigInt: when T is BigInt:
var reduced, unreduced{.noInit.}: T var reduced, unreduced{.noInit.}: T
@ -99,6 +99,11 @@ func random_unsafe[T](rng: var RngState, a: var T, C: static Curve) {.noInit.}=
for field in fields(a): for field in fields(a):
rng.random_unsafe(field, C) rng.random_unsafe(field, C)
func random_unsafe(rng: var RngState, a: var BigInt) =
## Initialize a standalone BigInt
for i in 0 ..< a.limbs.len:
a.limbs[i] = SecretWord(rng.next())
# Elliptic curves # Elliptic curves
# ------------------------------------------------------------ # ------------------------------------------------------------
@ -172,7 +177,9 @@ func random_unsafe*(rng: var RngState, T: typedesc): T =
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)
else: elif T is BigInt:
rng.random_unsafe(result)
else: # Fields
rng.random_unsafe(result, T.C) rng.random_unsafe(result, T.C)
func random_unsafe_with_randZ*(rng: var RngState, T: typedesc[ECP_SWei_Proj]): T = func random_unsafe_with_randZ*(rng: var RngState, T: typedesc[ECP_SWei_Proj]): T =

View File

@ -0,0 +1,69 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
# ############################################################
#
# BN254 test generator
#
# ############################################################
# Parameters
u = -(2^63 + 2^62 + 2^60 + 2^57 + 2^48 + 2^16)
p = (u - 1)^2 * (u^4 - u^2 + 1)//3 + u
r = u^4 - u^2 + 1
cofactor = Integer('0x396c8c005555e1568c00aaab0000aaab')
# Finite fields
F = GF(p)
K2.<u> = PolynomialRing(F)
# F2.<beta> = F.extension(u^2+1)
# K6.<v> = PolynomialRing(F2)
# F6.<eta> = F2.extension(v^3-beta)
# K12.<w> = PolynomialRing(F6)
# K12.<gamma> = F6.extension(w^2-eta)
# Curves
b = 4
G1 = EllipticCurve(F, [0, b])
# G2 = EllipticCurve(F2, [0, b*beta])
# Test generator
set_random_seed(1337)
for i in range(10):
print('---------------------------------------')
P = G1.random_point()
(Px, Py, Pz) = P
print('Px: ' + Integer(Px).hex())
print('Py: ' + Integer(Py).hex())
print('Pz: ' + Integer(Pz).hex())
exponent = randrange(r) # Pick an integer below curve order
print('scalar: ' + Integer(exponent).hex())
Q = exponent * P
(Qx, Qy, Qz) = Q
print('Qx: ' + Integer(Qx).hex())
print('Qy: ' + Integer(Qy).hex())
print('Qz: ' + Integer(Qz).hex())
# CurveOrder sanity check
#
# P = G1.random_point()
# (Px, Py, Pz) = P
# print('Px: ' + Integer(Px).hex())
# print('Py: ' + Integer(Py).hex())
# print('Pz: ' + Integer(Pz).hex())
#
# print('order: ' + Integer(r).hex())
#
# Q = (r * cofactor) * P
# (Qx, Qy, Qz) = Q
# print('Qx: ' + Integer(Qx).hex())
# print('Qy: ' + Integer(Qy).hex())
# print('Qz: ' + Integer(Qz).hex())

View File

@ -0,0 +1,68 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
# ############################################################
#
# BN254 test generator
#
# ############################################################
# Parameters
u = Integer('0x44E992B44A6909F1')
p = 36*u^4 + 36*u^3 + 24*u^2 + 6*u + 1
r = 36*u^4 + 36*u^3 + 18*u^2 + 6*u + 1
cofactor = 1
# Finite fields
F = GF(p)
K2.<u> = PolynomialRing(F)
# F2.<beta> = F.extension(u^2+9)
# K6.<v> = PolynomialRing(F2)
# F6.<eta> = F2.extension(v^3-beta)
# K12.<w> = PolynomialRing(F6)
# K12.<gamma> = F6.extension(w^2-eta)
# Curves
b = 3
G1 = EllipticCurve(F, [0, b])
# G2 = EllipticCurve(F2, [0, b/beta])
# Test generator
set_random_seed(1337)
for i in range(10):
print('---------------------------------------')
P = G1.random_point()
(Px, Py, Pz) = P
print('Px: ' + Integer(Px).hex())
print('Py: ' + Integer(Py).hex())
print('Pz: ' + Integer(Pz).hex())
exponent = randrange(r) # Pick an integer below curve order
print('scalar: ' + Integer(exponent).hex())
Q = exponent * P
(Qx, Qy, Qz) = Q
print('Qx: ' + Integer(Qx).hex())
print('Qy: ' + Integer(Qy).hex())
print('Qz: ' + Integer(Qz).hex())
# CurveOrder sanity check
#
# P = G1.random_point()
# (Px, Py, Pz) = P
# print('Px: ' + Integer(Px).hex())
# print('Py: ' + Integer(Py).hex())
# print('Pz: ' + Integer(Pz).hex())
#
# print('order: ' + Integer(r).hex())
#
# Q = (r * cofactor) * P
# (Qx, Qy, Qz) = Q
# print('Qx: ' + Integer(Qx).hex())
# print('Qy: ' + Integer(Qy).hex())
# print('Qz: ' + Integer(Qz).hex())

View File

@ -0,0 +1,52 @@
# 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/[common, curves],
../../constantine/arithmetic,
../../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective]
# Support files for testing Elliptic Curve arithmetic
# ------------------------------------------------------------------------------
iterator unpack(scalarByte: byte): bool =
yield bool((scalarByte and 0b10000000) shr 7)
yield bool((scalarByte and 0b01000000) shr 6)
yield bool((scalarByte and 0b00100000) shr 5)
yield bool((scalarByte and 0b00010000) shr 4)
yield bool((scalarByte and 0b00001000) shr 3)
yield bool((scalarByte and 0b00000100) shr 2)
yield bool((scalarByte and 0b00000010) shr 1)
yield bool( scalarByte and 0b00000001)
func unsafe_ECmul_double_add*(
P: var ECP_SWei_Proj,
scalar: openArray[byte],
) =
## **Unsafe** Elliptic Curve Scalar Multiplication
##
## P <- [k] P
##
## This uses the double-and-add algorithm to verify the constant-time production implementation
## This is UNSAFE to use in production and only intended for testing purposes.
##
## This is highly VULNERABLE to timing attacks and power analysis attacks
##
## `scalar` is in canonical representation in BigEndian (octet string)
var t0{.noInit.}, t1{.noInit.}: typeof(P)
t0.setInf()
t1.setInf()
for scalarByte in scalar:
for bit in unpack(scalarByte):
t1.double(t0)
if bit:
t0.sum(t1, P)
else:
t0 = t1
P = t0

151
tests/test_ec_bls12_381.nim Normal file
View File

@ -0,0 +1,151 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
# Standard library
unittest, times,
# Internals
../constantine/config/[common, curves],
../constantine/arithmetic,
../constantine/io/[io_bigints, io_ec],
../constantine/elliptic/[ec_weierstrass_projective],
# Test utilities
./support/ec_reference_scalar_mult
proc test(
id: int,
EC: typedesc[ECP_SWei_Proj],
Px, Py: string,
scalar: string,
Qx, Qy: string
) =
test "test " & $id:
var P: EC
let pOK = P.fromHex(Px, Py)
doAssert pOK
var Q: EC
let qOK = Q.fromHex(Qx, Qy)
let exponent = EC.F.C.matchingBigInt.fromHex(scalar)
var exponentCanonical: array[(exponent.bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
var
impl = P
reference = P
scratchSpace: array[1 shl 4, EC]
impl.scalarMul(exponentCanonical, scratchSpace)
reference.unsafe_ECmul_double_add(exponentCanonical)
doAssert: bool(Q == reference)
doAssert: bool(Q == impl)
suite "BLS12_381 implementation (and unsafe reference impl) vs SageMath":
# Generated via sage sage/testgen_bls12_381.sage
test(
id = 1,
EC = ECP_SWei_Proj[Fp[BLS12_381]],
Px = "f21eda282230f72b855d48055e68ab3825da87831fa5147a64fa071bade4c26bddd45e8b602e62df4d907414a6ec1b4",
Py = "531b38866cb35c19951f4a1ac62242f11fa714a1b99c6116a630fa75e7f4407fcd1ae9770a821c5899a777d341c915a",
scalar = "f7e60a832eb77ac47374bc93251360d6c81c21add62767ff816caf11a20d8db",
Qx = "18d7ca3fb93d7300a0484233f3bac9bca00b45595a4b9caf66aa0b2237f6fd51559a24a634f3876451332c5f754438b2",
Qy = "edbb203999303fc99ef04368412da4b3555f999c703b425dedff3fdc799317c292751c46275b27990c53d933de2db63"
)
test(
id = 2,
EC = ECP_SWei_Proj[Fp[BLS12_381]],
Px = "9ca8e33d8a330b04b052af6cf44bf2ed08cc93d83a4eb48cbb0cabfe02ffb2ef910df44862b271354352f15b70e45b5",
Py = "102f6d07ef45f51de9a4ecef5ec34eae16833f4761c2ddfbe2b414173c3580721135e5bbb74269ab85ba83cb03020d9b",
scalar = "5f10367bdae7aa872d90b5ac209321ce5a15181ce22848d032a8d452055cbfd0",
Qx = "a50d49e3d8757f994aae312dedd55205687c432bc9d97efbe69e87bef4256b87af1b665a669d06657cda6ff01ee42df",
Qy = "160d50aaa21f9d5b4faada77e4f91d8d4f152a0fcca4d30d271d74b20c1bba8638128f99f52d9603d4a24f8e27219bcd"
)
test(
id = 3,
EC = ECP_SWei_Proj[Fp[BLS12_381]],
Px = "173c28687d23de83c950131e548485e8e09d4053d32b814d13b618ee4159e8b61bf6320148ddabcedf2b04d3c9787cd4",
Py = "277f935b4e0a90155915960c617f395dcadead1c7297cf92916add07308fc3f0493aa6dabf31d1f15953f56ac37d3d9",
scalar = "4c321d72220c098fc0fd52306de98f8be9446bf854cf1e4d8dbae62375d18faf",
Qx = "16259e878b5921bbe1e5672cccea0f29fedbb93b8ce1bae4d4b602b6dd5708c6d4e5d82ff92868828c46fd333aadf82d",
Qy = "16d09713f4fe5705f2e3491aa9a1d5827fb3b280f5a1fdde0b01a2b75f5803d528d5f5603cc0e9da29d6a07b8e14df7c"
)
test(
id = 4,
EC = ECP_SWei_Proj[Fp[BLS12_381]],
Px = "177d32dfa6e97daf5ea8aee520bc5c33b7bee37cba114fda52dd948030ad81abdffbdda402c930791e1048ad531b2c80",
Py = "14e9f915698dadd2220586a8bcc921b1f855273c3c0e41a88569e5a9fd2a4e886eeff9a7a11b02ec286987c5a52d55ce",
scalar = "1738857afb76c55f615c2a20b44ca90dcb3267d804ec23fddea431dbee4eb37f",
Qx = "a4bfcfc65eb16562752f5c164349ef673477e19fe020de84eddbc2958f6d40bbbba39fc67ee8c8fdf007922fec97f79",
Qy = "106ccd382d15773e6097f8ea6f012cbec15184d6f4ea08bac2842ed419f0e555f1a43f7434b2e017f9e02971d07eb59d"
)
test(
id = 5,
EC = ECP_SWei_Proj[Fp[BLS12_381]],
Px = "1bc38e70e62770489063d3b7a3bebbc6735ab8dc389576ff03272c2883045aa051d74f8407920530b4c719b140cda81",
Py = "bd24e4fb09ed4098d61e3d2cb456f03d7818ded79dfba9cfe7956829797b12e10f1766c46c1a2e1cf2957295124c782",
scalar = "19c47811813444020c999a2b263940b5054cf45bb8ad8e086ff126bfcd5507e1",
Qx = "b310d4688f2c9f8cd4c030b62ed27341f4c71341fe9c56858a949a2d51670eb6ebe1339163bdb833e692b0ee0cf4e92",
Qy = "c92300561e1acb1e1ae6a1b75f83b9d2d2cb5f07c3f8ea945990ceb75e7ea12c4aec115227c13a05be92f5caed9268e"
)
test(
id = 6,
EC = ECP_SWei_Proj[Fp[BLS12_381]],
Px = "48cddafaca93d33caf910a7a6f74dc3489d53da9fa2f940b70b6dcf538cc08da1a369809ab86a8ee49cead0ed6bfef6",
Py = "173f8dfb384aea011bed89aaca625085dc2940d0775e5f2647fc7574ce822643d0d7b1b39e9a51b9f5a0dca7486bddd0",
scalar = "43ffcda71e45a3e90b7502d92b30a0b06c54c95a91aa21e0438677b1c2714ecb",
Qx = "ef1e4967a3eb19318a66d092eada9810bebf301c168cea7c73fad9d98f7d4c2bde1071fd142c3da90830509f22a82b5",
Qy = "da537922dcb6bf79e4d09237c1a3c5804e3a83b6f18ccb26991d50d77c81bef76139fa73d39c684c7c1616151b1058b"
)
test(
id = 7,
EC = ECP_SWei_Proj[Fp[BLS12_381]],
Px = "9d56cb273bdeef945078066192b74d2f3077f00f5bd1a50b338c44f7c640005a614f9c6fc89cb4678140b2a721c69a8",
Py = "107b42b9a0c22b9e9cd2191b90fede2ab280532ea26806338a5b28533cf9431bde1a8010677a5078c63482953d4f2451",
scalar = "64ad0d6c36dba5368e71f0010aebf860288f54611e5aaf18082bae7a404ebfd8",
Qx = "e0c78d1e1ed993fdeb14e4872965bc90014aa39c728c457a720bf3123ebcdcb17ac553a619b9b7073ada436565d4bb4",
Qy = "c2d9ba441ed90bae4f1597da90e434f1668fda320e4fa04cddcdce0eacb3bc54185d5f7cde826f5bd0e3d59b2424906"
)
test(
id = 8,
EC = ECP_SWei_Proj[Fp[BLS12_381]],
Px = "150a83a868fa6a74dbc5658445ea99ec47009572f303ce1d3c76370804c5a8c26d40c8b4b35a6585612d704c5fb090cb",
Py = "31e73ed0aedebcf0b58d60c16f2e5ddd2d4eb2a6e34177939efcca0767cde241966b5950c3333c62ccddee51de26fe6",
scalar = "b0ac3d0e685583075aa46c03a00859dfbec24ccb36e2cae3806d82275adcc03",
Qx = "9c5e69fbd492a64e5811af7cc69e42bc14d8626f6d384d3f479d8e06c20ec5f460a1e3839f33899b4a9e0ada876ac6e",
Qy = "16990d7d308897c74b87368f847df3ac0bb6609091c8d39b22d5778a4229f0bb92fea385d27db41e237dcfb0d05bd0e7"
)
test(
id = 9,
EC = ECP_SWei_Proj[Fp[BLS12_381]],
Px = "69498486a06c18f836a8e9ed507bbb563d6d03545e03e08f628e8fbd2e5d098e58950071d516ffd044d92b3a8b07184",
Py = "18a169f06fc94f40cd131bdcd23e48e95b1276c0c8daacf56c3a5e278e89ee1094c94aa516113aa4a2455da149f0f989",
scalar = "23941bb3c3659423d6fdafb7cff52e0e02de0ac91e64c537c6203d64905b63d0",
Qx = "482e085550f5e514dd98f2d9b119c284ac165514d228c8f7a179f2b442968984873223af2255a499dc931c63543c0ba",
Qy = "151ce80ca51dd09243d2b1a7937096d6b7494e89190da5ab7604cd913dc4105c871e48c815fefadee2906b8b401e7e71"
)
test(
id = 10,
EC = ECP_SWei_Proj[Fp[BLS12_381]],
Px = "98cc20aa561769b7ee569304503a94752e236bba52938fed7f3093d5867f65361dc8b48c83bd7db490c26736196e20e",
Py = "10a68394358903122bd649bd30b473f4d3b4f0830bfe7da1c48ae87d9429d8fd26f5b4be8d8fd8e4214017044696da29",
scalar = "4203156dcf70582ea8cbd0388104f47fd5a18ae336b2fed8458e1e4e74d7baf5",
Qx = "18ff1dfd96799b7d0bffaa7480121c3a719047815ae41419f1bd1fdd593288bed8827b3d9e45a3a1e01bf7d603b5ba0",
Qy = "49b95ca2c0f75dfb15fc07e5692d23f8eb38cb1cc9c48cd0e93a80adbff135a3945cc7a5d53d2b7510d6ee7cf97308d"
)

151
tests/test_ec_bn254.nim Normal file
View File

@ -0,0 +1,151 @@
# Constantine
# Copyright (c) 2018-2019 Status Research & Development GmbH
# Copyright (c) 2020-Present Mamy André-Ratsimbazafy
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at http://opensource.org/licenses/MIT).
# * Apache v2 license (license terms in the root directory or at http://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
# Standard library
unittest, times,
# Internals
../constantine/config/[common, curves],
../constantine/arithmetic,
../constantine/io/[io_bigints, io_ec],
../constantine/elliptic/[ec_weierstrass_projective],
# Test utilities
./support/ec_reference_scalar_mult
proc test(
id: int,
EC: typedesc[ECP_SWei_Proj],
Px, Py: string,
scalar: string,
Qx, Qy: string
) =
test "test " & $id:
var P: EC
let pOK = P.fromHex(Px, Py)
doAssert pOK
var Q: EC
let qOK = Q.fromHex(Qx, Qy)
let exponent = EC.F.C.matchingBigInt.fromHex(scalar)
var exponentCanonical: array[(exponent.bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
var
impl = P
reference = P
scratchSpace: array[1 shl 4, EC]
impl.scalarMul(exponentCanonical, scratchSpace)
reference.unsafe_ECmul_double_add(exponentCanonical)
doAssert: bool(Q == reference)
doAssert: bool(Q == impl)
suite "BN254 implementation (and unsafe reference impl) vs SageMath":
# Generated via sage sage/testgen_bn254_snarks.sage
test(
id = 1,
EC = ECP_SWei_Proj[Fp[BN254_Snarks]],
Px = "22d3af0f3ee310df7fc1a2a204369ac13eb4a48d969a27fcd2861506b2dc0cd7",
Py = "1c994169687886ccd28dd587c29c307fb3cab55d796d73a5be0bbf9aab69912e",
scalar = "e08a292f940cfb361cc82bc24ca564f51453708c9745a9cf8707b11c84bc448",
Qx = "267c05cd49d681c5857124876748365313b9c285e783206f48513ce06d3df931",
Qy = "2fa00719ce37465dbe7037f723ed5df08c76b9a27a4dd80d86c0ee5157349b96"
)
test(
id = 2,
EC = ECP_SWei_Proj[Fp[BN254_Snarks]],
Px = "2724750abe620fce759b6f18729e40f891a514160d477811a44b222372cc4ea3",
Py = "105cdcbe363921790a56bf2696e73642447c60b814827ca4dba86c814912c98a",
scalar = "2f5c2960850eabadab1e5595ff0bf841206885653e7f2024248b281a86744790",
Qx = "57d2dcbc665fb93fd5119bb982c29700d025423d60a42b5fe17210fd5a868fd",
Qy = "2abad564ff78fbc266dfb77bdd110b22271136b33ce5049fb3ca05107787abc"
)
test(
id = 3,
EC = ECP_SWei_Proj[Fp[BN254_Snarks]],
Px = "39bc19c41835082f86ca046b71875b051575072e4d6a4aeedac31eee34b07df",
Py = "1fdbf42fc20421e1e775fd93ed1888d614f7e39067e7443f21b6a4817481c346",
scalar = "29e140c33f706c0111443699b0b8396d8ead339a3d6f3c212b08749cf2a16f6b",
Qx = "83895d1c7a2b15a5dfe9371983196591415182978e8ff0e83262e32d768c712",
Qy = "2ed8b88e1cd08814ce1d1929d0e4bba6fb5897f915b3525cf12349256da95499"
)
test(
id = 4,
EC = ECP_SWei_Proj[Fp[BN254_Snarks]],
Px = "157a3e1ff9dabccced9746e19855a9438098be6d734f07d1c069aa1bd05b8d87",
Py = "1c96bf3e48bc1a6635d93d4f1302a0eba39bd907c5d861f2a9d0c714ee60f04d",
scalar = "29b05bd55963e262e0fa458c76297fb5be3ec1421fdb1354789f68fdce81dc2c",
Qx = "196aeca74447934eeaba0f2263177fcb7eb239985814f8ef2d7bf08677108c9",
Qy = "1f5aa4c7df4a9855113c63d8fd55c512c7e919b8ae0352e280bdb1009299c3b2"
)
test(
id = 5,
EC = ECP_SWei_Proj[Fp[BN254_Snarks]],
Px = "2f260967d4cd5d15f98c0a0a9d5abaae0c70d3b8d83e1e884586cd6ece395fe7",
Py = "2a102c7aebdfaa999d5a99984148ada142f72f5d4158c10368a2e13dded886f6",
scalar = "1796de74c1edac90d102e7c33f3fad94304eaff4a67a018cae678774d377f6cd",
Qx = "28c73e276807863ecf4ae60b1353790f10f176ca8c55b3db774e33c569ef39d5",
Qy = "c386e24828cead255ec7657698559b23a26fc9bd5db70a1fe20b48ecfbd6db9"
)
test(
id = 6,
EC = ECP_SWei_Proj[Fp[BN254_Snarks]],
Px = "1b4ccef57f4411360a02b8228e4251896c9492ff93a69ba3720da0cd46a04e83",
Py = "1fabcb215bd7c06ead2e6b0167497efc2cdd3dbacf69bcb0244142fd63c1e405",
scalar = "116741cd19dac61c5e77877fc6fef40f363b164b501dfbdbc09e17ea51d6beb0",
Qx = "192ca2e120b0f5296baf7cc47bfebbbc74748c8847bbdbe485bcb796de2622aa",
Qy = "8bc6b1aa4532c727be8fd21a8176d55bc721c727af327f601f7a8dff655b0b9"
)
test(
id = 7,
EC = ECP_SWei_Proj[Fp[BN254_Snarks]],
Px = "2807c88d6759280d6bd83a54d349a533d1a66dc32f72cab8114ab707f10e829b",
Py = "dbf0d486aeed3d303880f324faa2605aa0219e35661bc88150470c7df1c0b61",
scalar = "2a5976268563870739ced3e6efd8cf53887e8e4426803377095708509dd156ca",
Qx = "2841f67de361436f64e582a134fe36ab7196334c758a07e732e1cf1ccb35a476",
Qy = "21fb9b8311e53832044be5ff024f737aee474bc504c7c158fe760cc999da8612"
)
test(
id = 8,
EC = ECP_SWei_Proj[Fp[BN254_Snarks]],
Px = "2754a174a33a55f2a31573767e9bf5381b47dca1cbebc8b68dd4df58b3f1cc2",
Py = "f222f59c8893ad87c581dacb3f8b6e7c20e7a13bc5fb6e24262a3436d663b1",
scalar = "25d596bf6caf4565fbfd22d81f9cef40c8f89b1e5939f20caa1b28056e0e4f58",
Qx = "2b48dd3ace8e403c2905f00cdf13814f0dbecb0c0465e6455fe390cc9730f5a",
Qy = "fe65f0cd4ae0d2e459daa4163f32deed1250b5c384eb5aeb933162a41793d25"
)
test(
id = 9,
EC = ECP_SWei_Proj[Fp[BN254_Snarks]],
Px = "273bf6c679d8e880034590d16c007bbabc6c65ed870a263b5d1ce7375c18fd7",
Py = "2904086cb9e33657999229b082558a74c19b2b619a0499afb2e21d804d8598ee",
scalar = "67a499a389129f3902ba6140660c431a56811b53de01d043e924711bd341e53",
Qx = "1d827e4569f17f068457ffc52f1c6ed7e2ec89b8b520efae48eff41827f79128",
Qy = "be8c488bb9587bcb0faba916277974afe12511e54fbd749e27d3d7efd998713"
)
test(
id = 10,
EC = ECP_SWei_Proj[Fp[BN254_Snarks]],
Px = "ec892c09a5f1c68c1bfec7780a1ebd279739383f2698eeefbba745b3e717fd5",
Py = "23d273a1b9750fe1d4ebd4b7c25f4a8d7d94f6662c436305cca8ff2cdbd3f736",
scalar = "d2f09ceaa2638b7ac3d7d4aa9eff7a12e93dc85db0f9676e5f19fb86d6273e9",
Qx = "305d7692b141962a4a92038adfacc0d2691e5589ed097a1c661cc48c84e2b64e",
Qy = "bafa230a0f5cc2fa3cf07fa46312cb724fc944b097890fa60f2cf42a1be7963"
)

View File

@ -12,11 +12,15 @@ import
# Internals # Internals
../constantine/config/[common, curves], ../constantine/config/[common, curves],
../constantine/arithmetic, ../constantine/arithmetic,
../constantine/io/io_bigints,
../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective], ../constantine/elliptic/[ec_weierstrass_affine, ec_weierstrass_projective],
# Test utilities # Test utilities
../helpers/prng_unsafe ../helpers/prng_unsafe,
./support/ec_reference_scalar_mult
const Iters = 128 const
Iters = 128
ItersMul = Iters div 4
var rng: RngState var rng: RngState
let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32 let seed = uint32(getTime().toUnix() and (1'i64 shl 32 - 1)) # unixTime mod 2^32
@ -169,3 +173,209 @@ suite "Elliptic curve in Short Weierstrass form y² = x³ + a x + b with project
test(Fp[BN254_Snarks], randZ = true) test(Fp[BN254_Snarks], randZ = true)
test(Fp[BLS12_381], randZ = false) test(Fp[BLS12_381], randZ = false)
test(Fp[BLS12_381], randZ = true) test(Fp[BLS12_381], randZ = true)
const BN254_Snarks_order_bits = BN254_Snarks.getCurveOrderBitwidth()
const BLS12_381_order_bits = BLS12_381.getCurveOrderBitwidth()
test "EC mul [0]P == Inf":
proc test(F: typedesc, bits: static int, randZ: static bool) =
for _ in 0 ..< ItersMul:
when randZ:
let a = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
else:
let a = rng.random_unsafe(ECP_SWei_Proj[F])
# zeroInit
var exponentCanonical: array[(bits+7) div 8, byte]
var
impl = a
reference = a
scratchSpace{.noInit.}: array[1 shl 4, ECP_SWei_Proj[F]]
impl.scalarMul(exponentCanonical, scratchSpace)
reference.unsafe_ECmul_double_add(exponentCanonical)
check:
bool(impl.isInf())
bool(reference.isInf())
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = false)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = true)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = false)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = true)
test "EC mul [Order]P == Inf":
proc test(F: typedesc, bits: static int, randZ: static bool) =
for _ in 0 ..< ItersMul:
when randZ:
let a = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
else:
let a = rng.random_unsafe(ECP_SWei_Proj[F])
let exponent = F.C.getCurveOrder()
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
var
impl = a
reference = a
scratchSpace{.noInit.}: array[1 shl 4, ECP_SWei_Proj[F]]
impl.scalarMul(exponentCanonical, scratchSpace)
reference.unsafe_ECmul_double_add(exponentCanonical)
check:
bool(impl.isInf())
bool(reference.isInf())
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = false)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = true)
# TODO: BLS12 is using a subgroup of order "r" such as r*h = CurveOrder
# with h the curve cofactor
# instead of the full group
# test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = false)
# test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = true)
test "EC mul [1]P == P":
proc test(F: typedesc, bits: static int, randZ: static bool) =
for _ in 0 ..< ItersMul:
when randZ:
let a = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
else:
let a = rng.random_unsafe(ECP_SWei_Proj[F])
var exponent{.noInit.}: BigInt[bits]
exponent.setOne()
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
var
impl = a
reference = a
scratchSpace{.noInit.}: array[1 shl 4, ECP_SWei_Proj[F]]
impl.scalarMul(exponentCanonical, scratchSpace)
reference.unsafe_ECmul_double_add(exponentCanonical)
check:
bool(impl == a)
bool(reference == a)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = false)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = true)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = false)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = true)
test "EC mul [2]P == P.double()":
proc test(F: typedesc, bits: static int, randZ: static bool) =
for _ in 0 ..< ItersMul:
when randZ:
let a = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
else:
let a = rng.random_unsafe(ECP_SWei_Proj[F])
var doubleA{.noInit.}: ECP_SWei_Proj[F]
doubleA.double(a)
let exponent = BigInt[bits].fromUint(2)
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
var
impl = a
reference = a
scratchSpace{.noInit.}: array[1 shl 4, ECP_SWei_Proj[F]]
impl.scalarMul(exponentCanonical, scratchSpace)
reference.unsafe_ECmul_double_add(exponentCanonical)
check:
bool(impl == doubleA)
bool(reference == doubleA)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = false)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = true)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = false)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = true)
test "EC mul is distributive over EC add":
proc test(F: typedesc, bits: static int, randZ: static bool) =
for _ in 0 ..< ItersMul:
when randZ:
let a = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
let b = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
else:
let a = rng.random_unsafe(ECP_SWei_Proj[F])
let b = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
let exponent = rng.random_unsafe(BigInt[bits])
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
# [k](a + b) - Factorized
var
fImpl{.noInit.}: ECP_SWei_Proj[F]
fReference{.noInit.}: ECP_SWei_Proj[F]
scratchSpace{.noInit.}: array[1 shl 4, ECP_SWei_Proj[F]]
fImpl.sum(a, b)
fReference.sum(a, b)
fImpl.scalarMul(exponentCanonical, scratchSpace)
fReference.unsafe_ECmul_double_add(exponentCanonical)
# [k]a + [k]b - Distributed
var kaImpl = a
var kaRef = a
kaImpl.scalarMul(exponentCanonical, scratchSpace)
kaRef.unsafe_ECmul_double_add(exponentCanonical)
var kbImpl = b
var kbRef = b
kbImpl.scalarMul(exponentCanonical, scratchSpace)
kbRef.unsafe_ECmul_double_add(exponentCanonical)
var kakbImpl{.noInit.}, kakbRef{.noInit.}: ECP_SWei_Proj[F]
kakbImpl.sum(kaImpl, kbImpl)
kakbRef.sum(kaRef, kbRef)
check:
bool(fImpl == kakbImpl)
bool(fReference == kakbRef)
bool(fImpl == fReference)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = false)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = true)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = false)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = true)
test "EC mul constant-time is equivalent to a simple double-and-add algorithm":
proc test(F: typedesc, bits: static int, randZ: static bool) =
for _ in 0 ..< ItersMul:
when randZ:
let a = rng.random_unsafe_with_randZ(ECP_SWei_Proj[F])
else:
let a = rng.random_unsafe(ECP_SWei_Proj[F])
let exponent = rng.random_unsafe(BigInt[bits])
var exponentCanonical{.noInit.}: array[(bits+7) div 8, byte]
exponentCanonical.exportRawUint(exponent, bigEndian)
var
impl = a
reference = a
scratchSpace{.noInit.}: array[1 shl 4, ECP_SWei_Proj[F]]
impl.scalarMul(exponentCanonical, scratchSpace)
reference.unsafe_ECmul_double_add(exponentCanonical)
check: bool(impl == reference)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = false)
test(Fp[BN254_Snarks], bits = BN254_Snarks_order_bits, randZ = true)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = false)
test(Fp[BLS12_381], bits = BLS12_381_order_bits, randZ = true)

View File

@ -56,7 +56,7 @@ proc binary_prologue[C: static Curve, N: static int](
a, b, p: var mpz_t, a, b, p: var mpz_t,
aTest, bTest: var Fp[C], aTest, bTest: var Fp[C],
aBuf, bBuf: var array[N, byte]) = aBuf, bBuf: var array[N, byte]) =
const bits = C.getCurveBitSize() const bits = C.getCurveBitwidth()
# Generate random value in the range 0 ..< 2^(bits-1) # Generate random value in the range 0 ..< 2^(bits-1)
mpz_urandomb(a, gmpRng, uint bits) mpz_urandomb(a, gmpRng, uint bits)
@ -123,7 +123,7 @@ proc addTests(gmpRng: var gmp_randstate_t, a, b, p, r: var mpz_t, C: static Curv
# echo "Testing: random modular addition on ", $C # echo "Testing: random modular addition on ", $C
const const
bits = C.getCurveBitSize() bits = C.getCurveBitwidth()
bufLen = (bits + 7) div 8 bufLen = (bits + 7) div 8
var var
aTest, bTest{.noInit.}: Fp[C] aTest, bTest{.noInit.}: Fp[C]
@ -146,7 +146,7 @@ proc subTests(gmpRng: var gmp_randstate_t, a, b, p, r: var mpz_t, C: static Curv
# echo "Testing: random modular substraction on ", $C # echo "Testing: random modular substraction on ", $C
const const
bits = C.getCurveBitSize() bits = C.getCurveBitwidth()
bufLen = (bits + 7) div 8 bufLen = (bits + 7) div 8
var var
aTest, bTest{.noInit.}: Fp[C] aTest, bTest{.noInit.}: Fp[C]
@ -169,7 +169,7 @@ proc mulTests(gmpRng: var gmp_randstate_t, a, b, p, r: var mpz_t, C: static Curv
# echo "Testing: random modular multiplication on ", $C # echo "Testing: random modular multiplication on ", $C
const const
bits = C.getCurveBitSize() bits = C.getCurveBitwidth()
bufLen = (bits + 7) div 8 bufLen = (bits + 7) div 8
var var
aTest, bTest{.noInit.}: Fp[C] aTest, bTest{.noInit.}: Fp[C]
@ -189,7 +189,7 @@ proc invTests(gmpRng: var gmp_randstate_t, a, b, p, r: var mpz_t, C: static Curv
# echo "Testing: random modular inversion on ", $C # echo "Testing: random modular inversion on ", $C
const const
bits = C.getCurveBitSize() bits = C.getCurveBitwidth()
bufLen = (bits + 7) div 8 bufLen = (bits + 7) div 8
var var
aTest, bTest{.noInit.}: Fp[C] aTest, bTest{.noInit.}: Fp[C]