Implement GfP class and arithmetic.
- Ported over the GfP class functionality from the derohe project to Kotlin. - Ported over the GfP arithmetic logic too, focusing on the software implementation. We might be able to make these calculations more efficient if we do hardware calculations instead, but that can be left as a future optimization. - Created GfPTest to confirm that the arithmetic operations were implemented correctly. Hard lesson: Go operator precedence is different than Kotlin.
This commit is contained in:
parent
564e262f86
commit
6aa5a827ae
4 changed files with 353 additions and 0 deletions
|
@ -0,0 +1,29 @@
|
||||||
|
package net.agorise.shared.crypto.bn256
|
||||||
|
|
||||||
|
import java.math.BigInteger
|
||||||
|
|
||||||
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
|
object Constants {
|
||||||
|
/*
|
||||||
|
* Order is the number of elements in both G₁ and G₂: 36u⁴+36u³+18u²+6u+1.
|
||||||
|
* Needs to be highly 2-adic for efficient SNARK key and proof generation.
|
||||||
|
* Order - 1 = 2^28 * 3^2 * 13 * 29 * 983 * 11003 * 237073 * 405928799 * 1670836401704629 * 13818364434197438864469338081.
|
||||||
|
* Refer to https://eprint.iacr.org/2013/879.pdf and https://eprint.iacr.org/2013/507.pdf for more information on these parameters.
|
||||||
|
*/
|
||||||
|
val Order = BigInteger("21888242871839275222246405745257275088548364400416034343698204186575808495617")
|
||||||
|
|
||||||
|
// p2 is p, represented as little-endian 64-bit words.
|
||||||
|
val p2 = ulongArrayOf(0x3c208c16d87cfd47UL, 0x97816a916871ca8dUL, 0xb85045b68181585dUL, 0x30644e72e131a029UL)
|
||||||
|
|
||||||
|
// np is the negative inverse of p, mod 2^256.
|
||||||
|
val np = ulongArrayOf(0x87d20782e4866389UL, 0x9ede7d651eca6ac9UL, 0xd8afcbd01833da80UL, 0xf57a22b791888c6bUL)
|
||||||
|
|
||||||
|
// rN1 is R^-1 where R = 2^256 mod p.
|
||||||
|
val rN1 = GfP(ulongArrayOf(0xed84884a014afa37UL, 0xeb2022850278edf8UL, 0xcf63e9cfb74492d9UL, 0x2e67157159e5c639UL))
|
||||||
|
|
||||||
|
// r2 is R^2 where R = 2^256 mod p.
|
||||||
|
val r2 = GfP(ulongArrayOf(0xf32cfc5b538afa89UL, 0xb5e71911d44501fbUL, 0x47ab1eff0a417ff6UL, 0x06d89f71cab8351fUL))
|
||||||
|
|
||||||
|
// r3 is R^3 where R = 2^256 mod p.
|
||||||
|
val r3 = GfP(ulongArrayOf(0xb1cd6dafda1530dfUL, 0x62f210e6a7283db6UL, 0xef7f0b0c0ada0afbUL, 0x20fd6e902d592544UL))
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
@file:OptIn(ExperimentalUnsignedTypes::class)
|
||||||
|
|
||||||
|
package net.agorise.shared.crypto.bn256
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GfP implementation ported over from https://github.com/deroproject/derohe/blob/main/cryptography/bn256/gfp.go
|
||||||
|
*/
|
||||||
|
class GfP(val data: ULongArray) {
|
||||||
|
|
||||||
|
constructor() : this(ulongArrayOf(0UL, 0UL, 0UL, 0UL))
|
||||||
|
|
||||||
|
constructor(x: ULong) : this(ulongArrayOf(x, 0UL, 0UL, 0UL))
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (data.size != 4) {
|
||||||
|
throw Exception("bn256: invalid field element size")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return String.format("%016x %016x %016x %016x", data[3].toLong(), data[2].toLong(), data[1].toLong(), data[0].toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun set(f: GfP) {
|
||||||
|
data[0] = f.data[0]
|
||||||
|
data[1] = f.data[1]
|
||||||
|
data[2] = f.data[2]
|
||||||
|
data[3] = f.data[3]
|
||||||
|
}
|
||||||
|
|
||||||
|
fun invert(f: GfP) {
|
||||||
|
val bits = ulongArrayOf(0x3c208c16d87cfd45UL, 0x97816a916871ca8dUL, 0xb85045b68181585dUL, 0x30644e72e131a029UL)
|
||||||
|
val sum = Constants.rN1.copy()
|
||||||
|
val power = f.copy()
|
||||||
|
|
||||||
|
for (word in 0 until 4) {
|
||||||
|
for (bit in 0 until 64) {
|
||||||
|
if ((bits[word] shr bit) and 1UL == 1UL) {
|
||||||
|
gfpMul(sum, sum, power)
|
||||||
|
}
|
||||||
|
gfpMul(power, power, power)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gfpMul(sum, sum, Constants.r3)
|
||||||
|
set(sum)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun marshal(out: ByteArray) {
|
||||||
|
for (w in 0 until 4) {
|
||||||
|
for (b in 0 until 8) {
|
||||||
|
out[8 * w + b] = (data[3 - w] shr (56 - 8 * b)).toByte()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unmarshal(`in`: ByteArray) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in 3 downTo 0) {
|
||||||
|
if (data[i] < Constants.p2[i]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (data[i] > Constants.p2[i]) {
|
||||||
|
throw Exception("bn256: coordinate exceeds modulus")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Exception("bn256: coordinate equals modulus")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun copy(): GfP { return GfP(ulongArrayOf(data[0], data[1], data[2], data[3])) }
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newGfP(x: Long): GfP {
|
||||||
|
val out = if (x >= 0) {
|
||||||
|
GfP(x.toULong())
|
||||||
|
} else {
|
||||||
|
val negatedX = -x
|
||||||
|
val gfP = GfP(negatedX.toULong())
|
||||||
|
gfpNeg(gfP, gfP)
|
||||||
|
gfP
|
||||||
|
}
|
||||||
|
|
||||||
|
montEncode(out, out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun montEncode(c: GfP, a: GfP) { gfpMul(c, a, Constants.r2) }
|
||||||
|
fun montDecode(c: GfP, a: GfP) { gfpMul(c, a, GfP(1UL)) }
|
|
@ -0,0 +1,173 @@
|
||||||
|
@file:OptIn(ExperimentalUnsignedTypes::class)
|
||||||
|
|
||||||
|
package net.agorise.shared.crypto.bn256
|
||||||
|
|
||||||
|
// GfP arithmetic software implementation. We can add hardware implementation later on if necessary.
|
||||||
|
// Ported from https://github.com/deroproject/derohe/blob/main/cryptography/bn256/gfp_generic.go
|
||||||
|
|
||||||
|
fun gfpCarry(a: GfP, head: ULong) {
|
||||||
|
val b = GfP()
|
||||||
|
|
||||||
|
var carry = 0UL
|
||||||
|
for ((i, pi) in Constants.p2.withIndex()) {
|
||||||
|
val ai = a.data[i]
|
||||||
|
val bi = ai - pi - carry
|
||||||
|
b.data[i] = bi
|
||||||
|
carry = ((pi and ai.inv()) or ((pi or ai.inv()) and bi)) shr 63
|
||||||
|
}
|
||||||
|
carry = carry and head.inv()
|
||||||
|
|
||||||
|
// If b is negative, then return a. Else return b.
|
||||||
|
carry = 0UL - carry
|
||||||
|
val nCarry = carry.inv()
|
||||||
|
for (i in 0 until 4) {
|
||||||
|
a.data[i] = (a.data[i] and carry) or (b.data[i] and nCarry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun gfpNeg(c: GfP, a: GfP) {
|
||||||
|
var carry = 0UL
|
||||||
|
for ((i, pi) in Constants.p2.withIndex()) {
|
||||||
|
val ai = a.data[i]
|
||||||
|
val ci = pi - ai - carry
|
||||||
|
c.data[i] = ci
|
||||||
|
carry = ((ai and pi.inv()) or ((ai or pi.inv()) and ci)) shr 63
|
||||||
|
}
|
||||||
|
gfpCarry(c, 0UL)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun gfpAdd(c: GfP, a: GfP, b: GfP) {
|
||||||
|
var carry = 0UL
|
||||||
|
for ((i, ai) in a.data.withIndex()) {
|
||||||
|
val bi = b.data[i]
|
||||||
|
val ci = ai + bi + carry
|
||||||
|
c.data[i] = ci
|
||||||
|
carry = ((ai and bi) or ((ai or bi) and ci.inv())) shr 63
|
||||||
|
}
|
||||||
|
gfpCarry(c, carry)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun gfpSub(c: GfP, a: GfP, b: GfP) {
|
||||||
|
val t = GfP()
|
||||||
|
|
||||||
|
var carry = 0UL
|
||||||
|
for ((i, pi) in Constants.p2.withIndex()) {
|
||||||
|
val bi = b.data[i]
|
||||||
|
val ti = pi - bi - carry
|
||||||
|
t.data[i] = ti
|
||||||
|
carry = ((bi and pi.inv()) or ((bi or pi.inv()) and ti)) shr 63
|
||||||
|
}
|
||||||
|
|
||||||
|
carry = 0UL
|
||||||
|
for ((i, ai) in a.data.withIndex()) {
|
||||||
|
val ti = t.data[i]
|
||||||
|
val ci = ai + ti + carry
|
||||||
|
c.data[i] = ci
|
||||||
|
carry = ((ai and ti) or ((ai or ti) and ci.inv())) shr 63
|
||||||
|
}
|
||||||
|
gfpCarry(c, carry)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun gfpMul(c: GfP, a: GfP, b: GfP) {
|
||||||
|
val T = mul(a.data, b.data)
|
||||||
|
val m = halfMul(ulongArrayOf(T[0], T[1], T[2], T[3]), Constants.np)
|
||||||
|
val t = mul(ulongArrayOf(m[0], m[1], m[2], m[3]), Constants.p2)
|
||||||
|
|
||||||
|
var carry = 0UL
|
||||||
|
for ((i, Ti) in T.withIndex()) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
c.data[0] = T[4]
|
||||||
|
c.data[1] = T[5]
|
||||||
|
c.data[2] = T[6]
|
||||||
|
c.data[3] = T[7]
|
||||||
|
gfpCarry(c, carry)
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val mask16 = 0x0000ffffUL
|
||||||
|
private const val mask32 = 0xffffffffUL
|
||||||
|
|
||||||
|
private fun mul(a: ULongArray, b: ULongArray): ULongArray {
|
||||||
|
val buff = ULongArray(32)
|
||||||
|
for ((i, ai) in a.withIndex()) {
|
||||||
|
val (a0, a1, a2, a3) = listOf(ai and mask16, (ai shr 16) and mask16, (ai shr 32) and mask16, ai shr 48)
|
||||||
|
|
||||||
|
for ((j, bj) in b.withIndex()) {
|
||||||
|
val (b0, b2) = listOf(bj and mask32, bj shr 32)
|
||||||
|
|
||||||
|
val off = 4 * (i + j)
|
||||||
|
buff[off + 0] += a0 * b0
|
||||||
|
buff[off + 1] += a1 * b0
|
||||||
|
buff[off + 2] += a2 * b0 + a0 * b2
|
||||||
|
buff[off + 3] += a3 * b0 + a1 * b2
|
||||||
|
buff[off + 4] += a2 * b2
|
||||||
|
buff[off + 5] += a3 * b2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in 1 until 4) {
|
||||||
|
val shift = 16 * i
|
||||||
|
|
||||||
|
var head = 0UL
|
||||||
|
var carry = 0UL
|
||||||
|
for (j in 0 until 8) {
|
||||||
|
val block = 4 * j
|
||||||
|
|
||||||
|
val xi = buff[block]
|
||||||
|
val yi = (buff[block + i] shl shift) + head
|
||||||
|
val zi = xi + yi + carry
|
||||||
|
buff[block] = zi
|
||||||
|
carry = ((xi and yi) or ((xi or yi) and zi.inv())) shr 63
|
||||||
|
|
||||||
|
head = buff[block + i] shr (64 - shift)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ULongArray(8) { buff[it * 4] } // return only the first 8 elements
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun halfMul(a: ULongArray, b: ULongArray): ULongArray {
|
||||||
|
val buff = ULongArray(18)
|
||||||
|
for ((i, ai) in a.withIndex()) {
|
||||||
|
val (a0, a1, a2, a3) = listOf(ai and mask16, (ai shr 16) and mask16, (ai shr 32) and mask16, ai shr 48)
|
||||||
|
|
||||||
|
for ((j, bj) in b.withIndex()) {
|
||||||
|
if (i + j > 3) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
val (b0, b2) = listOf(bj and mask32, bj shr 32)
|
||||||
|
|
||||||
|
val off = 4 * (i + j)
|
||||||
|
buff[off + 0] += a0 * b0
|
||||||
|
buff[off + 1] += a1 * b0
|
||||||
|
buff[off + 2] += a2 * b0 + a0 * b2
|
||||||
|
buff[off + 3] += a3 * b0 + a1 * b2
|
||||||
|
buff[off + 4] += a2 * b2
|
||||||
|
buff[off + 5] += a3 * b2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in 1 until 4) {
|
||||||
|
val shift = 16 * i
|
||||||
|
|
||||||
|
var head = 0UL
|
||||||
|
var carry = 0UL
|
||||||
|
for (j in 0 until 4) {
|
||||||
|
val block = 4 * j
|
||||||
|
|
||||||
|
val xi = buff[block]
|
||||||
|
val yi = (buff[block + i] shl shift) + head
|
||||||
|
val zi = xi + yi + carry
|
||||||
|
buff[block] = zi
|
||||||
|
carry = ((xi and yi) or ((xi or yi) and zi.inv())) shr 63
|
||||||
|
|
||||||
|
head = buff[block + i] shr (64 - shift)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ULongArray(4) { buff[it * 4] }
|
||||||
|
}
|
|
@ -0,0 +1,54 @@
|
||||||
|
package net.agorise.shared.crypto.bn256
|
||||||
|
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
|
class GfPTest {
|
||||||
|
@Test
|
||||||
|
fun `given a GfP value - when gfpNeg is called - then result is correct`() {
|
||||||
|
val inputGfP = GfP(ulongArrayOf(0x0123456789abcdefUL, 0xfedcba9876543210UL, 0xdeadbeefdeadbeefUL, 0xfeebdaedfeebdaedUL))
|
||||||
|
val expectedGfP = GfP(ulongArrayOf(0xfedcba9876543211UL, 0x0123456789abcdefUL, 0x2152411021524110UL, 0x0114251201142512UL))
|
||||||
|
val actualGfP = GfP()
|
||||||
|
|
||||||
|
gfpNeg(actualGfP, inputGfP)
|
||||||
|
|
||||||
|
assertEquals(expectedGfP.data.toList(), actualGfP.data.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given two GfP values - when gfpAdd is called - then result is correct`() {
|
||||||
|
val aGfP = GfP(ulongArrayOf(0x0123456789abcdefUL, 0xfedcba9876543210UL, 0xdeadbeefdeadbeefUL, 0xfeebdaedfeebdaedUL))
|
||||||
|
val bGfP = GfP(ulongArrayOf(0xfedcba9876543210UL, 0x0123456789abcdefUL, 0xfeebdaedfeebdaedUL, 0xdeadbeefdeadbeefUL))
|
||||||
|
val expectedGfP = GfP(ulongArrayOf(0xc3df73e9278302b8UL, 0x687e956e978e3572UL, 0x254954275c18417fUL, 0xad354b6afc67f9b4UL))
|
||||||
|
val actualGfP = GfP()
|
||||||
|
|
||||||
|
gfpAdd(actualGfP, aGfP, bGfP)
|
||||||
|
|
||||||
|
assertEquals(expectedGfP.data.toList(), actualGfP.data.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given two GfP values - when gfpSub is called - then result is correct`() {
|
||||||
|
val aGfP = GfP(ulongArrayOf(0x0123456789abcdefUL, 0xfedcba9876543210UL, 0xdeadbeefdeadbeefUL, 0xfeebdaedfeebdaedUL))
|
||||||
|
val bGfP = GfP(ulongArrayOf(0xfedcba9876543210UL, 0x0123456789abcdefUL, 0xfeebdaedfeebdaedUL, 0xdeadbeefdeadbeefUL))
|
||||||
|
val expectedGfP = GfP(ulongArrayOf(0x02468acf13579bdfUL, 0xfdb97530eca86420UL, 0xdfc1e401dfc1e402UL, 0x203e1bfe203e1bfdUL))
|
||||||
|
val actualGfP = GfP()
|
||||||
|
|
||||||
|
gfpSub(actualGfP, aGfP, bGfP)
|
||||||
|
|
||||||
|
assertEquals(expectedGfP.data.toList(), actualGfP.data.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given two GfP values - when gfpMul is called - then result is correct`() {
|
||||||
|
val aGfP = GfP(ulongArrayOf(0x0123456789abcdefUL, 0xfedcba9876543210UL, 0xdeadbeefdeadbeefUL, 0xfeebdaedfeebdaedUL))
|
||||||
|
val bGfP = GfP(ulongArrayOf(0xfedcba9876543210UL, 0x0123456789abcdefUL, 0xfeebdaedfeebdaedUL, 0xdeadbeefdeadbeefUL))
|
||||||
|
val expectedGfP = GfP(ulongArrayOf(0xcbcbd377f7ad22d3UL, 0x3b89ba5d849379bfUL, 0x87b61627bd38b6d2UL, 0xc44052a2a0e654b2UL))
|
||||||
|
val actualGfP = GfP()
|
||||||
|
|
||||||
|
gfpMul(actualGfP, aGfP, bGfP)
|
||||||
|
|
||||||
|
assertEquals(expectedGfP.data.toList(), actualGfP.data.toList())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue