From 9e463f5c2b1ecd78a757b714114e35f454e43063 Mon Sep 17 00:00:00 2001 From: Advaita Saha Date: Mon, 25 Sep 2023 02:44:41 +0530 Subject: [PATCH 1/7] feat: MapToScalarField added for Banderwagon points --- constantine/ethereum_verkle_primitives.nim | 35 ++++++++++++++++++++++ tests/t_banderwagon.nim | 26 +++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 constantine/ethereum_verkle_primitives.nim diff --git a/constantine/ethereum_verkle_primitives.nim b/constantine/ethereum_verkle_primitives.nim new file mode 100644 index 000000000..42cbe4bd4 --- /dev/null +++ b/constantine/ethereum_verkle_primitives.nim @@ -0,0 +1,35 @@ +# 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, + ./math/io/[io_bigints, io_fields], + ./curves_primitives + + +func mapToBaseField*(p: ECP_TwEdwards_Prj[Fp[Banderwagon]]): Fp[Banderwagon] = + var invY: Fp[Banderwagon] + invY.inv(p.y) + result.prod(p.x, invY) + +func MapToScalarField*(res: var Fr[Banderwagon], p: ECP_TwEdwards_Prj[Fp[Banderwagon]]): bool {.discardable.} = + var baseField: Fp[Banderwagon] = p.mapToBaseField() + var baseFieldBytes: array[32, byte] + + let check1 = baseFieldBytes.marshalBE(baseField) + let check2 = res.unmarshalBE(baseFieldBytes) + + return check1 and check2 \ No newline at end of file diff --git a/tests/t_banderwagon.nim b/tests/t_banderwagon.nim index 9c48d07c8..62286299f 100644 --- a/tests/t_banderwagon.nim +++ b/tests/t_banderwagon.nim @@ -20,7 +20,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 +71,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 +213,21 @@ suite "Banderwagon Points Tests": testTwoTorsion() +suite "Banderwagon Elements Mapping": + test "Testing Multi Map To Base Field": + proc testMultiMapToBaseField() = + var A, B, genPoint {.noInit.}: EC + genPoint.fromAffine(generator) + + A.sum(genPoint, genPoint) + B.double(genPoint) + B.double() + + var expected_a, expected_b: Fr[Banderwagon] + 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() \ No newline at end of file From 521e294ce388f59163028a71b57622536b4e5f41 Mon Sep 17 00:00:00 2001 From: Advaita Saha Date: Sat, 30 Sep 2023 03:36:33 +0530 Subject: [PATCH 2/7] fix: syntax + NRVO --- constantine/ethereum_verkle_primitives.nim | 9 +++++---- tests/t_banderwagon.nim | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/constantine/ethereum_verkle_primitives.nim b/constantine/ethereum_verkle_primitives.nim index 42cbe4bd4..0f878f529 100644 --- a/constantine/ethereum_verkle_primitives.nim +++ b/constantine/ethereum_verkle_primitives.nim @@ -20,13 +20,14 @@ import ./curves_primitives -func mapToBaseField*(p: ECP_TwEdwards_Prj[Fp[Banderwagon]]): Fp[Banderwagon] = +func mapToBaseField*(dst: var Fp[Banderwagon],p: ECP_TwEdwards_Prj[Fp[Banderwagon]]) = var invY: Fp[Banderwagon] invY.inv(p.y) - result.prod(p.x, invY) + dst.prod(p.x, invY) -func MapToScalarField*(res: var Fr[Banderwagon], p: ECP_TwEdwards_Prj[Fp[Banderwagon]]): bool {.discardable.} = - var baseField: Fp[Banderwagon] = p.mapToBaseField() +func mapToScalarField*(res: var Fr[Banderwagon], p: ECP_TwEdwards_Prj[Fp[Banderwagon]]): bool {.discardable.} = + var baseField: Fp[Banderwagon] + baseField.mapToBaseField(p) var baseFieldBytes: array[32, byte] let check1 = baseFieldBytes.marshalBE(baseField) diff --git a/tests/t_banderwagon.nim b/tests/t_banderwagon.nim index 62286299f..f471b68a5 100644 --- a/tests/t_banderwagon.nim +++ b/tests/t_banderwagon.nim @@ -224,8 +224,8 @@ suite "Banderwagon Elements Mapping": B.double() var expected_a, expected_b: Fr[Banderwagon] - expected_a.MapToScalarField(A) - expected_b.MapToScalarField(B) + 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" From cd8d0c08ec3bf732b91314c05dcd277db5f43964 Mon Sep 17 00:00:00 2001 From: Advaita Saha Date: Sat, 30 Sep 2023 04:13:32 +0530 Subject: [PATCH 3/7] fix: comments added for function & spec files linked --- constantine/ethereum_verkle_primitives.nim | 24 +++++++++++++++++----- tests/t_banderwagon.nim | 17 ++++++++++++--- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/constantine/ethereum_verkle_primitives.nim b/constantine/ethereum_verkle_primitives.nim index 0f878f529..7431a5b75 100644 --- a/constantine/ethereum_verkle_primitives.nim +++ b/constantine/ethereum_verkle_primitives.nim @@ -21,16 +21,30 @@ import 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) - dst.prod(p.x, invY) + 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] - baseField.mapToBaseField(p) var baseFieldBytes: array[32, byte] + + baseField.mapToBaseField(p) # compute the defined mapping - let check1 = baseFieldBytes.marshalBE(baseField) - let check2 = res.unmarshalBE(baseFieldBytes) + let check1 = baseFieldBytes.marshalBE(baseField) # Fp -> bytes + let check2 = res.unmarshalBE(baseFieldBytes) # bytes -> Fr return check1 and check2 \ No newline at end of file diff --git a/tests/t_banderwagon.nim b/tests/t_banderwagon.nim index f471b68a5..61867926d 100644 --- a/tests/t_banderwagon.nim +++ b/tests/t_banderwagon.nim @@ -213,17 +213,28 @@ 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 Multi Map To Base Field": proc testMultiMapToBaseField() = var A, B, genPoint {.noInit.}: EC genPoint.fromAffine(generator) - A.sum(genPoint, genPoint) - B.double(genPoint) - B.double() + 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) From 1f6832c2e4f3ae1bd37b1f6a7fcf724cc41a95e0 Mon Sep 17 00:00:00 2001 From: Advaita Saha Date: Mon, 2 Oct 2023 02:48:21 +0530 Subject: [PATCH 4/7] feat: batchAffine + batchInversion --- .../elliptic/ec_twistededwards_batch_ops.nim | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 constantine/math/elliptic/ec_twistededwards_batch_ops.nim diff --git a/constantine/math/elliptic/ec_twistededwards_batch_ops.nim b/constantine/math/elliptic/ec_twistededwards_batch_ops.nim new file mode 100644 index 000000000..0d0cff5ef --- /dev/null +++ b/constantine/math/elliptic/ec_twistededwards_batch_ops.nim @@ -0,0 +1,113 @@ +# 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_vartime*[N: static int, F](elements: var array[N, F]) = + # TODO: optimize + var res: array[N, F] + var zeros: array[N, bool] + var accumulator: F + accumulator.setOne() + + for i in 0 ..< N: + if elements[i].isZero().bool(): + zeros[i] = true + continue + + res[i] = accumulator + accumulator *= elements[i] + + accumulator.inv_vartime() + + for i in countdown(N-1, 0): + if zeros[i] == true: + continue + res[i] *= accumulator + accumulator *= elements[i] + + elements = res \ No newline at end of file From c757957dd2a006186884bd6545ca226676220f70 Mon Sep 17 00:00:00 2001 From: Advaita Saha Date: Mon, 2 Oct 2023 02:48:52 +0530 Subject: [PATCH 5/7] feat: batchMapToScalarField + tests --- constantine/ethereum_verkle_primitives.nim | 29 +++++++- tests/t_banderwagon.nim | 79 +++++++++++++++++++++- 2 files changed, 103 insertions(+), 5 deletions(-) diff --git a/constantine/ethereum_verkle_primitives.nim b/constantine/ethereum_verkle_primitives.nim index 7431a5b75..8456a5b65 100644 --- a/constantine/ethereum_verkle_primitives.nim +++ b/constantine/ethereum_verkle_primitives.nim @@ -15,7 +15,10 @@ import ./math/config/[type_ff, curves], ./math/arithmetic, - ./math/elliptic/ec_twistededwards_projective, + ./math/elliptic/[ + ec_twistededwards_projective, + ec_twistededwards_batch_ops + ], ./math/io/[io_bigints, io_fields], ./curves_primitives @@ -47,4 +50,26 @@ func mapToScalarField*(res: var Fr[Banderwagon], p: ECP_TwEdwards_Prj[Fp[Banderw let check1 = baseFieldBytes.marshalBE(baseField) # Fp -> bytes let check2 = res.unmarshalBE(baseFieldBytes) # bytes -> Fr - return check1 and check2 \ No newline at end of file + return check1 and check2 + +func batchMapToScalarField*[N: static int]( + res: var array[N, Fr[Banderwagon]], + points: array[N, ECP_TwEdwards_Prj[Fp[Banderwagon]]]): bool {.discardable.} = + + var ys: array[N, Fp[Banderwagon]] + + for i in 0 ..< N: + ys[i] = points[i].y + + ys.batchInvert_vartime() + + var check: bool + for i in 0 ..< N: + var mappedElement: Fp[Banderwagon] + var bytes: array[32, byte] + + mappedElement.prod(points[i].x, ys[i]) + check = bytes.marshalBE(mappedElement) + check = check and res[i].unmarshalBE(bytes) + + return check \ No newline at end of file diff --git a/tests/t_banderwagon.nim b/tests/t_banderwagon.nim index 61867926d..de766a822 100644 --- a/tests/t_banderwagon.nim +++ b/tests/t_banderwagon.nim @@ -11,7 +11,8 @@ import ../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/[ @@ -222,7 +223,7 @@ suite "Banderwagon Elements Mapping": ## Tests if the mapping from Fp to Fr ## is working as expected or not - test "Testing Multi Map To Base Field": + test "Testing Map To Base Field": proc testMultiMapToBaseField() = var A, B, genPoint {.noInit.}: EC genPoint.fromAffine(generator) @@ -241,4 +242,76 @@ suite "Banderwagon Elements Mapping": 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() \ No newline at end of file + testMultiMapToBaseField() + + 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() + + # TODO: shift to appropiate file + test "BatchAffine and fromAffine Consistency": + proc testbatch(n: static int) = + var g, temp {.noInit.}: EC + g.fromAffine(generator) + 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() + + points_aff.batchAffine(points_prj) + + 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) + + test "Batch Inversion": + proc batchInvert(n: static int) = + var one, two: EC + var arr_fp: array[n, Fp[Banderwagon]] + + one.fromAffine(generator) + two.fromAffine(generator) + + for i in 0 ..< n: + arr_fp[i] = one.x + one.double() + + arr_fp.batchInvert_vartime() + + for i in 0 ..< n: + var temp: Fp[Banderwagon] + temp.inv(two.x) + doAssert (arr_fp[i] == temp).bool(), "Batch Inversion in consistent" + two.double() + + + batchInvert(100) \ No newline at end of file From 08a3e4cbfabbd2a4432ac229c73982ca3e68d013 Mon Sep 17 00:00:00 2001 From: Advaita Saha Date: Mon, 2 Oct 2023 04:09:06 +0530 Subject: [PATCH 6/7] fix: comments and formatting --- constantine/ethereum_verkle_primitives.nim | 16 +++- .../elliptic/ec_twistededwards_batch_ops.nim | 8 +- tests/t_banderwagon.nim | 83 ++++++++++++------- 3 files changed, 71 insertions(+), 36 deletions(-) diff --git a/constantine/ethereum_verkle_primitives.nim b/constantine/ethereum_verkle_primitives.nim index 8456a5b65..87a2e5657 100644 --- a/constantine/ethereum_verkle_primitives.nim +++ b/constantine/ethereum_verkle_primitives.nim @@ -52,10 +52,24 @@ func mapToScalarField*(res: var Fr[Banderwagon], p: ECP_TwEdwards_Prj[Fp[Banderw return check1 and check2 +## ############################################################ +## +## Batch Operations +## +## ############################################################ func batchMapToScalarField*[N: static int]( res: var array[N, Fr[Banderwagon]], points: array[N, 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 ys: array[N, Fp[Banderwagon]] for i in 0 ..< N: diff --git a/constantine/math/elliptic/ec_twistededwards_batch_ops.nim b/constantine/math/elliptic/ec_twistededwards_batch_ops.nim index 0d0cff5ef..518150359 100644 --- a/constantine/math/elliptic/ec_twistededwards_batch_ops.nim +++ b/constantine/math/elliptic/ec_twistededwards_batch_ops.nim @@ -88,11 +88,13 @@ func batchAffine*[M, N: static int, F]( batchAffine(affs[0].asUnchecked(), projs[0].asUnchecked(), M*N) func batchInvert_vartime*[N: static int, F](elements: var array[N, F]) = - # TODO: optimize + ## Montgomery's batch inversion + ## This function person the inversion in-place var res: array[N, F] var zeros: array[N, bool] + var accumulator: F - accumulator.setOne() + accumulator.setOne() # sets the accumulator to 1 for i in 0 ..< N: if elements[i].isZero().bool(): @@ -102,7 +104,7 @@ func batchInvert_vartime*[N: static int, F](elements: var array[N, F]) = res[i] = accumulator accumulator *= elements[i] - accumulator.inv_vartime() + accumulator.inv_vartime() # inversion of the accumulator for i in countdown(N-1, 0): if zeros[i] == true: diff --git a/tests/t_banderwagon.nim b/tests/t_banderwagon.nim index de766a822..1b707bbc2 100644 --- a/tests/t_banderwagon.nim +++ b/tests/t_banderwagon.nim @@ -244,35 +244,22 @@ suite "Banderwagon Elements Mapping": testMultiMapToBaseField() - 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() +# ############################################################ +# +# Banderwagon Batch Operations +# +# ############################################################ +suite "Batch Operations on Banderwagon": - # TODO: shift to appropiate file + ## 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) + g.fromAffine(generator) # setting the generator point + var aff{.noInit.}: ECP_TwEdwards_Aff[Fp[Banderwagon]] aff = generator @@ -281,10 +268,11 @@ suite "Banderwagon Elements Mapping": for i in 0 ..< n: points_prj[i] = g - g.double() - - points_aff.batchAffine(points_prj) + 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) @@ -293,13 +281,16 @@ suite "Banderwagon Elements Mapping": 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]] + var arr_fp: array[n, Fp[Banderwagon]] # array for Fp field elements - one.fromAffine(generator) - two.fromAffine(generator) + 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 @@ -307,11 +298,39 @@ suite "Banderwagon Elements Mapping": arr_fp.batchInvert_vartime() + # Checking the correspondence with singular element inversion for i in 0 ..< n: var temp: Fp[Banderwagon] temp.inv(two.x) doAssert (arr_fp[i] == temp).bool(), "Batch Inversion in consistent" two.double() + batchInvert(1000) + + ## 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" - batchInvert(100) \ No newline at end of file + testBatchMapToBaseField() \ No newline at end of file From 50c71d865c72e8b689a602755d33fbe137adb98c Mon Sep 17 00:00:00 2001 From: Advaita Saha Date: Fri, 6 Oct 2023 01:05:37 +0530 Subject: [PATCH 7/7] fix: static to openArray changed --- constantine.nimble | 1 + constantine/ethereum_verkle_primitives.nim | 19 +++++++++------ .../elliptic/ec_twistededwards_batch_ops.nim | 24 ++++++++++++------- ...n.nim => t_ethereum_verkle_primitives.nim} | 13 +++++++--- 4 files changed, 39 insertions(+), 18 deletions(-) rename tests/{t_banderwagon.nim => t_ethereum_verkle_primitives.nim} (97%) diff --git a/constantine.nimble b/constantine.nimble index e1b030791..ae7d24916 100644 --- a/constantine.nimble +++ b/constantine.nimble @@ -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) ] const testDescNvidia: seq[string] = @[ diff --git a/constantine/ethereum_verkle_primitives.nim b/constantine/ethereum_verkle_primitives.nim index 87a2e5657..043210353 100644 --- a/constantine/ethereum_verkle_primitives.nim +++ b/constantine/ethereum_verkle_primitives.nim @@ -57,9 +57,9 @@ func mapToScalarField*(res: var Fr[Banderwagon], p: ECP_TwEdwards_Prj[Fp[Banderw ## Batch Operations ## ## ############################################################ -func batchMapToScalarField*[N: static int]( - res: var array[N, Fr[Banderwagon]], - points: array[N, ECP_TwEdwards_Prj[Fp[Banderwagon]]]): bool {.discardable.} = +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 ## @@ -70,19 +70,24 @@ func batchMapToScalarField*[N: static int]( ## ## Spec : https://hackmd.io/wliPP_RMT4emsucVuCqfHA?view#MapToFieldElement - var ys: array[N, Fp[Banderwagon]] + 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.batchInvert_vartime() + ys_inv.batchInvert(ys, N) - var check: bool for i in 0 ..< N: var mappedElement: Fp[Banderwagon] var bytes: array[32, byte] - mappedElement.prod(points[i].x, ys[i]) + mappedElement.prod(points[i].x, ys_inv[i]) check = bytes.marshalBE(mappedElement) check = check and res[i].unmarshalBE(bytes) diff --git a/constantine/math/elliptic/ec_twistededwards_batch_ops.nim b/constantine/math/elliptic/ec_twistededwards_batch_ops.nim index 518150359..cdcf684a3 100644 --- a/constantine/math/elliptic/ec_twistededwards_batch_ops.nim +++ b/constantine/math/elliptic/ec_twistededwards_batch_ops.nim @@ -87,11 +87,14 @@ func batchAffine*[M, N: static int, F]( projs: array[M, array[N, ECP_TwEdwards_Prj[F]]]) {.inline.} = batchAffine(affs[0].asUnchecked(), projs[0].asUnchecked(), M*N) -func batchInvert_vartime*[N: static int, F](elements: var array[N, F]) = +func batchInvert*[F]( + dst: ptr UncheckedArray[F], + elements: ptr UncheckedArray[F], + N: int + ) {.noInline.} = ## Montgomery's batch inversion - ## This function person the inversion in-place - var res: array[N, F] - var zeros: array[N, bool] + var zeros = allocStackArray(bool, N) + zeroMem(zeros, N) var accumulator: F accumulator.setOne() # sets the accumulator to 1 @@ -101,15 +104,20 @@ func batchInvert_vartime*[N: static int, F](elements: var array[N, F]) = zeros[i] = true continue - res[i] = accumulator + dst[i] = accumulator accumulator *= elements[i] - accumulator.inv_vartime() # inversion of the accumulator + accumulator.inv() # inversion of the accumulator for i in countdown(N-1, 0): if zeros[i] == true: continue - res[i] *= accumulator + dst[i] *= accumulator accumulator *= elements[i] - elements = res \ No newline at end of file +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 diff --git a/tests/t_banderwagon.nim b/tests/t_ethereum_verkle_primitives.nim similarity index 97% rename from tests/t_banderwagon.nim rename to tests/t_ethereum_verkle_primitives.nim index 1b707bbc2..2cb73f6fa 100644 --- a/tests/t_banderwagon.nim +++ b/tests/t_ethereum_verkle_primitives.nim @@ -6,6 +6,12 @@ # * 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], @@ -296,16 +302,17 @@ suite "Batch Operations on Banderwagon": arr_fp[i] = one.x one.double() - arr_fp.batchInvert_vartime() + 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[i] == temp).bool(), "Batch Inversion in consistent" + doAssert (arr_fp_inv[i] == temp).bool(), "Batch Inversion in consistent" two.double() - batchInvert(1000) + batchInvert(10) ## Tests to check if the Batch Map to Scalar Field ## is consistent with it's respective singular operation