Implement BN256's G1 functionality.
- Ported over the BN256's G1 functionality from derohe's bh256.go file. - Updated CurePoint's toString() method: Create a copy of the object to avoid updating the values of the object when calling toString(). - Used UByteArray in GfP's marshal/unmarshal methods to fix issue with negative numbers. - Fixed an issue with how the carry is calculated in gfpMul method and added a test to confirm its correct behavior. - Used UByteArray in Lattice's multi method to avoid issues with negative numbers. - Added a test to confirm that G1 marshal and unmarshal methods work correctly.
This commit is contained in:
parent
9352f82894
commit
01be5d24ab
7 changed files with 211 additions and 22 deletions
|
@ -0,0 +1,145 @@
|
||||||
|
@file:OptIn(ExperimentalUnsignedTypes::class)
|
||||||
|
|
||||||
|
package net.agorise.library.crypto.bn256
|
||||||
|
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.security.SecureRandom
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Package bn256 implements a particular bilinear group at the 128-bit security
|
||||||
|
* level.
|
||||||
|
*
|
||||||
|
* Bilinear groups are the basis of many of the new cryptographic protocols that
|
||||||
|
* have been proposed over the past decade. They consist of a triplet of groups
|
||||||
|
* (G₁, G₂ and GT) such that there exists a function e(g₁ˣ,g₂ʸ)=gTˣʸ (where gₓ
|
||||||
|
* is a generator of the respective group). That function is called a pairing
|
||||||
|
* function.
|
||||||
|
*
|
||||||
|
* This package specifically implements the Optimal Ate pairing over a 256-bit
|
||||||
|
* Barreto-Naehrig curve as described in
|
||||||
|
* http://cryptojedi.org/papers/dclxvi-20100714.pdf. Its output is not
|
||||||
|
* compatible with the implementation described in that paper, as different
|
||||||
|
* parameters are chosen.
|
||||||
|
*
|
||||||
|
* (This package previously claimed to operate at a 128-bit security level.
|
||||||
|
* However, recent improvements in attacks mean that is no longer true. See
|
||||||
|
* https://moderncrypto.org/mail-archive/curves/2016/000740.html.)
|
||||||
|
*
|
||||||
|
* Ported over from https://github.com/deroproject/derohe/blob/main/cryptography/bn256/bn256.go
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun randomK(random: SecureRandom): BigInteger {
|
||||||
|
var k: BigInteger
|
||||||
|
do {
|
||||||
|
k = BigInteger(Constants.Order.bitLength(), random).mod(Constants.Order)
|
||||||
|
} while (k.signum() != 1)
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* G1 is an abstract cyclic group. The zero value is suitable for use as the
|
||||||
|
* output of an operation, but cannot be used as an input.
|
||||||
|
*/
|
||||||
|
class G1(var p: CurvePoint = CurvePoint()) {
|
||||||
|
override fun toString(): String {
|
||||||
|
return "bn256.G1: $p"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScalarBaseMult sets e to g*k where g is the generator of the group and then returns e.
|
||||||
|
fun scalarBaseMult(k: BigInteger): G1 {
|
||||||
|
this.p.mul(CurvePoint.curveGen, k)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
// ScalarMult sets e to a*k and then returns e.
|
||||||
|
fun scalarMult(a: G1, k: BigInteger): G1 {
|
||||||
|
this.p.mul(a.p, k)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add sets e to a+b and then returns e.
|
||||||
|
fun add(a: G1, b: G1): G1 {
|
||||||
|
this.p.add(a.p, b.p)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neg sets e to -a and then returns e.
|
||||||
|
fun neg(a: G1): G1 {
|
||||||
|
this.p.neg(a.p)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets e to a and then returns e.
|
||||||
|
fun set(a: G1): G1 {
|
||||||
|
p.set(a.p)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal converts e to a byte slice.
|
||||||
|
fun marshal(): UByteArray {
|
||||||
|
// Each value is a 256-bit number.
|
||||||
|
val numBytes = 256 / 8
|
||||||
|
|
||||||
|
this.p.makeAffine()
|
||||||
|
val ret = UByteArray(numBytes * 2)
|
||||||
|
if (this.p.isInfinity()) {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
val temp = GfP()
|
||||||
|
|
||||||
|
montDecode(temp, this.p.x)
|
||||||
|
temp.marshal(ret)
|
||||||
|
montDecode(temp, this.p.y)
|
||||||
|
temp.marshal(ret, numBytes)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal sets e to the result of converting the output of Marshal back into
|
||||||
|
// a group element and then returns e.
|
||||||
|
fun unmarshal(m: UByteArray): Result<UByteArray> {
|
||||||
|
// Each value is a 256-bit number.
|
||||||
|
val numBytes = 256 / 8
|
||||||
|
if (m.size < 2 * numBytes) {
|
||||||
|
return Result.failure(IllegalArgumentException("bn256: not enough data"))
|
||||||
|
}
|
||||||
|
// Unmarshal the points and check their caps
|
||||||
|
this.p.x = GfP()
|
||||||
|
this.p.y = GfP()
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.p.x.unmarshal(m)
|
||||||
|
this.p.y.unmarshal(m, numBytes)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
return Result.failure(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode into Montgomery form and ensure it's on the curve
|
||||||
|
montEncode(this.p.x, this.p.x)
|
||||||
|
montEncode(this.p.y, this.p.y)
|
||||||
|
|
||||||
|
val zero = GfP()
|
||||||
|
if (this.p.x == zero && this.p.y == zero) {
|
||||||
|
// This is the point at infinity.
|
||||||
|
this.p.y = GfP.newGfP(1)
|
||||||
|
this.p.z = GfP()
|
||||||
|
this.p.t = GfP()
|
||||||
|
} else {
|
||||||
|
this.p.z = GfP.newGfP(1)
|
||||||
|
this.p.t = GfP.newGfP(1)
|
||||||
|
|
||||||
|
if (this.p.isOnCurve().not()) {
|
||||||
|
return Result.failure(IllegalArgumentException("bn256: malformed point"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Result.success(m.copyOfRange(2 * numBytes, m.size))
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// RandomG1 returns x and g₁ˣ where x is a random, non-zero number read from r.
|
||||||
|
fun randomG1(random: SecureRandom): Pair<BigInteger, G1> {
|
||||||
|
val k = randomK(random)
|
||||||
|
return Pair(k, G1().scalarBaseMult(k))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,20 +7,23 @@ import java.math.BigInteger
|
||||||
* form and t=z² when valid. G₁ is the set of points of this curve on GF(p).
|
* form and t=z² when valid. G₁ is the set of points of this curve on GF(p).
|
||||||
* Ported over from https://github.com/deroproject/derohe/blob/main/cryptography/bn256/curve.go
|
* Ported over from https://github.com/deroproject/derohe/blob/main/cryptography/bn256/curve.go
|
||||||
*/
|
*/
|
||||||
internal class CurvePoint(
|
class CurvePoint(
|
||||||
private var x: GfP,
|
var x: GfP,
|
||||||
private var y: GfP,
|
var y: GfP,
|
||||||
private var z: GfP,
|
var z: GfP,
|
||||||
private var t: GfP,
|
var t: GfP,
|
||||||
) {
|
) {
|
||||||
private constructor() : this(GfP(0UL), GfP(0UL), GfP(0UL), GfP(0UL))
|
constructor() : this(GfP(), GfP(), GfP(), GfP())
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
makeAffine()
|
// Create a copy of the object to avoid modifying the original one
|
||||||
|
val copy = CurvePoint(this.x.copy(), this.y.copy(), this.z.copy(), this.t.copy())
|
||||||
|
|
||||||
|
copy.makeAffine()
|
||||||
val x = GfP()
|
val x = GfP()
|
||||||
val y = GfP()
|
val y = GfP()
|
||||||
montDecode(x, this.x)
|
montDecode(x, copy.x)
|
||||||
montDecode(y, this.y)
|
montDecode(y, copy.y)
|
||||||
return "($x, $y)"
|
return "($x, $y)"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,10 +226,10 @@ internal class CurvePoint(
|
||||||
|
|
||||||
// curveGen is the generator of G₁.
|
// curveGen is the generator of G₁.
|
||||||
internal val curveGen = CurvePoint(
|
internal val curveGen = CurvePoint(
|
||||||
GfP.newGfP(1),
|
x = GfP.newGfP(1),
|
||||||
GfP.newGfP(2),
|
y = GfP.newGfP(2),
|
||||||
GfP.newGfP(1),
|
z = GfP.newGfP(1),
|
||||||
GfP.newGfP(1),
|
t = GfP.newGfP(1),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,9 @@ class GfP(val data: ULongArray) {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return String.format("%016x %016x %016x %016x", data[3].toLong(), data[2].toLong(), data[1].toLong(), data[0].toLong())
|
// Easier for testing
|
||||||
|
return data.joinToString(separator = " ", transform = { it.toString(10) })
|
||||||
|
// return data.reversed().joinToString(separator = " ", transform = { it.toString(16) })
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
|
@ -54,19 +56,19 @@ class GfP(val data: ULongArray) {
|
||||||
set(sum)
|
set(sum)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun marshal(out: ByteArray) {
|
fun marshal(out: UByteArray, offset: Int = 0) {
|
||||||
for (w in 0 until 4) {
|
for (w in 0 until 4) {
|
||||||
for (b in 0 until 8) {
|
for (b in 0 until 8) {
|
||||||
out[8 * w + b] = (data[3 - w] shr (56 - 8 * b)).toByte()
|
out[offset + 8 * w + b] = (data[3 - w] shr (56 - 8 * b)).toUByte()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unmarshal(`in`: ByteArray) {
|
fun unmarshal(`in`: UByteArray, offset: Int = 0) {
|
||||||
for (w in 0 until 4) {
|
for (w in 0 until 4) {
|
||||||
data[3 - w] = 0UL
|
data[3 - w] = 0UL
|
||||||
for (b in 0 until 8) {
|
for (b in 0 until 8) {
|
||||||
data[3 - w] += `in`[8 * w + b].toULong() shl (56 - 8 * b)
|
data[3 - w] += `in`[offset + 8 * w + b].toULong() shl (56 - 8 * b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -78,7 +78,7 @@ fun gfpMul(c: GfP, a: GfP, b: GfP) {
|
||||||
val ti = t[i]
|
val ti = t[i]
|
||||||
val zi = Ti + ti + carry
|
val zi = Ti + ti + carry
|
||||||
T[i] = zi
|
T[i] = zi
|
||||||
carry = (Ti and ti or (Ti or ti) and zi.inv()) shr 63
|
carry = ((Ti and ti) or ((Ti or ti) and zi.inv())) shr 63
|
||||||
}
|
}
|
||||||
|
|
||||||
c.data[0] = T[4]
|
c.data[0] = T[4]
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
@file:OptIn(ExperimentalUnsignedTypes::class)
|
||||||
|
|
||||||
package net.agorise.library.crypto.bn256
|
package net.agorise.library.crypto.bn256
|
||||||
|
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
|
@ -52,14 +54,15 @@ internal class Lattice(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun multi(scalar: BigInteger): ByteArray {
|
fun multi(scalar: BigInteger): UByteArray {
|
||||||
val decomp = decompose(scalar)
|
val decomp = decompose(scalar)
|
||||||
val maxLen = decomp.maxOf { it.bitLength() }
|
val maxLen = decomp.maxOf { it.bitLength() }
|
||||||
|
|
||||||
val out = ByteArray(maxLen)
|
val out = UByteArray(maxLen)
|
||||||
for ((j, x) in decomp.withIndex()) {
|
for ((j, x) in decomp.withIndex()) {
|
||||||
for (i in 0 until maxLen) {
|
for (i in 0 until maxLen) {
|
||||||
out[i] = (out[i] + (if (x.testBit(i)) 1 else 0) shl j).toByte()
|
val bitValue = if (x.testBit(i)) 1u else 0u
|
||||||
|
out[i] = (out[i] + (bitValue shl j)).toUByte()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
@file:OptIn(ExperimentalUnsignedTypes::class)
|
||||||
|
|
||||||
|
package net.agorise.library.crypto.bn256
|
||||||
|
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
class Bn256Test {
|
||||||
|
@Test
|
||||||
|
fun `given a randomly generated G1 - when marshal and unmarshal are called - then result is correct`() {
|
||||||
|
val random = SecureRandom()
|
||||||
|
val (_, ga) = G1.randomG1(random)
|
||||||
|
val ma = ga.marshal()
|
||||||
|
|
||||||
|
val gb = G1()
|
||||||
|
gb.unmarshal(ma)
|
||||||
|
|
||||||
|
val mb = gb.marshal()
|
||||||
|
|
||||||
|
println(ma.toList())
|
||||||
|
assertEquals(ma.toList(), mb.toList(), "bytes are different")
|
||||||
|
}
|
||||||
|
}
|
|
@ -51,4 +51,16 @@ class GfPTest {
|
||||||
|
|
||||||
assertEquals(expectedGfP.data.toList(), actualGfP.data.toList())
|
assertEquals(expectedGfP.data.toList(), actualGfP.data.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given two dec GfP values - when gfpMul is called - then result is correct`() {
|
||||||
|
val aGfP = GfP(ulongArrayOf(15230403791020821917UL, 754611498739239741UL, 7381016538464732716UL, 1011752739694698287UL))
|
||||||
|
val bGfP = GfP(ulongArrayOf(4792239181621121553UL, 12809537390334529128UL, 17379557665190828237UL, 110455090769457822UL))
|
||||||
|
val expectedGfP = GfP(ulongArrayOf(4792239181621121553UL, 12809537390334529128UL, 17379557665190828237UL, 110455090769457822UL))
|
||||||
|
val actualGfP = GfP()
|
||||||
|
|
||||||
|
gfpMul(actualGfP, aGfP, bGfP)
|
||||||
|
|
||||||
|
assertEquals(expectedGfP.data.toList(), actualGfP.data.toList())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue