Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MapToScalarField() added for Banderwagon points #278

Merged
merged 8 commits into from
Oct 6, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions constantine/ethereum_verkle_primitives.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# 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*[N: static int](
res: var array[N, Fr[Banderwagon]],
points: array[N, ECP_TwEdwards_Prj[Fp[Banderwagon]]]): bool {.discardable.} =
advaita-saha marked this conversation as resolved.
Show resolved Hide resolved
## 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]]
advaita-saha marked this conversation as resolved.
Show resolved Hide resolved

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
115 changes: 115 additions & 0 deletions constantine/math/elliptic/ec_twistededwards_batch_ops.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# 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]) =
advaita-saha marked this conversation as resolved.
Show resolved Hide resolved
## Montgomery's batch inversion
## This function person the inversion in-place
var res: array[N, F]
var zeros: array[N, bool]
advaita-saha marked this conversation as resolved.
Show resolved Hide resolved

var accumulator: F
accumulator.setOne() # sets the accumulator to 1

for i in 0 ..< N:
if elements[i].isZero().bool():
zeros[i] = true
continue

res[i] = accumulator
accumulator *= elements[i]

accumulator.inv_vartime() # inversion of the accumulator

for i in countdown(N-1, 0):
if zeros[i] == true:
continue
res[i] *= accumulator
accumulator *= elements[i]

elements = res
131 changes: 129 additions & 2 deletions tests/t_banderwagon.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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/[
Expand All @@ -20,7 +21,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]]
Expand Down Expand Up @@ -70,6 +72,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
Expand Down Expand Up @@ -207,3 +214,123 @@ 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()

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"

testBatchMapToBaseField()
Loading