532 lines
15 KiB
Nim
532 lines
15 KiB
Nim
# 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)
|