diff --git a/library/crypto/src/main/kotlin/net/agorise/library/crypto/bn256/GpF2.kt b/library/crypto/src/main/kotlin/net/agorise/library/crypto/bn256/GpF2.kt new file mode 100644 index 0000000..b292685 --- /dev/null +++ b/library/crypto/src/main/kotlin/net/agorise/library/crypto/bn256/GpF2.kt @@ -0,0 +1,168 @@ +package net.agorise.library.crypto.bn256 + +/** + * For details of the algorithms used, see "Multiplication and Squaring on + * Pairing-Friendly Fields, Devegili et al. http://eprint.iacr.org/2006/471.pdf. + * GfP2 implements a field of size p² as a quadratic extension of the base field where i²=-1. + * + * Ported over from https://github.com/deroproject/derohe/blob/main/cryptography/bn256/gfp2.go + */ +class GfP2(private var x: GfP, private var y: GfP) { + + constructor() : this(GfP(0UL), GfP(0UL)) + + override fun toString(): String { + return "(${this.x}, ${this.y})" + } + + fun set(a: GfP2): GfP2 { + this.x.set(a.x) + this.y.set(a.y) + return this + } + + fun setZero(): GfP2 { + this.x = GfP(0UL) + this.y = GfP(0UL) + return this + } + + fun setOne(): GfP2 { + this.x = GfP(0UL) + this.y = GfP.newGfP(1) + return this + } + + fun isZero(): Boolean { + val zero = GfP(0UL) + return this.x == zero && this.y == zero + } + + fun isOne(): Boolean { + val zero = GfP(0UL) + val one = GfP.newGfP(1) + return this.x == zero && this.y == one + } + + fun conjugate(a: GfP2): GfP2 { + this.y.set(a.y) + gfpNeg(this.x, a.x) + return this + } + + fun neg(a: GfP2): GfP2 { + gfpNeg(this.x, a.x) + gfpNeg(this.y, a.y) + return this + } + + fun add(a: GfP2, b: GfP2): GfP2 { + gfpAdd(this.x, a.x, b.x) + gfpAdd(this.y, a.y, b.y) + return this + } + + fun sub(a: GfP2, b: GfP2): GfP2 { + gfpSub(this.x, a.x, b.x) + gfpSub(this.y, a.y, b.y) + return this + } + + /* + * See "Multiplication and Squaring in Pairing-Friendly Fields", + * http://eprint.iacr.org/2006/471.pdf + */ + fun mul(a: GfP2, b: GfP2): GfP2 { + val tx = GfP() + val t = GfP() + gfpMul(tx, a.x, b.y) + gfpMul(t, b.x, a.y) + gfpAdd(tx, tx, t) + + val ty = GfP() + gfpMul(ty, a.y, b.y) + gfpMul(t, a.x, b.x) + gfpSub(ty, ty, t) + + this.x.set(tx) + this.y.set(ty) + return this + } + + fun mulScalar(a: GfP2, b: GfP): GfP2 { + gfpMul(this.x, a.x, b) + gfpMul(this.y, a.y, b) + return this + } + + /* + * Sets e=ξa where ξ=i+9 and then returns e. + */ + fun mulXi(a: GfP2): GfP2 { + // (xi+y)(i+9) = (9x+y)i+(9y-x) + val tx = GfP() + gfpAdd(tx, a.x, a.x) + gfpAdd(tx, tx, tx) + gfpAdd(tx, tx, tx) + gfpAdd(tx, tx, a.x) + + gfpAdd(tx, tx, a.y) + + val ty = GfP() + gfpAdd(ty, a.y, a.y) + gfpAdd(ty, ty, ty) + gfpAdd(ty, ty, ty) + gfpAdd(ty, ty, a.y) + + gfpSub(ty, ty, a.x) + + this.x.set(tx) + this.y.set(ty) + return this + } + + fun square(a: GfP2): GfP2 { + // Complex squaring algorithm: (xi+y)² = (x+y)(y-x) + 2*i*x*y + val tx = GfP() + val ty = GfP() + gfpSub(tx, a.y, a.x) + gfpAdd(ty, a.x, a.y) + gfpMul(ty, tx, ty) + + gfpMul(tx, a.x, a.y) + gfpAdd(tx, tx, tx) + + this.x.set(tx) + this.y.set(ty) + return this + } + + fun invert(a: GfP2): GfP2 { + // See "Implementing cryptographic pairings", M. Scott, section 3.2. + // ftp://136.206.11.249/pub/crypto/pairings.pdf + val t1 = GfP() + val t2 = GfP() + gfpMul(t1, a.x, a.x) + gfpMul(t2, a.y, a.y) + gfpAdd(t1, t1, t2) + + val inv = GfP() + inv.invert(t1) + + gfpNeg(t1, a.x) + + gfpMul(this.x, t1, inv) + gfpMul(this.y, a.y, inv) + return this + } + + companion object { + fun decode(input: GfP2): GfP2 { + val output = GfP2(GfP(), GfP()) + montDecode(output.x, input.x) + montDecode(output.y, input.y) + return output + } + } +} + diff --git a/library/crypto/src/main/kotlin/net/agorise/library/crypto/bn256/TwistPoint.kt b/library/crypto/src/main/kotlin/net/agorise/library/crypto/bn256/TwistPoint.kt new file mode 100644 index 0000000..1996599 --- /dev/null +++ b/library/crypto/src/main/kotlin/net/agorise/library/crypto/bn256/TwistPoint.kt @@ -0,0 +1,221 @@ +@file:OptIn(ExperimentalUnsignedTypes::class) + +package net.agorise.library.crypto.bn256 + +import java.math.BigInteger + +/** + * twistPoint implements the elliptic curve y²=x³+3/ξ over GF(p²). Points are + * kept in Jacobian form and t=z² when valid. The group G₂ is the set of + * n-torsion points of this curve over GF(p²) (where n = Order) + * + * Ported over from https://github.com/deroproject/derohe/blob/main/cryptography/bn256/twist.go + */ +class TwistPoint( + private var x: GfP2, + private var y: GfP2, + private var z: GfP2, + private var t: GfP2, +) { + + constructor() : this(GfP2(), GfP2(), GfP2(), GfP2()) + + override fun toString(): String { + makeAffine() + val xDecoded = GfP2.decode(this.x) + val yDecoded = GfP2.decode(this.y) + return "($xDecoded, $yDecoded)" + } + + fun set(a: TwistPoint) { + this.x.set(a.x) + this.y.set(a.y) + this.z.set(a.z) + this.t.set(a.t) + } + + /* + * Returns true iff c is on the curve. + */ + fun isOnCurve(): Boolean { + makeAffine() + if (isInfinity()) { + return true + } + + val y2 = GfP2() + val x3 = GfP2() + y2.square(this.y) + x3.square(this.x).mul(x3, this.x).add(x3, twistB) + + if (y2 != x3) { + return false + } + + val cneg = TwistPoint() + cneg.mul(this, Constants.Order) + return cneg.z.isZero() + } + + fun setInfinity() { + this.x.setZero() + this.y.setOne() + this.z.setZero() + this.t.setZero() + } + + fun isInfinity(): Boolean { + return this.z.isZero() + } + + fun add(a: TwistPoint, b: TwistPoint) { + // For additional comments, see the same function in CurvePoint.kt + if (a.isInfinity()) { + set(b) + return + } + if (b.isInfinity()) { + set(a) + return + } + + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/addition/add-2007-bl.op3 + val z12 = GfP2().square(a.z) + val z22 = GfP2().square(b.z) + val u1 = GfP2().mul(a.x, z22) + val u2 = GfP2().mul(b.x, z12) + + val t = GfP2().mul(b.z, z22) + val s1 = GfP2().mul(a.y, t) + + t.mul(a.z, z12) + val s2 = GfP2().mul(b.y, t) + + val h = GfP2().sub(u2, u1) + val xEqual = h.isZero() + + t.add(h, h) + val i = GfP2().square(t) + val j = GfP2().mul(h, i) + + t.sub(s2, s1) + val yEqual = t.isZero() + if (xEqual && yEqual) { + double(a) + return + } + val r = GfP2().add(t, t) + + val v = GfP2().mul(u1, i) + + val t4 = GfP2().square(r) + t.add(v, v) + val t6 = GfP2().sub(t4, j) + this.x.sub(t6, t) + + t.sub(v, this.x) // t7 + t4.mul(s1, j) // t8 + t6.add(t4, t4) // t9 + t4.mul(r, t) // t10 + this.y.sub(t4, t6) + + t.add(a.z, b.z) // t11 + t4.square(t) // t12 + t.sub(t4, z12) // t13 + t4.sub(t, z22) // t14 + this.z.mul(t4, h) + } + + fun double(a: TwistPoint) { + // See http://hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/doubling/dbl-2009-l.op3 + val A = GfP2().square(a.x) + val B = GfP2().square(a.y) + val C = GfP2().square(B) + + val t = GfP2().add(a.x, B) + val t2 = GfP2().square(t) + t.sub(t2, A) + t2.sub(t, C) + val d = GfP2().add(t2, t2) + t.add(A, A) + val e = GfP2().add(t, A) + val f = GfP2().square(e) + + t.add(d, d) + this.x.sub(f, t) + + this.z.mul(a.y, a.z) + this.z.add(this.z, this.z) + + t.add(C, C) + t2.add(t, t) + t.add(t2, t2) + this.y.sub(d, this.x) + t2.mul(e, this.y) + this.y.sub(t2, t) + } + + fun mul(a: TwistPoint, scalar: BigInteger) { + val sum = TwistPoint() + val t = TwistPoint() + + for (i in scalar.bitLength() downTo 0) { + t.double(sum) + if (scalar.testBit(i)) { + sum.add(t, a) + } else { + sum.set(t) + } + } + + set(sum) + } + + fun makeAffine() { + if (this.z.isOne()) { + return + } else if (this.z.isZero()) { + this.x.setZero() + this.y.setOne() + this.t.setZero() + return + } + + val zInv = GfP2().invert(this.z) + val t = GfP2().mul(this.y, zInv) + val zInv2 = GfP2().square(zInv) + this.y.mul(t, zInv2) + t.mul(this.x, zInv2) + this.x.set(t) + this.z.setOne() + this.t.setOne() + } + + fun neg(a: TwistPoint) { + this.x.set(a.x) + this.y.neg(a.y) + this.z.set(a.z) + this.t.setZero() + } + + companion object { + val twistB = GfP2( + GfP(ulongArrayOf(0x38e7ecccd1dcff67UL, 0x65f0b37d93ce0d3eUL, 0xd749d0dd22ac00aaUL, 0x0141b9ce4a688d4dUL)), + GfP(ulongArrayOf(0x3bf938e377b802a8UL, 0x020b1b273633535dUL, 0x26b7edf049755260UL, 0x2514c6324384a86dUL)) + ) + + val twistGen = TwistPoint( + GfP2( + GfP(ulongArrayOf(0xafb4737da84c6140UL, 0x6043dd5a5802d8c4UL, 0x09e950fc52a02f86UL, 0x14fef0833aea7b6bUL)), + GfP(ulongArrayOf(0x8e83b5d102bc2026UL, 0xdceb1935497b0172UL, 0xfbb8264797811adfUL, 0x19573841af96503bUL)) + ), + GfP2( + GfP(ulongArrayOf(0x64095b56c71856eeUL, 0xdc57f922327d3cbbUL, 0x55f935be33351076UL, 0x0da4a0e693fd6482UL)), + GfP(ulongArrayOf(0x619dfa9d886be9f6UL, 0xfe7fd297f59e9b78UL, 0xff9e1a62231b7dfeUL, 0x28fd7eebae9e4206UL)) + ), + GfP2(GfP.newGfP(0), GfP.newGfP(1)), + GfP2(GfP.newGfP(0), GfP.newGfP(1)) + ) + } +} +