From 646df7e6d1388e8f4f577c8c1b1db1f15480fe63 Mon Sep 17 00:00:00 2001 From: Kristin Kirkov Date: Mon, 5 Dec 2022 16:23:42 +0200 Subject: [PATCH] feat(bncurve): Refactor `nim-bncurve` by separating `randomGeneration` --- bncurve/arith.nim | 27 +- bncurve/fields.nim | 4 +- bncurve/fp.nim | 13 +- bncurve/fq12.nim | 11 +- bncurve/fq2.nim | 11 +- bncurve/fq6.nim | 13 +- bncurve/group_operations.nim | 503 ++++++++++++++++++++++++++++++++ bncurve/groups.nim | 533 +--------------------------------- bncurve/random_generation.nim | 50 ++++ bncurve/types.nim | 47 +++ tests/tarith.nim | 2 +- tests/tfields.nim | 2 +- tests/tvectors.nim | 2 +- 13 files changed, 614 insertions(+), 604 deletions(-) create mode 100644 bncurve/group_operations.nim create mode 100644 bncurve/random_generation.nim create mode 100644 bncurve/types.nim diff --git a/bncurve/arith.nim b/bncurve/arith.nim index ffaf167..41c972a 100644 --- a/bncurve/arith.nim +++ b/bncurve/arith.nim @@ -6,24 +6,12 @@ # at your option. # This file may not be copied, modified, or distributed except according to # those terms. -import options, endians -import nimcrypto/[utils, sysrand] -export options +import options, endians, types +import nimcrypto/utils +export options, types {.deadCodeElim: on.} -type - BNU256* = array[4, uint64] - BNU512* = array[8, uint64] - -proc setRandom*(a: var BNU512) {.inline.} = - ## Set value of integer ``a`` to random value. - let ret = randomBytes(a) - doAssert(ret == 8) - -proc random*(t: typedesc[BNU512]): BNU512 {.inline, noinit.} = - ## Return random 512bit integer. - setRandom(result) proc setZero*(a: var BNU256) {.inline.} = ## Set value of integer ``a`` to zero. @@ -364,15 +352,6 @@ proc `$`*(src: BNU512): string = ## Return hexadecimal string representation of integer ``src``. result = toString(src, false) -proc setRandom*(a: var BNU256, modulo: BNU256) {.noinit, inline.} = - ## Set value of integer ``a`` to random value (mod ``modulo``). - var r = BNU512.random() - discard divrem(r, modulo, a) - -proc random*(t: typedesc[BNU256], modulo: BNU256): BNU256 {.noinit, inline.} = - ## Return random 256bit integer (mod ``modulo``). - result.setRandom(modulo) - proc invert*(a: var BNU256, modulo: BNU256) = ## Turn integer ``a`` into its multiplicative inverse (mod ``modulo``). var u = a diff --git a/bncurve/fields.nim b/bncurve/fields.nim index 945bafe..86f9c4d 100644 --- a/bncurve/fields.nim +++ b/bncurve/fields.nim @@ -6,5 +6,5 @@ # at your option. # This file may not be copied, modified, or distributed except according to # those terms. -import options, arith, fp, fq2, fq6, fq12 -export options, arith, fp, fq2, fq6, fq12 +import options, arith, fp, fq2, fq6, fq12, types +export options, arith, fp, fq2, fq6, fq12, types diff --git a/bncurve/fp.nim b/bncurve/fp.nim index a23bd57..51d2815 100644 --- a/bncurve/fp.nim +++ b/bncurve/fp.nim @@ -6,13 +6,11 @@ # at your option. # This file may not be copied, modified, or distributed except according to # those terms. -import arith, options - +import options, arith, types {.deadCodeElim: on.} template fieldImplementation(finame, fimodulus, firsquared, fircubed, fionep, fiinv: untyped): untyped {.dirty.} = - type finame* = distinct BNU256 proc setZero*(dst: var finame) {.noinit, inline.} = ## Set ``zero`` representation in Fp to ``dst``. @@ -38,15 +36,6 @@ template fieldImplementation(finame, fimodulus, firsquared, fircubed, ## Return ``Fp`` modulus. result = fimodulus - proc setRandom*(dst: var finame) {.noinit, inline.} = - ## Set ``dst`` to random value - var a = BNU256.random(fimodulus) - dst = finame(a) - - proc random*(t: typedesc[finame]): finame {.noinit, inline.} = - ## Return random ``Fp``. - result.setRandom() - proc `+`*(x, y: finame): finame {.noinit, inline.} = ## Return result of ``x + y``. result = x diff --git a/bncurve/fq12.nim b/bncurve/fq12.nim index 97756f3..a5d10fd 100644 --- a/bncurve/fq12.nim +++ b/bncurve/fq12.nim @@ -7,7 +7,7 @@ # This file may not be copied, modified, or distributed except according to # those terms. import options -import fq6, fq2, fp, arith +import fq6, fq2, fp, arith, types {.deadCodeElim: on.} @@ -32,11 +32,6 @@ const frobeniusCoeffsC1: array[4, FQ2] = [ ) ] -type - FQ12* = object - c0*: FQ6 - c1*: FQ6 - proc init*(c0, c1: FQ6): FQ12 {.inline, noinit.} = result.c0 = c0 result.c1 = c1 @@ -49,10 +44,6 @@ proc one*(t: typedesc[FQ12]): FQ12 {.inline, noinit.} = result.c0 = FQ6.one() result.c1 = FQ6.zero() -proc random*(t: typedesc[FQ12]): FQ12 {.inline, noinit.} = - result.c0 = FQ6.random() - result.c1 = FQ6.random() - proc isZero*(x: FQ12): bool {.inline, noinit.} = result = (x.c0.isZero() and x.c1.isZero()) diff --git a/bncurve/fq2.nim b/bncurve/fq2.nim index 7bad05f..c9861da 100644 --- a/bncurve/fq2.nim +++ b/bncurve/fq2.nim @@ -7,14 +7,9 @@ # This file may not be copied, modified, or distributed except according to # those terms. import options -import fp, arith - +import fp, arith, types {.deadCodeElim: on.} -type - FQ2* = object - c0*: FQ - c1*: FQ const FQNonResidue = FQ([ @@ -45,10 +40,6 @@ proc one*(t: typedesc[FQ2]): FQ2 {.inline, noinit.} = result.c0 = FQ.one() result.c1 = FQ.zero() -proc random*(t: typedesc[FQ2]): FQ2 {.inline, noinit.} = - result.c0 = FQ.random() - result.c1 = FQ.random() - proc isZero*(x: FQ2): bool {.inline, noinit.} = result = (x.c0.isZero() and x.c1.isZero()) diff --git a/bncurve/fq6.nim b/bncurve/fq6.nim index f74dc2f..51e0fcd 100644 --- a/bncurve/fq6.nim +++ b/bncurve/fq6.nim @@ -7,7 +7,7 @@ # This file may not be copied, modified, or distributed except according to # those terms. import options -import fq2, fp, arith +import fq2, fp, arith, types {.deadCodeElim: on.} @@ -53,12 +53,6 @@ const frobeniusCoeffsC2: array[4, FQ2] = [ ) ] -type - FQ6* = object - c0*: FQ2 - c1*: FQ2 - c2*: FQ2 - proc init*(c0, c1, c2: FQ2): FQ6 {.inline, noinit.} = result.c0 = c0 result.c1 = c1 @@ -74,11 +68,6 @@ proc one*(t: typedesc[FQ6]): FQ6 {.inline, noinit.} = result.c1 = FQ2.zero() result.c2 = FQ2.zero() -proc random*(t: typedesc[FQ6]): FQ6 {.inline, noinit.} = - result.c0 = FQ2.random() - result.c1 = FQ2.random() - result.c2 = FQ2.random() - proc isZero*(x: FQ6): bool {.inline, noinit.} = result = (x.c0.isZero() and x.c1.isZero() and x.c2.isZero()) diff --git a/bncurve/group_operations.nim b/bncurve/group_operations.nim new file mode 100644 index 0000000..e1c624f --- /dev/null +++ b/bncurve/group_operations.nim @@ -0,0 +1,503 @@ +# Nim Barreto-Naehrig pairing-friendly elliptic curve implementation +# Copyright (c) 2018 Status Research & Development GmbH +# Licensed under either of +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) +# at your option. +# This file may not be copied, modified, or distributed except according to +# those terms. +import fields, options +export fields, options +import nimcrypto/utils + +{.deadCodeElim: on.} + +const + G1One = Point[G1]( + x: FQ.one(), + y: FQ([0xa6ba871b8b1e1b3a'u64, 0x14f1d651eb8e167b'u64, + 0xccdd46def0f28c58'u64, 0x1c14ef83340fbe5e'u64]), + z: FQ.one() + ) + + G1B = FQ([0x7a17caa950ad28d7'u64, 0x1f6ac17ae15521b9'u64, + 0x334bea4e696bd284'u64, 0x2a1f6744ce179d8e'u64]) + + G2One = Point[G2]( + x: FQ2( + c0: FQ([0x8e83b5d102bc2026'u64, 0xdceb1935497b0172'u64, + 0xfbb8264797811adf'u64, 0x19573841af96503b'u64]), + c1: FQ([0xafb4737da84c6140'u64, 0x6043dd5a5802d8c4'u64, + 0x09e950fc52a02f86'u64, 0x14fef0833aea7b6b'u64]) + ), + y: FQ2( + c0: FQ([0x619dfa9d886be9f6'u64, 0xfe7fd297f59e9b78'u64, + 0xff9e1a62231b7dfe'u64, 0x28fd7eebae9e4206'u64]), + c1: FQ([0x64095b56c71856ee'u64, 0xdc57f922327d3cbb'u64, + 0x55f935be33351076'u64, 0x0da4a0e693fd6482'u64]) + ), + z: FQ2.one() + ) + + G2B = FQ2( + c0: FQ([0x3bf938e377b802a8'u64, 0x020b1b273633535d'u64, + 0x26b7edf049755260'u64, 0x2514c6324384a86d'u64]), + c1: FQ([0x38e7ecccd1dcff67'u64, 0x65f0b37d93ce0d3e'u64, + 0xd749d0dd22ac00aa'u64, 0x0141b9ce4a688d4d'u64]) + ) + + AteLoopCount = BNU256([ + 0x9d797039be763ba8'u64, 0x0000000000000001'u64, + 0x0000000000000000'u64, 0x0000000000000000'u64 + ]) + + TwoInv = FQ([ + 9781510331150239090'u64, 15059239858463337189'u64, + 10331104244869713732'u64, 2249375503248834476'u64 + ]) + + Twist = FQ2NonResidue + + TwistMulByQx = FQ2( + c0: FQ([ + 13075984984163199792'u64, 3782902503040509012'u64, + 8791150885551868305'u64, 1825854335138010348'u64 + ]), + c1: FQ([ + 7963664994991228759'u64, 12257807996192067905'u64, + 13179524609921305146'u64, 2767831111890561987'u64 + ]) + ) + + TwistMulByQy = FQ2( + c0: FQ([ + 16482010305593259561'u64, 13488546290961988299'u64, + 3578621962720924518'u64, 2681173117283399901'u64 + ]), + c1: FQ([ + 11661927080404088775'u64, 553939530661941723'u64, + 7860678177968807019'u64, 3208568454732775116'u64 + ]) + ) + +proc one*[T: G1|G2](t: typedesc[T]): Point[T] {.inline, noinit.} = + when T is G1: + result = G1One + else: + result = G2One + +# proc one*(t: typedesc[Gt]): Gt {.inline, noinit.} = +# result = FQ12.one() + +proc name*[T: G1|G2](t: typedesc[T]): string {.inline, noinit.} = + when T is G1: + result = "G1" + else: + result = "G2" + +proc coeff*(t: typedesc[G1]): FQ {.inline, noinit.} = + result = G1B + +proc coeff*(t: typedesc[G2]): FQ2 {.inline, noinit.} = + result = G2B + +proc zero*[T: G1|G2](t: typedesc[T]): Point[T] {.inline, noinit.} = + when T is G1: + result.x = FQ.zero() + result.y = FQ.one() + result.z = FQ.zero() + else: + result.x = FQ2.zero() + result.y = FQ2.one() + result.z = FQ2.zero() + +proc isZero*[T: G1|G2](p: Point[T]): bool {.inline, noinit.} = + result = p.z.isZero() + +proc double*[T: G1|G2](p: Point[T]): Point[T] {.noinit.} = + let a = p.x.squared() + let b = p.y.squared() + let c = b.squared() + var d = (p.x + b).squared() - a - c + d = d + d + let e = a + a + a + let f = e.squared() + let x3 = f - (d + d) + var eightc = c + c + eightc = eightc + eightc + eightc = eightc + eightc + let y1z1 = p.y * p.z + result.x = x3 + result.y = e * (d - x3) - eightc + result.z = y1z1 + y1z1 + +proc `*`*[T: G1|G2](p: Point[T], by: FR): Point[T] = + result = T.zero() + var foundOne = false + for i in BNU256.into(by).bits(): + if foundOne: + result = result.double() + if i: + foundOne = true + result = result + p + +proc `+`*[T: G1|G2](p1, p2: Point[T]): Point[T] {.noinit.} = + if p1.isZero(): + return p2 + if p2.isZero(): + return p1 + + let z1squared = p1.z.squared() + let z2squared = p2.z.squared() + let u1 = p1.x * z2squared + let u2 = p2.x * z1squared + let z1cubed = p1.z * z1squared + let z2cubed = p2.z * z2squared + let s1 = p1.y * z2cubed + let s2 = p2.y * z1cubed + + if u1 == u2 and s1 == s2: + result = p1.double() + else: + let h = u2 - u1 + let s2minuss1 = s2 - s1 + let i = (h + h).squared() + let j = h * i + let r = s2minuss1 + s2minuss1 + let v = u1 * i + let s1j = s1 * j + let x3 = r.squared() - j - (v + v) + result.x = x3 + result.y = r * (v - x3) - (s1j + s1j) + result.z = ((p1.z + p2.z).squared() - z1squared - z2squared) * h + +proc `-`*[T: G1|G2](p: Point[T]): Point[T] {.inline, noinit.} = + if p.isZero(): + return p + else: + result.x = p.x + result.y = -p.y + result.z = p.z + +proc `-`*[T: G1|G2](p: AffinePoint[T]): AffinePoint[T] {.inline, noinit.} = + result.x = p.x + result.y = -p.y + +proc `-`*[T: G1|G2](p1, p2: Point[T]): Point[T] {.inline, noinit.} = + result = p1 + (-p2) + +proc `==`*[T: G1|G2](p1, p2: Point[T]): bool = + if p1.isZero(): + return p2.isZero() + if p2.isZero(): + return false + let z1squared = p1.z.squared() + let z2squared = p2.z.squared() + if (p1.x * z2squared) != (p2.x * z1squared): + return false + let z1cubed = p1.z * z1squared + let z2cubed = p2.z * z2squared + if (p1.y * z2cubed) != (p2.y * z1cubed): + return false + return true + +proc toJacobian*[T: G1|G2](p: AffinePoint[T]): Point[T] {.inline, noinit.} = + ## Convert affine coordinates' point ``p`` to point. + result.x = p.x + result.y = p.y + when T is G1: + result.z = FQ.one() + else: + result.z = FQ2.one() + +proc toAffine*[T: G1|G2](p: Point[T]): Option[AffinePoint[T]] = + ## Attempt to convert point ``p`` to affine coordinates. + when T is G1: + var fone = FQ.one() + else: + var fone = FQ2.one() + + if p.z.isZero(): + result = none[AffinePoint[T]]() + elif p.z == fone: + result = some[AffinePoint[T]](AffinePoint[T](x: p.x, y: p.y)) + else: + let ozinv = p.z.inverse() + if isSome(ozinv): + let zinv = ozinv.get() + var zinvsquared = zinv.squared() + result = some[AffinePoint[T]]( + AffinePoint[T]( + x: p.x * zinvsquared, + y: p.y * (zinvsquared * zinv) + ) + ) + else: + result = none[AffinePoint[T]]() + +proc normalize*(p: var Point[G2]) {.inline, noinit.} = + let aopt = p.toAffine() + if isSome(aopt): + p = aopt.get().toJacobian() + else: + return + +proc isOnCurve*[T: G1|G2](p: AffinePoint[T]): bool = + when T is G1: + result = (p.y.squared() == (p.x.squared() * p.x) + G1B) + else: + result = (p.y.squared() == (p.x.squared() * p.x) + G2B) + +proc mulByQ(p: AffinePoint[G2]): AffinePoint[G2] = + result.x = TwistMulByQx * p.x.frobeniusMap(1) + result.y = TwistMulByQy * p.y.frobeniusMap(1) + +proc mixedAdditionStepForFlippedML(p: var Point[G2], + base: AffinePoint[G2]): EllCoeffs = + let d = p.x - p.z * base.x + let e = p.y - p.z * base.y + let f = d.squared() + let g = e.squared() + let h = d * f + let i = p.x * f + let j = p.z * g + h - (i + i) + + p.x = d * j + p.y = e * (i - j) - h * p.y + p.z = p.z * h + + result.ell_0 = Twist * (e * base.x - d * base.y) + result.ell_vv = -e + result.ell_vw = d + +proc doublingStepForFlippedML(p: var Point[G2]): EllCoeffs = + let a = (p.x * p.y).scale(TwoInv) + let b = p.y.squared() + let c = p.z.squared() + let d = c + c + c + let e = G2B * d + let f = e + e + e + let g = (b + f).scale(TwoInv) + let h = (p.y + p.z).squared() - (b + c) + let i = e - b + let j = p.x.squared() + let e_sq = e.squared() + + p.x = a * (b - f) + p.y = g.squared() - (e_sq + e_sq + e_sq) + p.z = b * h + + result.ell_0 = Twist * i + result.ell_vw = -h + result.ell_vv = j + j + j + +proc precompute*(p: AffinePoint[G2]): G2Precomp = + var r = p.toJacobian() + result.coeffs = newSeqOfCap[EllCoeffs](102) + var foundOne = false + + for i in AteLoopCount.bits(): + if not foundOne: + foundOne = i + continue + result.coeffs.add(r.doublingStepForFlippedML()) + if i: + result.coeffs.add(r.mixedAdditionStepForFlippedML(p)) + + let q1 = p.mulByQ() + let q2 = -(q1.mulByQ()) + + result.coeffs.add(r.mixedAdditionStepForFlippedML(q1)) + result.coeffs.add(r.mixedAdditionStepForFlippedML(q2)) + result.q = p + +proc millerLoop*(pc: G2Precomp, g1: AffinePoint[G1]): FQ12 = + result = FQ12.one() + var idx = 0 + var foundOne = false + var c: EllCoeffs + for i in AteLoopCount.bits(): + if not foundOne: + foundOne = i + continue + c = pc.coeffs[idx] + inc(idx) + result = result.squared().mulBy024(c.ell_0, c.ell_vw.scale(g1.y), + c.ell_vv.scale(g1.x)) + if i: + c = pc.coeffs[idx] + idx += 1 + result = result.mulBy024(c.ell_0, c.ell_vw.scale(g1.y), + c.ell_vv.scale(g1.x)) + c = pc.coeffs[idx] + idx += 1 + result = result.mulBy024(c.ell_0, c.ell_vw.scale(g1.y), c.ell_vv.scale(g1.x)) + c = pc.coeffs[idx] + result = result.mulBy024(c.ell_0, c.ell_vw.scale(g1.y), c.ell_vv.scale(g1.x)) + +proc pairing*(p: Point[G1], q: Point[G2]): FQ12 {.noinit, inline.} = + result = FQ12.one() + var optp = p.toAffine() + var optq = q.toAffine() + if optp.isSome() and optq.isSome(): + let pc = optq.get().precompute() + let ores = finalExponentiation(pc.millerLoop(optp.get())) + if ores.isSome(): + result = ores.get() + +proc init*(p: var AffinePoint[G1], x: FQ, y: FQ): bool {.inline.} = + ## Initializes AffinePoint[G1] with coordinates ``x`` and ``y``. + ## Returns ``true`` if (x, y) is on curve and in the subgroup. + if y.squared() == ((x.squared() * x) + G1B): + let point = Point[G1](x: x, y: y, z: FQ.one()) + if (point * (-FR.one())) + point == G1.zero(): + p.x = x + p.y = y + result = true + +proc init*(p: var AffinePoint[G2], x: FQ2, y: FQ2): bool {.inline.} = + ## Initializes AffinePoint[G2] with coordinates ``x`` and ``y``. + ## Returns ``true`` if (x, y) is on curve and in the subgroup. + if y.squared() == ((x.squared() * x) + G2B): + let point = Point[G2](x: x, y: y, z: FQ2.one()) + if (point * (-FR.one())) + point == G2.zero(): + p.x = x + p.y = y + result = true + +proc toBytes*[T: G1|G2](p: AffinePoint[T], dst: var openArray[byte]): bool = + ## Encode affine point coordinates (x, y) to big-endian bytes representation + ## ``dst``. + ## Returns ``true`` if coordinates was successfully serialized, ``false`` + ## otherwise. + when T is G1: + if len(dst) >= 64: + if p.x.toBytes(toOpenArray(dst, 0, 31)): + if p.y.toBytes(toOpenArray(dst, 32, 63)): + result = true + else: + if len(dst) >= 128: + if p.x.toBytes(toOpenArray(dst, 0, 63)): + if p.y.toBytes(toOpenArray(dst, 64, 127)): + result = true + +proc fromBytes*[T: G1|G2](p: var AffinePoint[T], src: openArray[byte]): bool = + ## Decode affine point coordinates (x, y) from big endian bytes representation + ## ``src``. + ## Returns ``true`` if coordinates was successfully serialized, ``false`` + ## otherwise. + when T is G1: + const + nextOffset = 32 + coeff = G1B + var + x, y: FQ + point: Point[G1] + else: + const + nextOffset = 64 + coeff = G2B + var + x, y: FQ2 + point: Point[G2] + + if len(src) >= nextOffset * 2: + if x.fromBytes(src): + if y.fromBytes(toOpenArray(src, nextOffset, len(src) - 1)): + if y.squared() == (x.squared() * x) + coeff: + ## Check if point on curve. + point.x = x + point.y = y + when T is G1: + point.z = FQ.one() + else: + point.z = FQ2.one() + if (point * (-FR.one())) + point == T.zero(): + p.x = x + p.y = y + result = true + else: + ## Point is not in the subgroup + discard + +proc fromHexString*[T: G1|G2](p: var AffinePoint[T], + src: string): bool {.inline.} = + ## Decode affine point coordinates (x, y) from hexadecimal string + ## representation ``src``. + ## Returns ``true`` if coordinates was successfully serialized, ``false`` + ## otherwise. + ## ``Note:`` Can raise exception on malformed hexadecimal string. + result = fromBytes(p, fromHex(src)) + +proc toHexString*[T: G1|G2](p: AffinePoint[T], + lowercase = false): string {.inline.} = + ## Encode affine point coordinates (x, y) and return hexadecimal string + ## representation. + when T is G1: + var buffer: array[64, byte] + else: + var buffer: array[128, byte] + if toBytes(p, buffer): + result = toHex(buffer, lowercase) + +proc toBytes*[T: G1|G2](p: Point[T], + dst: var openArray[byte]): bool {.inline.} = + ## Encode point coordinates (x, y, z) to big-endian bytes representation + ## ``dst``. + ## Returns ``true`` if coordinates was successfully serialized, ``false`` + ## otherwise. + when T is G1: + const outputSize = 64 + else: + const outputSize = 128 + if p.isZero(): + if len(dst) >= 1: + dst[0] = 0x00'u8 + result = true + else: + result = false + else: + if len(dst) >= 1 + outputSize: + var apo = p.toAffine() + if isSome(apo): + dst[0] = 0x04'u8 + result = apo.get().toBytes(toOpenArray(dst, 1, outputSize)) + +proc fromBytes*[T: G1|G2](p: var Point[T], + src: openArray[byte]): bool {.inline.} = + ## Decode affine point coordinates (x, y, z) from big endian bytes + ## representation ``src``. + ## Returns ``true`` if coordinates was successfully serialized, ``false`` + ## otherwise. + when T is G1: + const inputSize = 64 + else: + const inputSize = 128 + if len(src) > 0: + if src[0] == 0x00'u8: + p = T.zero() + result = true + elif src[0] == 0x04'u8: + if len(src) >= inputSize + 1: + var ap: AffinePoint[T] + if ap.fromBytes(toOpenArray(src, 1, inputSize)): + p = toJacobian(ap) + result = true + +proc fromHexString*[T: G1|G2](p: var Point[T], src: string): bool {.inline.} = + ## Decode point coordinates (x, y, z) from hexadecimal string + ## representation ``src``. + ## Returns ``true`` if coordinates was successfully serialized, ``false`` + ## otherwise. + ## ``Note:`` Can raise exception on malformed hexadecimal string. + result = fromBytes(p, fromHex(src)) + +proc toHexString*[T: G1|G2](p: Point[T], lowercase = false): string {.inline.} = + ## Encode affine point coordinates (x, y, z) and return hexadecimal string + ## representation. + when T is G1: + var buffer: array[64 + 1, byte] + else: + var buffer: array[128 + 1, byte] + if toBytes(p, buffer): + result = toHex(buffer, lowercase) diff --git a/bncurve/groups.nim b/bncurve/groups.nim index 174a9f2..22b4e76 100644 --- a/bncurve/groups.nim +++ b/bncurve/groups.nim @@ -1,531 +1,2 @@ -# Nim Barreto-Naehrig pairing-friendly elliptic curve implementation -# Copyright (c) 2018 Status Research & Development GmbH -# Licensed under either of -# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) -# * MIT license ([LICENSE-MIT](LICENSE-MIT)) -# at your option. -# This file may not be copied, modified, or distributed except according to -# those terms. -import fields, arith, options -export fields, arith, options -import nimcrypto/utils - -{.deadCodeElim: on.} - -type - G1* = object - G2* = object - - Point*[T: G1|G2] = object - when T is G1: - x*, y*, z*: FQ - else: - x*, y*, z*: FQ2 - - AffinePoint*[T: G1|G2] = object - when T is G1: - x*, y*: FQ - else: - x*, y*: FQ2 - - EllCoeffs* = object - ell_0*: FQ2 - ell_vw*: FQ2 - ell_vv*: FQ2 - - G2Precomp* = object - q*: AffinePoint[G2] - coeffs*: seq[EllCoeffs] - -const - G1One = Point[G1]( - x: FQ.one(), - y: FQ([0xa6ba871b8b1e1b3a'u64, 0x14f1d651eb8e167b'u64, - 0xccdd46def0f28c58'u64, 0x1c14ef83340fbe5e'u64]), - z: FQ.one() - ) - - G1B = FQ([0x7a17caa950ad28d7'u64, 0x1f6ac17ae15521b9'u64, - 0x334bea4e696bd284'u64, 0x2a1f6744ce179d8e'u64]) - - G2One = Point[G2]( - x: FQ2( - c0: FQ([0x8e83b5d102bc2026'u64, 0xdceb1935497b0172'u64, - 0xfbb8264797811adf'u64, 0x19573841af96503b'u64]), - c1: FQ([0xafb4737da84c6140'u64, 0x6043dd5a5802d8c4'u64, - 0x09e950fc52a02f86'u64, 0x14fef0833aea7b6b'u64]) - ), - y: FQ2( - c0: FQ([0x619dfa9d886be9f6'u64, 0xfe7fd297f59e9b78'u64, - 0xff9e1a62231b7dfe'u64, 0x28fd7eebae9e4206'u64]), - c1: FQ([0x64095b56c71856ee'u64, 0xdc57f922327d3cbb'u64, - 0x55f935be33351076'u64, 0x0da4a0e693fd6482'u64]) - ), - z: FQ2.one() - ) - - G2B = FQ2( - c0: FQ([0x3bf938e377b802a8'u64, 0x020b1b273633535d'u64, - 0x26b7edf049755260'u64, 0x2514c6324384a86d'u64]), - c1: FQ([0x38e7ecccd1dcff67'u64, 0x65f0b37d93ce0d3e'u64, - 0xd749d0dd22ac00aa'u64, 0x0141b9ce4a688d4d'u64]) - ) - - AteLoopCount = BNU256([ - 0x9d797039be763ba8'u64, 0x0000000000000001'u64, - 0x0000000000000000'u64, 0x0000000000000000'u64 - ]) - - TwoInv = FQ([ - 9781510331150239090'u64, 15059239858463337189'u64, - 10331104244869713732'u64, 2249375503248834476'u64 - ]) - - Twist = FQ2NonResidue - - TwistMulByQx = FQ2( - c0: FQ([ - 13075984984163199792'u64, 3782902503040509012'u64, - 8791150885551868305'u64, 1825854335138010348'u64 - ]), - c1: FQ([ - 7963664994991228759'u64, 12257807996192067905'u64, - 13179524609921305146'u64, 2767831111890561987'u64 - ]) - ) - - TwistMulByQy = FQ2( - c0: FQ([ - 16482010305593259561'u64, 13488546290961988299'u64, - 3578621962720924518'u64, 2681173117283399901'u64 - ]), - c1: FQ([ - 11661927080404088775'u64, 553939530661941723'u64, - 7860678177968807019'u64, 3208568454732775116'u64 - ]) - ) - -proc one*[T: G1|G2](t: typedesc[T]): Point[T] {.inline, noinit.} = - when T is G1: - result = G1One - else: - result = G2One - -# proc one*(t: typedesc[Gt]): Gt {.inline, noinit.} = -# result = FQ12.one() - -proc name*[T: G1|G2](t: typedesc[T]): string {.inline, noinit.} = - when T is G1: - result = "G1" - else: - result = "G2" - -proc coeff*(t: typedesc[G1]): FQ {.inline, noinit.} = - result = G1B - -proc coeff*(t: typedesc[G2]): FQ2 {.inline, noinit.} = - result = G2B - -proc zero*[T: G1|G2](t: typedesc[T]): Point[T] {.inline, noinit.} = - when T is G1: - result.x = FQ.zero() - result.y = FQ.one() - result.z = FQ.zero() - else: - result.x = FQ2.zero() - result.y = FQ2.one() - result.z = FQ2.zero() - -proc isZero*[T: G1|G2](p: Point[T]): bool {.inline, noinit.} = - result = p.z.isZero() - -proc double*[T: G1|G2](p: Point[T]): Point[T] {.noinit.} = - let a = p.x.squared() - let b = p.y.squared() - let c = b.squared() - var d = (p.x + b).squared() - a - c - d = d + d - let e = a + a + a - let f = e.squared() - let x3 = f - (d + d) - var eightc = c + c - eightc = eightc + eightc - eightc = eightc + eightc - let y1z1 = p.y * p.z - result.x = x3 - result.y = e * (d - x3) - eightc - result.z = y1z1 + y1z1 - -proc `*`*[T: G1|G2](p: Point[T], by: FR): Point[T] = - result = T.zero() - var foundOne = false - for i in BNU256.into(by).bits(): - if foundOne: - result = result.double() - if i: - foundOne = true - result = result + p - -proc random*[T: G1|G2](t: typedesc[T]): Point[T] {.inline, noinit.} = - result = t.one() * FR.random() - -proc `+`*[T: G1|G2](p1, p2: Point[T]): Point[T] {.noinit.} = - if p1.isZero(): - return p2 - if p2.isZero(): - return p1 - - let z1squared = p1.z.squared() - let z2squared = p2.z.squared() - let u1 = p1.x * z2squared - let u2 = p2.x * z1squared - let z1cubed = p1.z * z1squared - let z2cubed = p2.z * z2squared - let s1 = p1.y * z2cubed - let s2 = p2.y * z1cubed - - if u1 == u2 and s1 == s2: - result = p1.double() - else: - let h = u2 - u1 - let s2minuss1 = s2 - s1 - let i = (h + h).squared() - let j = h * i - let r = s2minuss1 + s2minuss1 - let v = u1 * i - let s1j = s1 * j - let x3 = r.squared() - j - (v + v) - result.x = x3 - result.y = r * (v - x3) - (s1j + s1j) - result.z = ((p1.z + p2.z).squared() - z1squared - z2squared) * h - -proc `-`*[T: G1|G2](p: Point[T]): Point[T] {.inline, noinit.} = - if p.isZero(): - return p - else: - result.x = p.x - result.y = -p.y - result.z = p.z - -proc `-`*[T: G1|G2](p: AffinePoint[T]): AffinePoint[T] {.inline, noinit.} = - result.x = p.x - result.y = -p.y - -proc `-`*[T: G1|G2](p1, p2: Point[T]): Point[T] {.inline, noinit.} = - result = p1 + (-p2) - -proc `==`*[T: G1|G2](p1, p2: Point[T]): bool = - if p1.isZero(): - return p2.isZero() - if p2.isZero(): - return false - let z1squared = p1.z.squared() - let z2squared = p2.z.squared() - if (p1.x * z2squared) != (p2.x * z1squared): - return false - let z1cubed = p1.z * z1squared - let z2cubed = p2.z * z2squared - if (p1.y * z2cubed) != (p2.y * z1cubed): - return false - return true - -proc toJacobian*[T: G1|G2](p: AffinePoint[T]): Point[T] {.inline, noinit.} = - ## Convert affine coordinates' point ``p`` to point. - result.x = p.x - result.y = p.y - when T is G1: - result.z = FQ.one() - else: - result.z = FQ2.one() - -proc toAffine*[T: G1|G2](p: Point[T]): Option[AffinePoint[T]] = - ## Attempt to convert point ``p`` to affine coordinates. - when T is G1: - var fone = FQ.one() - else: - var fone = FQ2.one() - - if p.z.isZero(): - result = none[AffinePoint[T]]() - elif p.z == fone: - result = some[AffinePoint[T]](AffinePoint[T](x: p.x, y: p.y)) - else: - let ozinv = p.z.inverse() - if isSome(ozinv): - let zinv = ozinv.get() - var zinvsquared = zinv.squared() - result = some[AffinePoint[T]]( - AffinePoint[T]( - x: p.x * zinvsquared, - y: p.y * (zinvsquared * zinv) - ) - ) - else: - result = none[AffinePoint[T]]() - -proc normalize*(p: var Point[G2]) {.inline, noinit.} = - let aopt = p.toAffine() - if isSome(aopt): - p = aopt.get().toJacobian() - else: - return - -proc isOnCurve*[T: G1|G2](p: AffinePoint[T]): bool = - when T is G1: - result = (p.y.squared() == (p.x.squared() * p.x) + G1B) - else: - result = (p.y.squared() == (p.x.squared() * p.x) + G2B) - -proc mulByQ(p: AffinePoint[G2]): AffinePoint[G2] = - result.x = TwistMulByQx * p.x.frobeniusMap(1) - result.y = TwistMulByQy * p.y.frobeniusMap(1) - -proc mixedAdditionStepForFlippedML(p: var Point[G2], - base: AffinePoint[G2]): EllCoeffs = - let d = p.x - p.z * base.x - let e = p.y - p.z * base.y - let f = d.squared() - let g = e.squared() - let h = d * f - let i = p.x * f - let j = p.z * g + h - (i + i) - - p.x = d * j - p.y = e * (i - j) - h * p.y - p.z = p.z * h - - result.ell_0 = Twist * (e * base.x - d * base.y) - result.ell_vv = -e - result.ell_vw = d - -proc doublingStepForFlippedML(p: var Point[G2]): EllCoeffs = - let a = (p.x * p.y).scale(TwoInv) - let b = p.y.squared() - let c = p.z.squared() - let d = c + c + c - let e = G2B * d - let f = e + e + e - let g = (b + f).scale(TwoInv) - let h = (p.y + p.z).squared() - (b + c) - let i = e - b - let j = p.x.squared() - let e_sq = e.squared() - - p.x = a * (b - f) - p.y = g.squared() - (e_sq + e_sq + e_sq) - p.z = b * h - - result.ell_0 = Twist * i - result.ell_vw = -h - result.ell_vv = j + j + j - -proc precompute*(p: AffinePoint[G2]): G2Precomp = - var r = p.toJacobian() - result.coeffs = newSeqOfCap[EllCoeffs](102) - var foundOne = false - - for i in AteLoopCount.bits(): - if not foundOne: - foundOne = i - continue - result.coeffs.add(r.doublingStepForFlippedML()) - if i: - result.coeffs.add(r.mixedAdditionStepForFlippedML(p)) - - let q1 = p.mulByQ() - let q2 = -(q1.mulByQ()) - - result.coeffs.add(r.mixedAdditionStepForFlippedML(q1)) - result.coeffs.add(r.mixedAdditionStepForFlippedML(q2)) - result.q = p - -proc millerLoop*(pc: G2Precomp, g1: AffinePoint[G1]): FQ12 = - result = FQ12.one() - var idx = 0 - var foundOne = false - var c: EllCoeffs - for i in AteLoopCount.bits(): - if not foundOne: - foundOne = i - continue - c = pc.coeffs[idx] - inc(idx) - result = result.squared().mulBy024(c.ell_0, c.ell_vw.scale(g1.y), - c.ell_vv.scale(g1.x)) - if i: - c = pc.coeffs[idx] - idx += 1 - result = result.mulBy024(c.ell_0, c.ell_vw.scale(g1.y), - c.ell_vv.scale(g1.x)) - c = pc.coeffs[idx] - idx += 1 - result = result.mulBy024(c.ell_0, c.ell_vw.scale(g1.y), c.ell_vv.scale(g1.x)) - c = pc.coeffs[idx] - result = result.mulBy024(c.ell_0, c.ell_vw.scale(g1.y), c.ell_vv.scale(g1.x)) - -proc pairing*(p: Point[G1], q: Point[G2]): FQ12 {.noinit, inline.} = - result = FQ12.one() - var optp = p.toAffine() - var optq = q.toAffine() - if optp.isSome() and optq.isSome(): - let pc = optq.get().precompute() - let ores = finalExponentiation(pc.millerLoop(optp.get())) - if ores.isSome(): - result = ores.get() - -proc init*(p: var AffinePoint[G1], x: FQ, y: FQ): bool {.inline.} = - ## Initializes AffinePoint[G1] with coordinates ``x`` and ``y``. - ## Returns ``true`` if (x, y) is on curve and in the subgroup. - if y.squared() == ((x.squared() * x) + G1B): - let point = Point[G1](x: x, y: y, z: FQ.one()) - if (point * (-FR.one())) + point == G1.zero(): - p.x = x - p.y = y - result = true - -proc init*(p: var AffinePoint[G2], x: FQ2, y: FQ2): bool {.inline.} = - ## Initializes AffinePoint[G2] with coordinates ``x`` and ``y``. - ## Returns ``true`` if (x, y) is on curve and in the subgroup. - if y.squared() == ((x.squared() * x) + G2B): - let point = Point[G2](x: x, y: y, z: FQ2.one()) - if (point * (-FR.one())) + point == G2.zero(): - p.x = x - p.y = y - result = true - -proc toBytes*[T: G1|G2](p: AffinePoint[T], dst: var openArray[byte]): bool = - ## Encode affine point coordinates (x, y) to big-endian bytes representation - ## ``dst``. - ## Returns ``true`` if coordinates was successfully serialized, ``false`` - ## otherwise. - when T is G1: - if len(dst) >= 64: - if p.x.toBytes(toOpenArray(dst, 0, 31)): - if p.y.toBytes(toOpenArray(dst, 32, 63)): - result = true - else: - if len(dst) >= 128: - if p.x.toBytes(toOpenArray(dst, 0, 63)): - if p.y.toBytes(toOpenArray(dst, 64, 127)): - result = true - -proc fromBytes*[T: G1|G2](p: var AffinePoint[T], src: openArray[byte]): bool = - ## Decode affine point coordinates (x, y) from big endian bytes representation - ## ``src``. - ## Returns ``true`` if coordinates was successfully serialized, ``false`` - ## otherwise. - when T is G1: - const - nextOffset = 32 - coeff = G1B - var - x, y: FQ - point: Point[G1] - else: - const - nextOffset = 64 - coeff = G2B - var - x, y: FQ2 - point: Point[G2] - - if len(src) >= nextOffset * 2: - if x.fromBytes(src): - if y.fromBytes(toOpenArray(src, nextOffset, len(src) - 1)): - if y.squared() == (x.squared() * x) + coeff: - ## Check if point on curve. - point.x = x - point.y = y - when T is G1: - point.z = FQ.one() - else: - point.z = FQ2.one() - if (point * (-FR.one())) + point == T.zero(): - p.x = x - p.y = y - result = true - else: - ## Point is not in the subgroup - discard - -proc fromHexString*[T: G1|G2](p: var AffinePoint[T], - src: string): bool {.inline.} = - ## Decode affine point coordinates (x, y) from hexadecimal string - ## representation ``src``. - ## Returns ``true`` if coordinates was successfully serialized, ``false`` - ## otherwise. - ## ``Note:`` Can raise exception on malformed hexadecimal string. - result = fromBytes(p, fromHex(src)) - -proc toHexString*[T: G1|G2](p: AffinePoint[T], - lowercase = false): string {.inline.} = - ## Encode affine point coordinates (x, y) and return hexadecimal string - ## representation. - when T is G1: - var buffer: array[64, byte] - else: - var buffer: array[128, byte] - if toBytes(p, buffer): - result = toHex(buffer, lowercase) - -proc toBytes*[T: G1|G2](p: Point[T], - dst: var openArray[byte]): bool {.inline.} = - ## Encode point coordinates (x, y, z) to big-endian bytes representation - ## ``dst``. - ## Returns ``true`` if coordinates was successfully serialized, ``false`` - ## otherwise. - when T is G1: - const outputSize = 64 - else: - const outputSize = 128 - if p.isZero(): - if len(dst) >= 1: - dst[0] = 0x00'u8 - result = true - else: - result = false - else: - if len(dst) >= 1 + outputSize: - var apo = p.toAffine() - if isSome(apo): - dst[0] = 0x04'u8 - result = apo.get().toBytes(toOpenArray(dst, 1, outputSize)) - -proc fromBytes*[T: G1|G2](p: var Point[T], - src: openArray[byte]): bool {.inline.} = - ## Decode affine point coordinates (x, y, z) from big endian bytes - ## representation ``src``. - ## Returns ``true`` if coordinates was successfully serialized, ``false`` - ## otherwise. - when T is G1: - const inputSize = 64 - else: - const inputSize = 128 - if len(src) > 0: - if src[0] == 0x00'u8: - p = T.zero() - result = true - elif src[0] == 0x04'u8: - if len(src) >= inputSize + 1: - var ap: AffinePoint[T] - if ap.fromBytes(toOpenArray(src, 1, inputSize)): - p = toJacobian(ap) - result = true - -proc fromHexString*[T: G1|G2](p: var Point[T], src: string): bool {.inline.} = - ## Decode point coordinates (x, y, z) from hexadecimal string - ## representation ``src``. - ## Returns ``true`` if coordinates was successfully serialized, ``false`` - ## otherwise. - ## ``Note:`` Can raise exception on malformed hexadecimal string. - result = fromBytes(p, fromHex(src)) - -proc toHexString*[T: G1|G2](p: Point[T], lowercase = false): string {.inline.} = - ## Encode affine point coordinates (x, y, z) and return hexadecimal string - ## representation. - when T is G1: - var buffer: array[64 + 1, byte] - else: - var buffer: array[128 + 1, byte] - if toBytes(p, buffer): - result = toHex(buffer, lowercase) +import group_operations, random_generation +export group_operations, random_generation diff --git a/bncurve/random_generation.nim b/bncurve/random_generation.nim new file mode 100644 index 0000000..b9684e0 --- /dev/null +++ b/bncurve/random_generation.nim @@ -0,0 +1,50 @@ +import arith, group_operations, types +import nimcrypto/sysrand + +proc setRandom*(a: var BNU512) {.inline.} = + ## Set value of integer ``a`` to random value. + let ret = randomBytes(a) + doAssert(ret == 8) +proc random*(t: typedesc[BNU512]): BNU512 {.inline, noinit.} = + ## Return random 512bit integer. + setRandom(result) +proc setRandom*(a: var BNU256, modulo: BNU256) {.noinit, inline.} = + ## Set value of integer ``a`` to random value (mod ``modulo``). + var r = BNU512.random() + discard divrem(r, modulo, a) +proc random*(t: typedesc[BNU256], modulo: BNU256): BNU256 {.noinit, inline.} = + ## Return random 256bit integer (mod ``modulo``). + result.setRandom(modulo) + +var fimodulusFQ = [0x3c208c16d87cfd47'u64, 0x97816a916871ca8d'u64, 0xb85045b68181585d'u64, 0x30644e72e131a029'u64] +proc setRandom*(dst: var FQ) {.noinit, inline.} = + ## Set ``dst`` to random value + var a = BNU256.random(fimodulusFQ) + dst = FQ(a) +var fimodulusFR = [0x43e1f593f0000001'u64, 0x2833e84879b97091'u64, 0xb85045b68181585d'u64, 0x30644e72e131a029'u64] +proc setRandom*(dst: var FR) {.noinit, inline.} = + ## Set ``dst`` to random value + var a = BNU256.random(fimodulusFR) + dst = FR(a) +proc random*(t: typedesc[FQ]): FQ {.noinit, inline.} = + ## Return random ``Fp``. + result.setRandom() +proc random*(t: typedesc[FR]): FR {.noinit, inline.} = + ## Return random ``Fp``. + result.setRandom() + +proc random*(t: typedesc[FQ2]): FQ2 {.inline, noinit.} = + result.c0 = FQ.random() + result.c1 = FQ.random() + +proc random*(t: typedesc[FQ6]): FQ6 {.inline, noinit.} = + result.c0 = FQ2.random() + result.c1 = FQ2.random() + result.c2 = FQ2.random() + +proc random*(t: typedesc[FQ12]): FQ12 {.inline, noinit.} = + result.c0 = FQ6.random() + result.c1 = FQ6.random() + +proc random*[T: G1|G2](t: typedesc[T]): Point[T] {.inline, noinit.} = + result = t.one() * FR.random() diff --git a/bncurve/types.nim b/bncurve/types.nim new file mode 100644 index 0000000..749c1f8 --- /dev/null +++ b/bncurve/types.nim @@ -0,0 +1,47 @@ +type + BNU256* = array[4, uint64] + BNU512* = array[8, uint64] + + FR* = distinct BNU256 + FQ* = distinct BNU256 + +type + FQ2* = object + c0*: FQ + c1*: FQ + +type + FQ6* = object + c0*: FQ2 + c1*: FQ2 + c2*: FQ2 + +type + FQ12* = object + c0*: FQ6 + c1*: FQ6 + +type + G1* = object + G2* = object + + Point*[T: G1|G2] = object + when T is G1: + x*, y*, z*: FQ + else: + x*, y*, z*: FQ2 + + AffinePoint*[T: G1|G2] = object + when T is G1: + x*, y*: FQ + else: + x*, y*: FQ2 + + EllCoeffs* = object + ell_0*: FQ2 + ell_vw*: FQ2 + ell_vv*: FQ2 + + G2Precomp* = object + q*: AffinePoint[G2] + coeffs*: seq[EllCoeffs] diff --git a/tests/tarith.nim b/tests/tarith.nim index 3a5bc47..7aae204 100644 --- a/tests/tarith.nim +++ b/tests/tarith.nim @@ -1,5 +1,5 @@ import unittest -import ../bncurve/arith +import ../bncurve/[arith, random_generation] when isMainModule: let modulo = [ diff --git a/tests/tfields.nim b/tests/tfields.nim index 89bc153..66c9626 100644 --- a/tests/tfields.nim +++ b/tests/tfields.nim @@ -1,6 +1,6 @@ import unittest import nimcrypto/utils -import ../bncurve/fields +import ../bncurve/[fields, random_generation] proc randomSquaring*[T](): bool = for i in 0..100: diff --git a/tests/tvectors.nim b/tests/tvectors.nim index 8a8aabb..ce12d31 100644 --- a/tests/tvectors.nim +++ b/tests/tvectors.nim @@ -30079,7 +30079,7 @@ when isMainModule: test "[G2] operations and serialization vectors": var acc = G2.one() - var by = FR.fromString("23938123") + var by = FR.fromString("23938123") for i in 0..<10000: var point: Point[G2] var buffer: array[128 + 1, byte]