diff --git a/library/crypto/src/main/kotlin/net/agorise/library/crypto/bn256/Constants.kt b/library/crypto/src/main/kotlin/net/agorise/library/crypto/bn256/Constants.kt index ee059e1..f5d00c9 100644 --- a/library/crypto/src/main/kotlin/net/agorise/library/crypto/bn256/Constants.kt +++ b/library/crypto/src/main/kotlin/net/agorise/library/crypto/bn256/Constants.kt @@ -27,7 +27,30 @@ object Constants { // r3 is R^3 where R = 2^256 mod p. val r3 = GfP(ulongArrayOf(0xb1cd6dafda1530dfUL, 0x62f210e6a7283db6UL, 0xef7f0b0c0ada0afbUL, 0x20fd6e902d592544UL)) + // xiToPMinus1Over6 is ξ^((p-1)/6) where ξ = i+9. + val xiToPMinus1Over6 = GfP2( + GfP(ulongArrayOf(0xa222ae234c492d72u, 0xd00f02a4565de15bu, 0xdc2ff3a253dfc926u, 0x10a75716b3899551u)), + GfP(ulongArrayOf(0xaf9ba69633144907u, 0xca6b1d7387afb78au, 0x11bded5ef08a2087u, 0x02f34d751a1f3a7cu)) + ) + + // xiToPMinus1Over3 is ξ^((p-1)/3) where ξ = i+9. + val xiToPMinus1Over3 = GfP2( + GfP(ulongArrayOf(0x6e849f1ea0aa4757u, 0xaa1c7b6d89f89141u, 0xb6e713cdfae0ca3au, 0x26694fbb4e82ebc3u)), + GfP(ulongArrayOf(0xb5773b104563ab30u, 0x347f91c8a9aa6454u, 0x7a007127242e0991u, 0x1956bcd8118214ecu)) + ) + + // xiToPSquaredMinus1Over3 is ξ^((p²-1)/3) where ξ = i+9. + val xiToPSquaredMinus1Over3 = GfP(ulongArrayOf(0x3350c88e13e80b9cu, 0x7dce557cdb5e56b9u, 0x6001b4b8b615564au, 0x2682e617020217e0u)) + // xiTo2PSquaredMinus2Over3 is ξ^((2p²-2)/3) where ξ = i+9 (a cubic root of unity, mod p). val xiTo2PSquaredMinus2Over3 = GfP(ulongArrayOf(0x71930c11d782e155u, 0xa6bb947cffbe3323u, 0xaa303344d4741444u, 0x2c3b3f0d26594943u)) + // xiToPSquaredMinus1Over6 is ξ^((p²-1)/6) where ξ = i+9 (a cubic root of -1, mod p). + val xiToPSquaredMinus1Over6 = GfP(ulongArrayOf(0xca8d800500fa1bf2u, 0xf0c5d61468b39769u, 0x0e201271ad0d4418u, 0x04290f65bad856e6u)) + + // xiTo2PMinus2Over3 is ξ^((2p-2)/3) where ξ = i+9. + val xiTo2PMinus2Over3 = GfP2( + GfP(ulongArrayOf(0x5dddfd154bd8c949u, 0x62cb29a5a4445b60u, 0x37bc870a0c7dd2b9u, 0x24830a9d3171f0fdu)), + GfP(ulongArrayOf(0x7361d77f843abe92u, 0xa5bb2bd3273411fbu, 0x9c941f314b3e2399u, 0x15df9cddbb9fd3ecu)) + ) } diff --git a/library/crypto/src/main/kotlin/net/agorise/library/crypto/bn256/GfP12.kt b/library/crypto/src/main/kotlin/net/agorise/library/crypto/bn256/GfP12.kt new file mode 100644 index 0000000..bd8e7f3 --- /dev/null +++ b/library/crypto/src/main/kotlin/net/agorise/library/crypto/bn256/GfP12.kt @@ -0,0 +1,164 @@ +package net.agorise.library.crypto.bn256 + +import java.math.BigInteger + +/** + * For details of the algorithms used, see "Multiplication and Squaring on + * Pairing-Friendly Fields, Devegili et al. http://eprint.iacr.org/2006/471.pdf. + * + * Ported over from https://github.com/deroproject/derohe/blob/main/cryptography/bn256/gfp12.go + */ +class GfP12( + // value is xω + y + var x: GfP6 = GfP6(), + var y: GfP6 = GfP6() +) { + override fun toString(): String { + return "(${this.x},${this.y})" + } + + fun set(a: GfP12): GfP12 { + this.x.set(a.x) + this.y.set(a.y) + return this + } + + fun setZero(): GfP12 { + this.x.setZero() + this.y.setZero() + return this + } + + fun setOne(): GfP12 { + this.x.setZero() + this.y.setOne() + return this + } + + fun isZero(): Boolean { + return this.x.isZero() && this.y.isZero() + } + + fun isOne(): Boolean { + return this.x.isZero() && this.y.isOne() + } + + fun conjugate(a: GfP12): GfP12 { + this.x.neg(a.x) + this.y.set(a.y) + return this + } + + fun neg(a: GfP12): GfP12 { + this.x.neg(a.x) + this.y.neg(a.y) + return this + } + + // Frobenius computes (xω+y)^p = x^p ω·ξ^((p-1)/6) + y^p + fun frobenius(a: GfP12): GfP12 { + this.x.frobenius(a.x) + this.y.frobenius(a.y) + this.x.mulScalar(this.x, Constants.xiToPMinus1Over6) + return this + } + + // FrobeniusP2 computes (xω+y)^p² = x^p² ω·ξ^((p²-1)/6) + y^p² + fun frobeniusP2(a: GfP12): GfP12 { + this.x.frobeniusP2(a.x) + this.x.mulGFP(this.x, Constants.xiToPSquaredMinus1Over6) + this.y.frobeniusP2(a.y) + return this + } + + fun frobeniusP4(a: GfP12): GfP12 { + this.x.frobeniusP4(a.x) + this.x.mulGFP(this.x, Constants.xiToPSquaredMinus1Over3) + this.y.frobeniusP4(a.y) + return this + } + + fun add(a: GfP12, b: GfP12): GfP12 { + this.x.add(a.x, b.x) + this.y.add(a.y, b.y) + return this + } + + fun sub(a: GfP12, b: GfP12): GfP12 { + this.x.sub(a.x, b.x) + this.y.sub(a.y, b.y) + return this + } + + fun mul(a: GfP12, b: GfP12): GfP12 { + val tx = GfP6().mul(a.x, b.y) + val t = GfP6().mul(b.x, a.y) + tx.add(tx, t) + + val ty = GfP6().mul(a.y, b.y) + t.mul(a.x, b.x).mulTau(t) + + this.x.set(tx) + this.y.add(ty, t) + return this + } + + /** + * Note: Slightly different than the Go implementation. There seems to be a bug in Go. + */ + fun mulScalar(a: GfP12, b: GfP6): GfP12 { + this.x.mul(a.x, b) + this.y.mul(a.y, b) + return this + } + + fun exp(a: GfP12, power: BigInteger): GfP12 { + var sum = GfP12().setOne() + val t = GfP12() + + for (i in power.bitLength() - 1 downTo 0) { + t.square(sum) + sum = if (power.testBit(i)) { + GfP12().mul(t, a) + } else { + GfP12().set(t) + } + } + + this.set(sum) + return this + } + + fun square(a: GfP12): GfP12 { + // Complex squaring algorithm + val v0 = GfP6().mul(a.x, a.y) + + val t = GfP6().mulTau(a.x) + t.add(a.y, t) + val ty = GfP6().add(a.x, a.y) + ty.mul(ty, t).sub(ty, v0) + t.mulTau(v0) + ty.sub(ty, t) + + this.x.add(v0, v0) + this.y.set(ty) + return this + } + + fun invert(a: GfP12): GfP12 { + // See "Implementing cryptographic pairings", M. Scott, section 3.2. + // ftp://136.206.11.249/pub/crypto/pairings.pdf + val t1 = GfP6() + val t2 = GfP6() + + t1.square(a.x) + t2.square(a.y) + t1.mulTau(t1).sub(t2, t1) + t2.invert(t1) + + this.x.neg(a.x) + this.y.set(a.y) + this.mulScalar(this, t2) + return this + } +} \ No newline at end of file diff --git a/library/crypto/src/main/kotlin/net/agorise/library/crypto/bn256/GfP6.kt b/library/crypto/src/main/kotlin/net/agorise/library/crypto/bn256/GfP6.kt new file mode 100644 index 0000000..2d20313 --- /dev/null +++ b/library/crypto/src/main/kotlin/net/agorise/library/crypto/bn256/GfP6.kt @@ -0,0 +1,219 @@ +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. + * + * GfP6 implements the field of size p⁶ as a cubic extension of GfP2 where τ³=ξ + * and ξ=i+9. + * + * Ported over from https://github.com/deroproject/derohe/blob/main/cryptography/bn256/gfp6.go + */ +class GfP6( + // value is xτ² + yτ + z + var x: GfP2 = GfP2(), + var y: GfP2 = GfP2(), + var z: GfP2 = GfP2(), +) { + override fun toString(): String { + return "(${this.x}, ${this.y}, ${this.z})" + } + + fun set(a: GfP6): GfP6 { + this.x.set(a.x) + this.y.set(a.y) + this.z.set(a.z) + return this + } + + fun setZero(): GfP6 { + this.x.setZero() + this.y.setZero() + this.z.setZero() + return this + } + + fun setOne(): GfP6 { + this.x.setZero() + this.y.setZero() + this.z.setOne() + return this + } + + fun isZero(): Boolean { + return this.x.isZero() && this.y.isZero() && this.z.isZero() + } + + fun isOne(): Boolean { + return this.x.isZero() && this.y.isZero() && this.z.isOne() + } + + fun neg(a: GfP6): GfP6 { + this.x.neg(a.x) + this.y.neg(a.y) + this.z.neg(a.z) + return this + } + + fun frobenius(a: GfP6): GfP6 { + this.x.conjugate(a.x) + this.y.conjugate(a.y) + this.z.conjugate(a.z) + + this.x.mul(this.x, Constants.xiTo2PMinus2Over3) + this.y.mul(this.y, Constants.xiToPMinus1Over3) + return this + } + + // FrobeniusP2 computes (xτ²+yτ+z)^(p²) = xτ^(2p²) + yτ^(p²) + z + fun frobeniusP2(a: GfP6): GfP6 { + // τ^(2p²) = τ²τ^(2p²-2) = τ²ξ^((2p²-2)/3) + this.x.mulScalar(a.x, Constants.xiTo2PSquaredMinus2Over3) + // τ^(p²) = ττ^(p²-1) = τξ^((p²-1)/3) + this.y.mulScalar(a.y, Constants.xiToPSquaredMinus1Over3) + this.z.set(a.z) + return this + } + + fun frobeniusP4(a: GfP6): GfP6 { + this.x.mulScalar(a.x, Constants.xiToPSquaredMinus1Over3) + this.y.mulScalar(a.y, Constants.xiTo2PSquaredMinus2Over3) + this.z.set(a.z) + return this + } + + fun add(a: GfP6, b: GfP6): GfP6 { + this.x.add(a.x, b.x) + this.y.add(a.y, b.y) + this.z.add(a.z, b.z) + return this + } + + fun sub(a: GfP6, b: GfP6): GfP6 { + this.x.sub(a.x, b.x) + this.y.sub(a.y, b.y) + this.z.sub(a.z, b.z) + return this + } + + fun mul(a: GfP6, b: GfP6): GfP6 { + // "Multiplication and Squaring on Pairing-Friendly Fields" + // Section 4, Karatsuba method. + // http://eprint.iacr.org/2006/471.pdf + val v0 = GfP2().mul(a.z, b.z) + val v1 = GfP2().mul(a.y, b.y) + val v2 = GfP2().mul(a.x, b.x) + + val t0 = GfP2().add(a.x, a.y) + val t1 = GfP2().add(b.x, b.y) + val tz = GfP2().mul(t0, t1) + tz.sub(tz, v1).sub(tz, v2).mulXi(tz).add(tz, v0) + + t0.add(a.y, a.z) + t1.add(b.y, b.z) + val ty = GfP2().mul(t0, t1) + t0.mulXi(v2) + ty.sub(ty, v0).sub(ty, v1).add(ty, t0) + + t0.add(a.x, a.z) + t1.add(b.x, b.z) + val tx = GfP2().mul(t0, t1) + tx.sub(tx, v0).add(tx, v1).sub(tx, v2) + + this.x.set(tx) + this.y.set(ty) + this.z.set(tz) + return this + } + + fun mulScalar(a: GfP6, b: GfP2): GfP6 { + this.x.mul(a.x, b) + this.y.mul(a.y, b) + this.z.mul(a.z, b) + return this + } + + fun mulGFP(a: GfP6, b: GfP): GfP6 { + this.x.mulScalar(a.x, b) + this.y.mulScalar(a.y, b) + this.z.mulScalar(a.z, b) + return this + } + + // MulTau computes τ·(aτ²+bτ+c) = bτ²+cτ+aξ + fun mulTau(a: GfP6): GfP6 { + val tz = GfP2().mulXi(a.x) + val ty = GfP2().set(a.y) + + this.y.set(a.z) + this.x.set(ty) + this.z.set(tz) + return this + } + + fun square(a: GfP6): GfP6 { + val v0 = GfP2().square(a.z) + val v1 = GfP2().square(a.y) + val v2 = GfP2().square(a.x) + + val c0 = GfP2().add(a.x, a.y) + c0.square(c0).sub(c0, v1).sub(c0, v2).mulXi(c0).add(c0, v0) + + val c1 = GfP2().add(a.y, a.z) + c1.square(c1).sub(c1, v0).sub(c1, v1) + val xiV2 = GfP2().mulXi(v2) + c1.add(c1, xiV2) + + val c2 = GfP2().add(a.x, a.z) + c2.square(c2).sub(c2, v0).add(c2, v1).sub(c2, v2) + + this.x.set(c2) + this.y.set(c1) + this.z.set(c0) + return this + } + + fun invert(a: GfP6): GfP6 { + // See "Implementing cryptographic pairings", M. Scott, section 3.2. + // ftp://136.206.11.249/pub/crypto/pairings.pdf + + // Here we can give a short explanation of how it works: let j be a cubic root of + // unity in GF(p²) so that 1+j+j²=0. + // Then (xτ² + yτ + z)(xj²τ² + yjτ + z)(xjτ² + yj²τ + z) + // = (xτ² + yτ + z)(Cτ²+Bτ+A) + // = (x³ξ²+y³ξ+z³-3ξxyz) = F is an element of the base field (the norm). + // + // On the other hand (xj²τ² + yjτ + z)(xjτ² + yj²τ + z) + // = τ²(y²-ξxz) + τ(ξx²-yz) + (z²-ξxy) + // + // So that's why A = (z²-ξxy), B = (ξx²-yz), C = (y²-ξxz) + val t1 = GfP2().mul(a.x, a.y) + t1.mulXi(t1) + + val A = GfP2().square(a.z) + A.sub(A, t1) + + val B = GfP2().square(a.x) + B.mulXi(B) + t1.mul(a.y, a.z) + B.sub(B, t1) + + val C = GfP2().square(a.y) + t1.mul(a.x, a.z) + C.sub(C, t1) + + val F = GfP2().mul(C, a.y) + F.mulXi(F) + t1.mul(A, a.z) + F.add(F, t1) + t1.mul(B, a.x).mulXi(t1) + F.add(F, t1) + + F.invert(F) + + this.x.mul(C, F) + this.y.mul(B, F) + this.z.mul(A, F) + return this + } +}