`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:
parent
0f9b9e9606
commit
c97036d1df
|
@ -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),
|
||||
]
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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()
|
Loading…
Reference in New Issue