mirror of
https://github.com/logos-storage/constantine.git
synced 2026-01-06 23:23:13 +00:00
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_bls_signatures.nim", false),
|
||||||
("tests/t_ethereum_eip2333_bls12381_key_derivation.nim", false),
|
("tests/t_ethereum_eip2333_bls12381_key_derivation.nim", false),
|
||||||
("tests/t_ethereum_eip4844_deneb_kzg.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),
|
("tests/t_ethereum_eip4844_deneb_kzg_parallel.nim", false),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
94
constantine/ethereum_verkle_primitives.nim
Normal file
94
constantine/ethereum_verkle_primitives.nim
Normal 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
|
||||||
123
constantine/math/elliptic/ec_twistededwards_batch_ops.nim
Normal file
123
constantine/math/elliptic/ec_twistededwards_batch_ops.nim
Normal 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
|
||||||
@ -6,12 +6,19 @@
|
|||||||
# * 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.
|
||||||
|
|
||||||
|
# ############################################################
|
||||||
|
#
|
||||||
|
# Ethereum Verkle Primitves Tests
|
||||||
|
#
|
||||||
|
# ############################################################
|
||||||
|
|
||||||
import
|
import
|
||||||
std/unittest,
|
std/unittest,
|
||||||
../constantine/math/config/[type_ff, curves],
|
../constantine/math/config/[type_ff, curves],
|
||||||
../constantine/math/elliptic/[
|
../constantine/math/elliptic/[
|
||||||
ec_twistededwards_affine,
|
ec_twistededwards_affine,
|
||||||
ec_twistededwards_projective
|
ec_twistededwards_projective,
|
||||||
|
ec_twistededwards_batch_ops
|
||||||
],
|
],
|
||||||
../constantine/math/io/io_fields,
|
../constantine/math/io/io_fields,
|
||||||
../constantine/serialization/[
|
../constantine/serialization/[
|
||||||
@ -20,7 +27,8 @@ import
|
|||||||
codecs
|
codecs
|
||||||
],
|
],
|
||||||
../constantine/math/arithmetic,
|
../constantine/math/arithmetic,
|
||||||
../constantine/math/constants/zoo_generators
|
../constantine/math/constants/zoo_generators,
|
||||||
|
../constantine/ethereum_verkle_primitives
|
||||||
|
|
||||||
type
|
type
|
||||||
EC* = ECP_TwEdwards_Prj[Fp[Banderwagon]]
|
EC* = ECP_TwEdwards_Prj[Fp[Banderwagon]]
|
||||||
@ -70,6 +78,11 @@ const bad_bit_string: array[16, string] = [
|
|||||||
"0x120faa1df94d5d831bbb69fc44816e25afd27288a333299ac3c94518fd0e016f",
|
"0x120faa1df94d5d831bbb69fc44816e25afd27288a333299ac3c94518fd0e016f",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const expected_scalar_field_elements: array[2, string] = [
|
||||||
|
"0x0e0c604381ef3cd11bdc84e8faa59b542fbbc92f800ed5767f21e5dbc59840ce",
|
||||||
|
"0x0a21f7dfa8ddaf6ef6f2044f13feec50cbb963996112fa1de4e3f52dbf6b7b6d"
|
||||||
|
] # test data generated from go-ipa implementation
|
||||||
|
|
||||||
# ############################################################
|
# ############################################################
|
||||||
#
|
#
|
||||||
# Banderwagon Serialization Tests
|
# Banderwagon Serialization Tests
|
||||||
@ -207,3 +220,124 @@ suite "Banderwagon Points Tests":
|
|||||||
|
|
||||||
testTwoTorsion()
|
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…
x
Reference in New Issue
Block a user