diff --git a/README.md b/README.md index be9d64f..b12664b 100644 --- a/README.md +++ b/README.md @@ -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), diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt b/src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt index c609223..cd49435 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt @@ -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 diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcGroupContext.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcGroupContext.kt index 8aa0505..dfd1df0 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcGroupContext.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcGroupContext.kt @@ -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) diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroup.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroup.kt index 4cc9ee9..021722f 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroup.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroup.kt @@ -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) { diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroup.kt b/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroup.kt index dfac810..e89b6ce 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroup.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroup.kt @@ -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 @@ -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? = @@ -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) diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/GroupTest.kt b/src/test/kotlin/org/cryptobiotic/eg/core/GroupTest.kt index 9994d58..2ee570b 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/GroupTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/GroupTest.kt @@ -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) { @@ -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) } @@ -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()) diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/KotestGenerators.kt b/src/test/kotlin/org/cryptobiotic/eg/core/KotestGenerators.kt index 83256a9..14b7eb0 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/KotestGenerators.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/KotestGenerators.kt @@ -20,16 +20,10 @@ fun elementsModP(ctx: GroupContext): Arb { // 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 = arbitrary{ ctx.randomElementModQ() } - -/* fun elementsModQ(ctx: GroupContext, minimum: Int = 0): Arb = - Arb.byteArray(Arb.constant(ctx.MAX_BYTES_Q), Arb.byte()) - .map { ctx.randomElementModQ() } - - */ +fun elementsModQ(ctx: GroupContext): Arb = 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 @@ -43,7 +37,7 @@ fun validResiduesOfP(ctx: GroupContext): Arb = * accelerated using the default PowRadixOption in the GroupContext. */ fun elGamalKeypairs(ctx: GroupContext): Arb = - elementsModQ(ctx, minimum = 2).map { elGamalKeyPairFromSecret(it) } + elementsModQ(ctx).map { elGamalKeyPairFromSecret(it) } /** Generates arbitrary UInt256 values. */ fun uint256s(): Arb = Arb.byteArray(Arb.constant(32), Arb.byte()).map { UInt256(it) } diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/SchnorrTest.kt b/src/test/kotlin/org/cryptobiotic/eg/core/SchnorrTest.kt index 2111f01..b61f615 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/SchnorrTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/SchnorrTest.kt @@ -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 } diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/TestBase64.kt b/src/test/kotlin/org/cryptobiotic/eg/core/TestBase64.kt index 05fbad0..9564d7c 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/TestBase64.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/TestBase64.kt @@ -69,7 +69,7 @@ class TestBase64 { runTest { val context = productionGroup() val test = "1234567890".repeat(1000) - val e1 = assertFailsWith(block = { println(" convert = ${context.base64ToElementModQ(test)}") }) + assertFailsWith(block = { println(" convert = ${context.base64ToElementModQ(test)}") }) } } diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/ecgroup/TestElem.kt b/src/test/kotlin/org/cryptobiotic/eg/core/ecgroup/TestElem.kt index 8b96a1d..5bbf667 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/ecgroup/TestElem.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/ecgroup/TestElem.kt @@ -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 -> diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/TinyGroup.kt b/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/TinyGroup.kt index d456139..c4f63f5 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/TinyGroup.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/TinyGroup.kt @@ -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) @@ -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() @@ -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 { @@ -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() } @@ -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 { diff --git a/src/test/kotlin/org/cryptobiotic/eg/keyceremony/ShareEncryptDecryptTest.kt b/src/test/kotlin/org/cryptobiotic/eg/keyceremony/ShareEncryptDecryptTest.kt index 2b134d4..58cd3f4 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/keyceremony/ShareEncryptDecryptTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/keyceremony/ShareEncryptDecryptTest.kt @@ -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) diff --git a/src/test/kotlin/org/cryptobiotic/eg/publish/json/WebappDecryptionTest.kt b/src/test/kotlin/org/cryptobiotic/eg/publish/json/WebappDecryptionTest.kt index 2bc2b4b..66d5598 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/publish/json/WebappDecryptionTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/publish/json/WebappDecryptionTest.kt @@ -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) @@ -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) val org = ChallengeRequest(batchId, crs) val responses = org.publishJson().import(group) @@ -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) diff --git a/src/test/kotlin/org/cryptobiotic/gmp/VecTest.kt b/src/test/kotlin/org/cryptobiotic/gmp/VecTest.kt index ec7e2e8..424fcd2 100644 --- a/src/test/kotlin/org/cryptobiotic/gmp/VecTest.kt +++ b/src/test/kotlin/org/cryptobiotic/gmp/VecTest.kt @@ -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 }