Skip to content

Commit

Permalink
Add statBytes parameter to randomElementModQ, randomElementModP.
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnLCaron committed May 5, 2024
1 parent d11c532 commit f05bbf7
Show file tree
Hide file tree
Showing 14 changed files with 76 additions and 55 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[![License](https://img.shields.io/github/license/JohnLCaron/egk-ec)](https://github.com/JohnLCaron/egk-ec/blob/main/LICENSE.txt)
![GitHub branch checks state](https://img.shields.io/github/actions/workflow/status/JohnLCaron/egk-ec/unit-tests.yml)
![Coverage](https://img.shields.io/badge/coverage-90.4%25%20LOC%20(6991/7730)-blue)
![Coverage](https://img.shields.io/badge/coverage-90.5%25%20LOC%20(6991/7729)-blue)

# ElectionGuard-Kotlin Elliptic Curve

_last update 04/30/2024_
_last update 05/05/2024_

EGK Elliptic Curve (egk-ec) is an experimental implementation of [ElectionGuard](https://github.com/microsoft/electionguard),
[version 2.0](https://github.com/microsoft/electionguard/releases/download/v2.0/EG_Spec_2_0.pdf),
Expand Down
15 changes: 9 additions & 6 deletions src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,19 @@ interface GroupContext {
fun binaryToElementModQ(b: ByteArray): ElementModQ

/**
* Returns a random number in [2, Q). Promises to use a "secure" random number generator, such that
* the results are suitable for use as cryptographic keys.
* Returns a random number in [2, Q).
* Add "statistical distance" when generating.
* Uses a "secure" random number generator, such that the results are suitable for use as cryptographic keys.
*/
fun randomElementModQ() : ElementModQ
fun randomElementModQ(statBytes:Int = 0) : ElementModQ

/**
* Returns a random ElementModP. Promises to use a "secure" random number generator, such that
* the results are suitable for use as cryptographic keys.
* Returns a random ElementModP.
* Add "statistical distance" when generating.
* Uses a "secure" random number generator, such that the results are suitable for use as cryptographic keys.
* TODO no one actually needs this
*/
fun randomElementModP() : ElementModP
fun randomElementModP(statBytes:Int = 0) : ElementModP

/** Converts an integer to an ElementModQ, with optimizations when possible for small integers */
fun uIntToElementModQ(i: UInt): ElementModQ
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@ class EcGroupContext(val name: String, useNative: Boolean = true): GroupContext
}

/** Returns a random number in [2, Q). */
override fun randomElementModQ() : ElementModQ {
val b = randomBytes(MAX_BYTES_Q)
override fun randomElementModQ(statBytes:Int) : ElementModQ {
val b = randomBytes(MAX_BYTES_Q + statBytes)
val tmp = b.toBigInteger().mod(vecGroup.order)
val tmp2 = if (tmp < BigInteger.TWO) tmp + BigInteger.TWO else tmp
return EcElementModQ(this, tmp2)
}

override fun randomElementModP() = EcElementModP(this, vecGroup.randomElement())
override fun randomElementModP(statBytes:Int) = EcElementModP(this, vecGroup.randomElement(statBytes))

override fun dLogG(p: ElementModP, maxResult: Int): Int? {
require(p is EcElementModP)
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,10 @@ open class VecGroup(
}

// this value will always > 1, since 0, 1 are not on the curve.
fun randomElement(): VecElementP {
fun randomElement(statBytes:Int): VecElementP {
for (j in 0 until 1000) { // limited in case theres a bug
try {
val x = BigInteger(1, randomBytes(pbyteLength))
val x = BigInteger(1, randomBytes(pbyteLength + statBytes)).mod(primeModulus)
val fx = equationf(x)

if (jacobiSymbol(fx, primeModulus) == 1) {
Expand Down
18 changes: 11 additions & 7 deletions src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ class ProductionGroupContext(
}

val groupConstants = IntGroupConstants(name, p, q, r, g)
val pm1overq = (p - BigInteger.ONE).div(q) // (p-1)/q

override val constants = groupConstants.constants

override fun toString() : String = name
Expand Down Expand Up @@ -90,11 +92,13 @@ class ProductionGroupContext(
}

/** Returns a random number in [2, P). */
override fun randomElementModP(): ElementModP {
val b = randomBytes(MAX_BYTES_P)
val tmp = b.toBigInteger().mod(p)
val tmp2 = if (tmp < BigInteger.TWO) tmp + BigInteger.TWO else tmp
return ProductionElementModP(tmp2, this)
override fun randomElementModP(statBytes:Int): ElementModP {
val b = randomBytes(MAX_BYTES_P+statBytes)
val bi = b.toBigInteger()
val ti = bi.modPow(pm1overq, p) // by magic this makes it into a group element

val tinbounds = if (ti < BigInteger.TWO) ti + BigInteger.TWO else ti
return ProductionElementModP(tinbounds, this)
}

override fun binaryToElementModP(b: ByteArray): ElementModP? =
Expand All @@ -106,8 +110,8 @@ class ProductionGroupContext(
}

/** Returns a random number in [2, Q). */
override fun randomElementModQ() : ElementModQ {
val b = randomBytes(MAX_BYTES_Q)
override fun randomElementModQ(statBytes:Int) : ElementModQ {
val b = randomBytes(MAX_BYTES_Q + statBytes)
val tmp = b.toBigInteger().mod(q)
val tmp2 = if (tmp < BigInteger.TWO) tmp + BigInteger.TWO else tmp
return ProductionElementModQ(tmp2, this)
Expand Down
26 changes: 25 additions & 1 deletion src/test/kotlin/org/cryptobiotic/eg/core/GroupTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ class GroupTest {
fun basics() {
groups.forEach { testBasics(it) }
groups.forEach { testBasicsL(it) }
groups.forEach { testRandom(it) }
groups.forEach { testRandomWithStatBytes(it) }
}

fun testBasics(context: GroupContext) {
Expand All @@ -36,6 +38,27 @@ class GroupTest {
assertEquals(seven, three + four)
}

fun testRandom(group: GroupContext) {
val randomP = group.randomElementModP()
val randomQ = group.randomElementModQ()
if (!randomP.isValidElement()) {
randomP.isValidElement()
}
assertTrue(randomP.isValidElement(),"group ${group.constants.name}")
assertTrue(randomQ.isValidElement(), "group ${group.constants.name}")

println("random p= ${randomP.toStringShort()} random q = $randomQ are ok")
}

fun testRandomWithStatBytes(group: GroupContext) {
val randomP = group.randomElementModP(16)
val randomQ = group.randomElementModQ(16)
assertTrue(randomP.isValidElement(), "group ${group.constants.name}")
assertTrue(randomQ.isValidElement(), "group ${group.constants.name}")

println("random p= ${randomP.toStringShort()} random q = $randomQ are ok")
}

@Test
fun comparisonOperations() {
groups.forEach { comparisonOperations(it) }
Expand Down Expand Up @@ -89,7 +112,8 @@ class GroupTest {
fun binaryArrayRoundTrip(context: GroupContext) {
runTest {
forAll(propTestFastConfig, elementsModP(context)) {
it == context.binaryToElementModP(it.byteArray())
val what = context.binaryToElementModP(it.byteArray())
it == what
}
forAll(propTestFastConfig, elementsModQ(context)) {
it == context.binaryToElementModQ(it.byteArray())
Expand Down
12 changes: 3 additions & 9 deletions src/test/kotlin/org/cryptobiotic/eg/core/KotestGenerators.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,10 @@ fun elementsModP(ctx: GroupContext): Arb<ElementModP> {
// fun elementsModPNoZero(ctx: GroupContext) = elementsModP(ctx)

/** Generate an arbitrary ElementModQ in [minimum, Q) for the given group context. */
fun elementsModQ(ctx: GroupContext, minimum: Int = 0): Arb<ElementModQ> = arbitrary{ ctx.randomElementModQ() }

/* fun elementsModQ(ctx: GroupContext, minimum: Int = 0): Arb<ElementModQ> =
Arb.byteArray(Arb.constant(ctx.MAX_BYTES_Q), Arb.byte())
.map { ctx.randomElementModQ() }
*/
fun elementsModQ(ctx: GroupContext): Arb<ElementModQ> = arbitrary{ ctx.randomElementModQ() }

/** Generate an arbitrary ElementModQ in [1, Q) for the given group context. */
fun elementsModQNoZero(ctx: GroupContext) = elementsModQ(ctx, 1)
fun elementsModQNoZero(ctx: GroupContext) = elementsModQ(ctx)

/**
* Generates a valid element of the subgroup of ElementModP where there exists an e in Q such that v
Expand All @@ -43,7 +37,7 @@ fun validResiduesOfP(ctx: GroupContext): Arb<ElementModP> =
* accelerated using the default PowRadixOption in the GroupContext.
*/
fun elGamalKeypairs(ctx: GroupContext): Arb<ElGamalKeypair> =
elementsModQ(ctx, minimum = 2).map { elGamalKeyPairFromSecret(it) }
elementsModQ(ctx).map { elGamalKeyPairFromSecret(it) }

/** Generates arbitrary UInt256 values. */
fun uint256s(): Arb<UInt256> = Arb.byteArray(Arb.constant(32), Arb.byte()).map { UInt256(it) }
Expand Down
2 changes: 1 addition & 1 deletion src/test/kotlin/org/cryptobiotic/eg/core/SchnorrTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ private fun testSchnorrProof(name: String, group: GroupContext) = wordSpec {
Arb.int(0, 10),
elementsModQ(group),
elementsModQ(group)
) { kp, i, j, nonce, fakeElementModQ ->
) { kp, i, j, nonce, _ ->
val goodProof = kp.schnorrProof(i, j, nonce)
(goodProof.validate(i, j) is Ok) shouldBe true
}
Expand Down
2 changes: 1 addition & 1 deletion src/test/kotlin/org/cryptobiotic/eg/core/TestBase64.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class TestBase64 {
runTest {
val context = productionGroup()
val test = "1234567890".repeat(1000)
val e1 = assertFailsWith<IllegalArgumentException>(block = { println(" convert = ${context.base64ToElementModQ(test)}") })
assertFailsWith<IllegalArgumentException>(block = { println(" convert = ${context.base64ToElementModQ(test)}") })
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class TestElem {
val group = VecGroups.getEcGroup("P-256")
val n = 100

val elems = List(n) { group.randomElement() }
val elems = List(n) { group.randomElement(16) }
val exps = List(n) { BigInteger(256, r)}
val stopwatch = Stopwatch()
elems.forEachIndexed { idx, elem ->
Expand Down
30 changes: 13 additions & 17 deletions src/test/kotlin/org/cryptobiotic/eg/core/intgroup/TinyGroup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ internal class TinyGroupContext(
val twoModQ: ElementModQ
val dlogger: DLogarithm
val qMinus1Q: ElementModQ
val pm1overq = TinyElementModQ ((p - 1U).div(q), this) // (p-1)/q

init {
oneModP = TinyElementModP(1U, this)
Expand Down Expand Up @@ -102,7 +103,7 @@ internal class TinyGroupContext(
* If the modulus is zero, it's ignored, and the intermediate value is truncated to a UInt and
* returned.
*/
internal fun ByteArray.toUIntMod(modulus: UInt = 0U): UInt {
internal fun ByteArray.toUIntMod(modulus: UInt): UInt {
val preModulus = this.fold(0UL) { prev, next -> ((prev shl 8) or next.toUByte().toULong()) }
return if (modulus == 0U) {
preModulus.toUInt()
Expand All @@ -111,18 +112,22 @@ internal class TinyGroupContext(
}
}

override fun randomElementModQ() : ElementModQ {
override fun randomElementModQ(statBytes:Int) : ElementModQ {
val b = randomBytes(MAX_BYTES_Q)
val u32 = b.toUIntMod(q)
val result = if (u32 < 2U) u32 + 2U else u32
return uIntToElementModQ(result)
}

override fun binaryToElementModP(b: ByteArray): ElementModP? {
if (b.size > 4) return null // guaranteed to be out of bounds
override fun randomElementModP(statBytes:Int): ElementModP {
val tmp = binaryToElementModP(randomBytes(MAX_BYTES_P)) as TinyElementModP
val modp = if (tmp.element < 2U) uIntToElementModP(tmp.element + 2U) else tmp
return modp powP pm1overq // by magic this makes it into a group element
}

val u32: UInt = b.toUIntMod()
return if (u32 >= p) null else uIntToElementModP(u32)
override fun binaryToElementModP(b: ByteArray): ElementModP {
val u32: UInt = b.toUIntMod(p)
return uIntToElementModP(u32)
}

override fun binaryToElementModQ(b: ByteArray): ElementModQ {
Expand Down Expand Up @@ -160,15 +165,6 @@ internal class TinyGroupContext(

override fun dLogG(p: ElementModP, maxResult: Int): Int? = dlogger.dLog(p, maxResult)

override fun randomElementModP() = binaryToElementModPmin(randomBytes(MAX_BYTES_P), 2)

private fun binaryToElementModPmin(b: ByteArray, minimum: Int): ElementModP {
val useMinimum = if (minimum <= 0) 0U else minimum.toUInt()
val u32 = b.toUIntMod(p)
val result = if (u32 < useMinimum) u32 + useMinimum else u32
return uIntToElementModP(result)
}

override fun getAndClearOpCounts() = emptyMap<String, Int>()
}

Expand All @@ -180,8 +176,8 @@ internal class TinyElementModP(val element: UInt, val groupContext: TinyGroupCon

override fun isValidElement(): Boolean {
val inBounds = element < groupContext.p
val residue = this powP TinyElementModQ(groupContext.q, groupContext) == groupContext.ONE_MOD_P
return inBounds && residue
val residue = this powP TinyElementModQ(groupContext.q, groupContext)
return inBounds && (residue == groupContext.ONE_MOD_P)
}

override fun powP(exp: ElementModQ): ElementModP {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class ShareEncryptDecryptTest {
checkAll(
propTestFastConfig,
Arb.int(min = 1, max = 100),
elementsModQ(group, minimum = 2)
elementsModQ(group)
) { xcoord, pil ->
val trustee1 = KeyCeremonyTrustee(group, "id1", xcoord, 4, 4)
val trustee2 = KeyCeremonyTrustee(group, "id2", xcoord + 1, 4, 4)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class WebappDecryptionTest {
propTestFastConfig,
Arb.string(minSize = 3),
Arb.int(min = 1, max = 5),
elementsModQ(group, minimum = 2)
elementsModQ(group)
) { name, nmissing, lc ->
val miss = List(nmissing) { name + it }
val org = SetMissingRequest(lc, miss)
Expand Down Expand Up @@ -111,7 +111,7 @@ class WebappDecryptionTest {
Arb.int(min = 1, max = 122221),
Arb.int(min = 1, max = 11),
) { batchId, nrequests ->
val crs = List(nrequests) { elementsModQ(group, minimum = 2).single() }
val crs = List(nrequests) { elementsModQ(group).single() }
// ChallengeRequest(val batchId: Int, val texts: List<ElementModQ>)
val org = ChallengeRequest(batchId, crs)
val responses = org.publishJson().import(group)
Expand All @@ -133,7 +133,7 @@ class WebappDecryptionTest {
Arb.string(minSize = 3),
Arb.int(min = 1, max = 11),
) { name, nrequests ->
val crs = List(nrequests) { elementsModQ(group, minimum = 2).single() }
val crs = List(nrequests) { elementsModQ(group).single() }
val org = ChallengeResponses(null, 42, crs)
val responses = org.publishJson().import(group)
assertTrue(responses is Ok)
Expand Down
2 changes: 1 addition & 1 deletion src/test/kotlin/org/cryptobiotic/gmp/VecTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ class VecTest {

assertTrue(h is EcElementModP)
assertTrue(hn is EcElementModP)
assertTrue((h as EcElementModP).ec is VecElementP)
assertTrue((h as EcElementModP).ec !is VecElementPnative)
assertTrue((hn as EcElementModP).ec is VecElementPnative)

val prodpow: ElementModP = nonces.map { h powP it }.reduce { a, b -> a * b }
Expand Down

0 comments on commit f05bbf7

Please sign in to comment.