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).
|
||||
* Ported over from https://github.com/deroproject/derohe/blob/main/cryptography/bn256/curve.go
|
||||
*/
|
||||
internal class CurvePoint(
|
||||
private var x: GfP,
|
||||
private var y: GfP,
|
||||
private var z: GfP,
|
||||
private var t: GfP,
|
||||
class CurvePoint(
|
||||
var x: GfP,
|
||||
var y: GfP,
|
||||
var z: 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 {
|
||||
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 y = GfP()
|
||||
montDecode(x, this.x)
|
||||
montDecode(y, this.y)
|
||||
montDecode(x, copy.x)
|
||||
montDecode(y, copy.y)
|
||||
return "($x, $y)"
|
||||
}
|
||||
|
||||
|
@ -223,10 +226,10 @@ internal class CurvePoint(
|
|||
|
||||
// curveGen is the generator of G₁.
|
||||
internal val curveGen = CurvePoint(
|
||||
GfP.newGfP(1),
|
||||
GfP.newGfP(2),
|
||||
GfP.newGfP(1),
|
||||
GfP.newGfP(1),
|
||||
x = GfP.newGfP(1),
|
||||
y = GfP.newGfP(2),
|
||||
z = GfP.newGfP(1),
|
||||
t = GfP.newGfP(1),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,9 @@ class GfP(val data: ULongArray) {
|
|||
}
|
||||
|
||||
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 {
|
||||
|
@ -54,19 +56,19 @@ class GfP(val data: ULongArray) {
|
|||
set(sum)
|
||||
}
|
||||
|
||||
fun marshal(out: ByteArray) {
|
||||
fun marshal(out: UByteArray, offset: Int = 0) {
|
||||
for (w in 0 until 4) {
|
||||
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) {
|
||||
data[3 - w] = 0UL
|
||||
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 zi = Ti + ti + carry
|
||||
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]
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@file:OptIn(ExperimentalUnsignedTypes::class)
|
||||
|
||||
package net.agorise.library.crypto.bn256
|
||||
|
||||
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 maxLen = decomp.maxOf { it.bitLength() }
|
||||
|
||||
val out = ByteArray(maxLen)
|
||||
val out = UByteArray(maxLen)
|
||||
for ((j, x) in decomp.withIndex()) {
|
||||
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
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
||||
@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