Implement GfP6 and GfP12 functionality.

This commit is contained in:
Severiano Jaramillo 2024-05-27 20:53:06 -07:00
parent b8a7bb7881
commit 9352f82894
3 changed files with 406 additions and 0 deletions

View file

@ -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))
)
}

View file

@ -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
}
}

View file

@ -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
}
}