Skip to content

Commit

Permalink
Twisted Edwards MSM: setNeutral instead of zeroMem (#406)
Browse files Browse the repository at this point in the history
* fix: initialization of buckets in MSM and equality check with zero points for Twisted Edwards

* fix: twistedEdwards zero affine point conversion to projective

* enabled optimized MSM for Verkle IPA
  • Loading branch information
mratsim authored Jun 27, 2024
1 parent da3a68a commit 29def11
Show file tree
Hide file tree
Showing 8 changed files with 58 additions and 34 deletions.
2 changes: 1 addition & 1 deletion config.nims
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Absolute imports from porject root
# Absolute imports from project root
--path:"."
4 changes: 2 additions & 2 deletions constantine/commitments/eth_verkle_ipa.nim
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ func ipa_verify*[N, logN: static int, EcAff, F](

# ∑ᵢ[uᵢ]Lᵢ + ∑ᵢ[uᵢ⁻¹]Rᵢ + [⟨ā,b̄⟩.w]G - [a₀]G₀ - [a₀.b₀.w]G
var t {.noInit.}: EC
t.multiScalarMul_reference_vartime(fs.toOpenArray(), ecs.toOpenArray())
t.multiScalarMul_vartime(fs.toOpenArray(), ecs.toOpenArray())

# ∑ᵢ[uᵢ]Lᵢ + ∑ᵢ[uᵢ⁻¹]Rᵢ + [⟨ā,b̄⟩.w]G - [a₀]G₀ - [a₀.b₀.w]G = -⟨ā,Ḡ⟩
var C {.noInit.}: EC
Expand Down Expand Up @@ -949,7 +949,7 @@ func ipa_multi_verify*[N, logN: static int, EcAff, F](
# E = ∑rⁱ.Cᵢ/(t-zᵢ)
var E {.noInit.}: EC
var Eaff {.noInit.}: EcAff
E.multiScalarMul_reference_vartime(invTminusChallenges, comms_by_challenges, num_distinct_challenges)
E.multiScalarMul_vartime(invTminusChallenges, comms_by_challenges, num_distinct_challenges)
Eaff.affine(E)
transcript.absorb("E", Eaff)
freeHeapAligned(invTminusChallenges)
Expand Down
4 changes: 2 additions & 2 deletions constantine/commitments/pedersen_commitments.nim
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func pedersen_commit[EC, ECaff](
##
## Output:
## Commit(m) := ∑[mᵢ]Gᵢ
r.multiScalarMul_reference_vartime(messages, public_generators)
r.multiScalarMul_vartime(messages, public_generators)

func pedersen_commit*[N: static int, EC, ECaff, F](
crs: PolynomialEval[N, EcAff],
Expand All @@ -49,7 +49,7 @@ func pedersen_commit*[N: static int, EC, ECaff, F](
##
## Output:
## Commit(m) := ∑[mᵢ]Gᵢ
r.multiScalarMul_reference_vartime(messages.evals, crs.evals)
r.multiScalarMul_vartime(messages.evals, crs.evals)

func pedersen_commit*[EC, ECaff, F](
public_generators: View[ECaff],
Expand Down
6 changes: 4 additions & 2 deletions constantine/math/elliptic/ec_multi_scalar_mul.nim
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,8 @@ func multiScalarMul_vartime*[bits: static int, EC, ECaff](
const numBuckets = 1 shl (c-1)

let buckets = allocHeapArray(EC, numBuckets)
zeroMem(buckets[0].addr, sizeof(EC) * numBuckets)
for i in 0 ..< numBuckets:
buckets[i].setNeutral()

# Algorithm
# ---------
Expand Down Expand Up @@ -366,7 +367,8 @@ func multiScalarMulAffine_vartime[bits: static int, EC, ECaff](
when top != 0: # Prologue
when excess != 0:
# The top might use only a few bits, the affine scheduler would likely have significant collisions
zeroMem(sched.buckets.pt.addr, buckets.pt.sizeof())
for i in 0 ..< numBuckets:
sched.buckets.pt[i].setNeutral()
r.miniMSM(sched.buckets.pt.asUnchecked(), w, kTopWindow, c, coefs, points, N)
w -= c
else:
Expand Down
14 changes: 8 additions & 6 deletions constantine/math/elliptic/ec_multi_scalar_mul_parallel.nim
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,15 @@ export bestBucketBitSize
# Parallel MSM non-affine
# ------------------------------

proc bucketAccumReduce_zeroMem[bits: static int, EC, ECaff](
proc bucketAccumReduce_withInit[bits: static int, EC, ECaff](
windowSum: ptr EC,
buckets: ptr EC or ptr UncheckedArray[EC],
bitIndex: int, miniMsmKind: static MiniMsmKind, c: static int,
coefs: ptr UncheckedArray[BigInt[bits]], points: ptr UncheckedArray[ECaff], N: int) =
const numBuckets = 1 shl (c-1)
let buckets = cast[ptr UncheckedArray[EC]](buckets)
zeroMem(buckets, sizeof(EC) * numBuckets)
for i in 0 ..< numBuckets:
buckets[i].setNeutral()
bucketAccumReduce(windowSum[], buckets, bitIndex, miniMsmKind, c, coefs, points, N)

proc msm_vartime_parallel[bits: static int, EC, ECaff](
Expand All @@ -168,14 +169,14 @@ proc msm_vartime_parallel[bits: static int, EC, ECaff](
# ---------

block: # 1. Bucket accumulation and reduction
miniMSMsReady[0] = tp.spawnAwaitable bucketAccumReduce_zeroMem(
miniMSMsReady[0] = tp.spawnAwaitable bucketAccumReduce_withInit(
miniMSMsResults[0].addr,
bucketsMatrix[0].addr,
bitIndex = 0, kBottomWindow, c,
coefs, points, N)

for w in 1 ..< numFullWindows:
miniMSMsReady[w] = tp.spawnAwaitable bucketAccumReduce_zeroMem(
miniMSMsReady[w] = tp.spawnAwaitable bucketAccumReduce_withInit(
miniMSMsResults[w].addr,
bucketsMatrix[w*numBuckets].addr,
bitIndex = w*c, kFullWindow, c,
Expand All @@ -187,7 +188,7 @@ proc msm_vartime_parallel[bits: static int, EC, ECaff](

when top != 0:
when excess != 0:
bucketAccumReduce_zeroMem(
bucketAccumReduce_withInit(
r,
bucketsMatrix[numFullWindows*numBuckets].addr,
bitIndex = top, kTopWindow, c,
Expand Down Expand Up @@ -374,7 +375,8 @@ proc msmAffine_vartime_parallel[bits: static int, EC, ECaff](
when top != 0:
when excess != 0:
let buckets = allocHeapArray(EC, numBuckets)
zeroMem(buckets[0].addr, sizeof(EC) * numBuckets)
for i in 0 ..< numBuckets:
buckets[i].setNeutral()
r[].bucketAccumReduce(buckets, bitIndex = top, kTopWindow, c,
coefs, points, N)
buckets.freeHeap()
Expand Down
38 changes: 23 additions & 15 deletions constantine/math/elliptic/ec_twistededwards_projective.nim
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ type EC_TwEdw_Prj*[F] = object
template getScalarField*(EC: type EC_TwEdw_Prj): untyped =
Fr[EC.F.Name]

func isNeutral*(P: EC_TwEdw_Prj): SecretBool {.inline.} =
## Returns true if P is the neutral element / identity element
## and false otherwise, i.e. ∀Q, P+Q == Q
## Contrary to Short Weierstrass curve, the neutral element is on the curve
# Isogeny-based constructions to create
# prime order curves overload this generic identity check.
result = P.x.isZero() and (P.y == P.z)

func setNeutral*(P: var EC_TwEdw_Prj) {.inline.} =
## Set P to the neutral element / identity element
## i.e. ∀Q, P+Q == Q.
## Contrary to Short Weierstrass curve, the neutral element is on the curve
P.x.setZero()
P.y.setOne()
P.z.setOne()

func `==`*(P, Q: EC_TwEdw_Prj): SecretBool =
## Constant-time equality check
## This is a costly operation
Expand All @@ -52,21 +68,8 @@ func `==`*(P, Q: EC_TwEdw_Prj): SecretBool =
b.prod(Q.y, P.z)
result = result and a == b

func isNeutral*(P: EC_TwEdw_Prj): SecretBool {.inline.} =
## Returns true if P is the neutral element / identity element
## and false otherwise, i.e. ∀Q, P+Q == Q
## Contrary to Short Weierstrass curve, the neutral element is on the curve
# Isogeny-based constructions to create
# prime order curves overload this generic identity check.
result = P.x.isZero() and (P.y == P.z)

func setNeutral*(P: var EC_TwEdw_Prj) {.inline.} =
## Set P to the neutral element / identity element
## i.e. ∀Q, P+Q == Q.
## Contrary to Short Weierstrass curve, the neutral element is on the curve
P.x.setZero()
P.y.setOne()
P.z.setOne()
# Ensure a zero-init point doesn't propagate 0s and match any
result = result and not(P.isNeutral() xor Q.isNeutral())

func ccopy*(P: var EC_TwEdw_Prj, Q: EC_TwEdw_Prj, ctl: SecretBool) {.inline.} =
## Constant-time conditional copy
Expand Down Expand Up @@ -458,6 +461,11 @@ func fromAffine*[F](
proj.y = aff.y
proj.z.setOne()

let inf = aff.isNeutral()
proj.x.csetZero(inf)
proj.y.csetOne(inf)
proj.z.csetOne(inf)

# Vartime overloading
# ------------------------------------------------------------
# For generic vartime operations on both ShortWeierstrass curves and Twisted Edwards
Expand Down
10 changes: 10 additions & 0 deletions constantine/named/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# Named Algebraic Objects

This folder holds named fields and named curves configuration and precomputed constants.


⚠️ For Twisted Edwards curves
The formula are complete only if d is not a square, otherwise
they must be modified to handle the identity/neutral element with conditional moves.

Sage script to check with Bandersnatch constants for example
r = Integer('0x1cfb69d4ca675f520cce760202687600ff8f87007419047174fd06b52876e7e1')
d = Integer('0x6389c12633c267cbc66e3bf86be3b6d8cb66677177e54f92b369f2f5188d58e7')
GF(r)(d).nth_root(2, all=True)
14 changes: 8 additions & 6 deletions constantine/named/config_fields_and_curves.nim
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ export CurveFamily, SexticTwist
# Barbulescu, R. and S. Duquesne, "Updating Key Size Estimations for Pairings",
# Journal of Cryptology, DOI 10.1007/s00145-018-9280-5, January 2018.

# Generates public:
# - type Curve* = enum
# - proc Mod*(curve: static Curve): auto
# which returns the field modulus of the curve
# - proc family*(curve: static Curve): CurveFamily
# which returns the curve family
# ⚠️ For Twisted Edwards curves
# The formula are complete only if d is not a square, otherwise
# they must be modified to handle the identity/neutral element with conditional moves.
#
# Sage script to check with Bandersnatch constants for example
# r = Integer('0x1cfb69d4ca675f520cce760202687600ff8f87007419047174fd06b52876e7e1')
# d = Integer('0x6389c12633c267cbc66e3bf86be3b6d8cb66677177e54f92b369f2f5188d58e7')
# GF(r)(d).nth_root(2, all=True)

declareCurves:
# -----------------------------------------------------------------------------
Expand Down

0 comments on commit 29def11

Please sign in to comment.