`MapToScalarField()` added for Banderwagon points (#278)

* feat: MapToScalarField added for Banderwagon points

* fix: syntax + NRVO

* fix: comments added for function & spec files linked

* feat: batchAffine + batchInversion

* feat: batchMapToScalarField + tests

* fix: comments and formatting

* fix: static to openArray changed

---------

Co-authored-by: Mamy Ratsimbazafy <mamy_github@numforge.co>
This commit is contained in:
Advaita Saha 2023-10-06 13:33:42 +05:30 committed by GitHub
parent 0f9b9e9606
commit c97036d1df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 354 additions and 2 deletions

View File

@ -497,6 +497,7 @@ const testDesc: seq[tuple[path: string, useGMP: bool]] = @[
("tests/t_ethereum_bls_signatures.nim", false),
("tests/t_ethereum_eip2333_bls12381_key_derivation.nim", false),
("tests/t_ethereum_eip4844_deneb_kzg.nim", false),
("tests/t_ethereum_verkle_primitives.nim", false),
("tests/t_ethereum_eip4844_deneb_kzg_parallel.nim", false),
]

View File

@ -0,0 +1,94 @@
# 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.
## ############################################################
##
## Verkle Trie primitives for Ethereum
##
## ############################################################
import
./math/config/[type_ff, curves],
./math/arithmetic,
./math/elliptic/[
ec_twistededwards_projective,
ec_twistededwards_batch_ops
],
./math/io/[io_bigints, io_fields],
./curves_primitives
func mapToBaseField*(dst: var Fp[Banderwagon],p: ECP_TwEdwards_Prj[Fp[Banderwagon]]) =
## The mapping chosen for the Banderwagon Curve is x/y
##
## This function takes a Banderwagon element & then
## computes the x/y value and returns as an Fp element
##
## Spec : https://hackmd.io/@6iQDuIePQjyYBqDChYw_jg/BJBNcv9fq#Map-To-Field
var invY: Fp[Banderwagon]
invY.inv(p.y) # invY = 1/Y
dst.prod(p.x, invY) # dst = (X) * (1/Y)
func mapToScalarField*(res: var Fr[Banderwagon], p: ECP_TwEdwards_Prj[Fp[Banderwagon]]): bool {.discardable.} =
## This function takes the x/y value from the above function as Fp element
## and convert that to bytes in Big Endian,
## and then load that to a Fr element
##
## Spec : https://hackmd.io/wliPP_RMT4emsucVuCqfHA?view#MapToFieldElement
var baseField: Fp[Banderwagon]
var baseFieldBytes: array[32, byte]
baseField.mapToBaseField(p) # compute the defined mapping
let check1 = baseFieldBytes.marshalBE(baseField) # Fp -> bytes
let check2 = res.unmarshalBE(baseFieldBytes) # bytes -> Fr
return check1 and check2
## ############################################################
##
## Batch Operations
##
## ############################################################
func batchMapToScalarField*(
res: var openArray[Fr[Banderwagon]],
points: openArray[ECP_TwEdwards_Prj[Fp[Banderwagon]]]): bool {.discardable.} =
## This function performs the `mapToScalarField` operation
## on a batch of points
##
## The batch inversion used in this using
## the montogomenry trick, makes is faster than
## just iterating of over the array of points and
## converting the curve points to field elements
##
## Spec : https://hackmd.io/wliPP_RMT4emsucVuCqfHA?view#MapToFieldElement
var check: bool = true
check = check and (res.len == points.len)
let N = res.len
var ys = allocStackArray(Fp[Banderwagon], N)
var ys_inv = allocStackArray(Fp[Banderwagon], N)
for i in 0 ..< N:
ys[i] = points[i].y
ys_inv.batchInvert(ys, N)
for i in 0 ..< N:
var mappedElement: Fp[Banderwagon]
var bytes: array[32, byte]
mappedElement.prod(points[i].x, ys_inv[i])
check = bytes.marshalBE(mappedElement)
check = check and res[i].unmarshalBE(bytes)
return check

View File

@ -0,0 +1,123 @@
# 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
../../platforms/abstractions,
../arithmetic,
./ec_twistededwards_affine,
./ec_twistededwards_projective
# No exceptions allowed, or array bound checks or integer overflow
{.push raises: [], checks:off.}
# ############################################################
#
# Elliptic Curve in Twisted Edwards form
# Batch conversion
#
# ############################################################
func batchAffine*[F](
affs: ptr UncheckedArray[ECP_TwEdwards_Aff[F]],
projs: ptr UncheckedArray[ECP_TwEdwards_Prj[F]],
N: int) {.noInline, tags:[Alloca].} =
# Algorithm: Montgomery's batch inversion
# - Speeding the Pollard and Elliptic Curve Methods of Factorization
# Section 10.3.1
# Peter L. Montgomery
# https://www.ams.org/journals/mcom/1987-48-177/S0025-5718-1987-0866113-7/S0025-5718-1987-0866113-7.pdf
# - Modern Computer Arithmetic
# Section 2.5.1 Several inversions at once
# Richard P. Brent and Paul Zimmermann
# https://members.loria.fr/PZimmermann/mca/mca-cup-0.5.9.pdf
# To avoid temporaries, we store partial accumulations
# in affs[i].x
let zeroes = allocStackArray(SecretBool, N)
affs[0].x = projs[0].z
zeroes[0] = affs[0].x.isZero()
affs[0].x.csetOne(zeroes[0])
for i in 1 ..< N:
# Skip zero z-coordinates (infinity points)
var z = projs[i].z
zeroes[i] = z.isZero()
z.csetOne(zeroes[i])
if i != N-1:
affs[i].x.prod(affs[i-1].x, z, skipFinalSub = true)
else:
affs[i].x.prod(affs[i-1].x, z, skipFinalSub = false)
var accInv {.noInit.}: F
accInv.inv(affs[N-1].x)
for i in countdown(N-1, 1):
# Extract 1/Pᵢ
var invi {.noInit.}: F
invi.prod(accInv, affs[i-1].x, skipFinalSub = true)
invi.csetZero(zeroes[i])
# Now convert Pᵢ to affine
affs[i].x.prod(projs[i].x, invi)
affs[i].y.prod(projs[i].y, invi)
# next iteration
invi = projs[i].z
invi.csetOne(zeroes[i])
accInv.prod(accInv, invi, skipFinalSub = true)
block: # tail
accInv.csetZero(zeroes[0])
affs[0].x.prod(projs[0].x, accInv)
affs[0].y.prod(projs[0].y, accInv)
func batchAffine*[N: static int, F](
affs: var array[N, ECP_TwEdwards_Aff[F]],
projs: array[N, ECP_TwEdwards_Prj[F]]) {.inline.} =
batchAffine(affs.asUnchecked(), projs.asUnchecked(), N)
func batchAffine*[M, N: static int, F](
affs: var array[M, array[N, ECP_TwEdwards_Aff[F]]],
projs: array[M, array[N, ECP_TwEdwards_Prj[F]]]) {.inline.} =
batchAffine(affs[0].asUnchecked(), projs[0].asUnchecked(), M*N)
func batchInvert*[F](
dst: ptr UncheckedArray[F],
elements: ptr UncheckedArray[F],
N: int
) {.noInline.} =
## Montgomery's batch inversion
var zeros = allocStackArray(bool, N)
zeroMem(zeros, N)
var accumulator: F
accumulator.setOne() # sets the accumulator to 1
for i in 0 ..< N:
if elements[i].isZero().bool():
zeros[i] = true
continue
dst[i] = accumulator
accumulator *= elements[i]
accumulator.inv() # inversion of the accumulator
for i in countdown(N-1, 0):
if zeros[i] == true:
continue
dst[i] *= accumulator
accumulator *= elements[i]
func batchInvert*[F](dst: openArray[F], source: openArray[F]): bool {.inline.} =
if dst.len != source.len:
return false
let N = dst.len
batchInvert(dst.asUnchecked(), source.asUnchecked(), N)
return true

View File

@ -6,12 +6,19 @@
# * 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.
# ############################################################
#
# Ethereum Verkle Primitves Tests
#
# ############################################################
import
std/unittest,
../constantine/math/config/[type_ff, curves],
../constantine/math/elliptic/[
ec_twistededwards_affine,
ec_twistededwards_projective
ec_twistededwards_projective,
ec_twistededwards_batch_ops
],
../constantine/math/io/io_fields,
../constantine/serialization/[
@ -20,7 +27,8 @@ import
codecs
],
../constantine/math/arithmetic,
../constantine/math/constants/zoo_generators
../constantine/math/constants/zoo_generators,
../constantine/ethereum_verkle_primitives
type
EC* = ECP_TwEdwards_Prj[Fp[Banderwagon]]
@ -70,6 +78,11 @@ const bad_bit_string: array[16, string] = [
"0x120faa1df94d5d831bbb69fc44816e25afd27288a333299ac3c94518fd0e016f",
]
const expected_scalar_field_elements: array[2, string] = [
"0x0e0c604381ef3cd11bdc84e8faa59b542fbbc92f800ed5767f21e5dbc59840ce",
"0x0a21f7dfa8ddaf6ef6f2044f13feec50cbb963996112fa1de4e3f52dbf6b7b6d"
] # test data generated from go-ipa implementation
# ############################################################
#
# Banderwagon Serialization Tests
@ -207,3 +220,124 @@ suite "Banderwagon Points Tests":
testTwoTorsion()
# ############################################################
#
# Banderwagon Points Mapped to Scalar Field ( Fp -> Fr )
#
# ############################################################
suite "Banderwagon Elements Mapping":
## Tests if the mapping from Fp to Fr
## is working as expected or not
test "Testing Map To Base Field":
proc testMultiMapToBaseField() =
var A, B, genPoint {.noInit.}: EC
genPoint.fromAffine(generator)
A.sum(genPoint, genPoint) # A = g+g = 2g
B.double(genPoint) # B = 2g
B.double() # B = 2B = 4g
var expected_a, expected_b: Fr[Banderwagon]
# conver the points A & B which are in Fp
# to the their mapped Fr points
expected_a.mapToScalarField(A)
expected_b.mapToScalarField(B)
doAssert expected_a.toHex() == expected_scalar_field_elements[0], "Mapping to Scalar Field Incorrect"
doAssert expected_b.toHex() == expected_scalar_field_elements[1], "Mapping to Scalar Field Incorrect"
testMultiMapToBaseField()
# ############################################################
#
# Banderwagon Batch Operations
#
# ############################################################
suite "Batch Operations on Banderwagon":
## Tests if the Batch Affine operations are
## consistent with the signular affine operation
## Using the concept of point double from generator point
## we try to achive this
test "BatchAffine and fromAffine Consistency":
proc testbatch(n: static int) =
var g, temp {.noInit.}: EC
g.fromAffine(generator) # setting the generator point
var aff{.noInit.}: ECP_TwEdwards_Aff[Fp[Banderwagon]]
aff = generator
var points_prj: array[n, EC]
var points_aff: array[n, ECP_TwEdwards_Aff[Fp[Banderwagon]]]
for i in 0 ..< n:
points_prj[i] = g
g.double() # doubling the point
points_aff.batchAffine(points_prj) # performs the batch operation
# checking correspondence with singular affine conversion
for i in 0 ..< n:
doAssert (points_aff[i] == aff).bool(), "batch inconsistent with singular ops"
temp.fromAffine(aff)
temp.double()
aff.affine(temp)
testbatch(1000)
## Tests to check if the Motgomery Batch Inversion
## Check if the Batch Inversion is consistent with
## it's respective sigular inversion operation of field elements
test "Batch Inversion":
proc batchInvert(n: static int) =
var one, two: EC
var arr_fp: array[n, Fp[Banderwagon]] # array for Fp field elements
one.fromAffine(generator) # setting the 1st generator point
two.fromAffine(generator) # setting the 2nd generator point
for i in 0 ..< n:
arr_fp[i] = one.x
one.double()
var arr_fp_inv: array[n, Fp[Banderwagon]]
doAssert arr_fp_inv.batchInvert(arr_fp) == true
# Checking the correspondence with singular element inversion
for i in 0 ..< n:
var temp: Fp[Banderwagon]
temp.inv(two.x)
doAssert (arr_fp_inv[i] == temp).bool(), "Batch Inversion in consistent"
two.double()
batchInvert(10)
## Tests to check if the Batch Map to Scalar Field
## is consistent with it's respective singular operation
## of mapping from Fp to Fr
## Using the concept of point double from generator point
## we try to achive this
test "Testing Batch Map to Base Field":
proc testBatchMapToBaseField() =
var A, B, g: EC
g.fromAffine(generator)
A.sum(g, g)
B.double(g)
B.double()
var expected_a, expected_b: Fr[Banderwagon]
expected_a.mapToScalarField(A)
expected_b.mapToScalarField(B)
var ARes, BRes: Fr[Banderwagon]
var scalars: array[2, Fr[Banderwagon]] = [ARes, BRes]
var fps: array[2, EC] = [A, B]
doAssert scalars.batchMapToScalarField(fps), "Batch Map to Scalar Failed"
doAssert (expected_a == scalars[0]).bool(), "expected scalar for point `A` is incorrect"
doAssert (expected_b == scalars[1]).bool(), "expected scalar for point `B` is incorrect"
testBatchMapToBaseField()