From ac2283404788ce69649c5a2b03512b5303cf9e65 Mon Sep 17 00:00:00 2001 From: JohnLCaron Date: Thu, 18 Apr 2024 11:36:54 -0600 Subject: [PATCH] Simplify GroupContext.addQ and multP(). --- README.md | 2 +- .../cryptobiotic/eg/core/ElGamalCiphertext.kt | 2 +- .../org/cryptobiotic/eg/core/GroupContext.kt | 16 ++-------- .../eg/core/ecgroup/EcGroupContext.kt | 17 ++++------- .../cryptobiotic/eg/core/intgroup/IntGroup.kt | 25 ++++++++++++---- .../eg/decrypt/CipherDecryptor.kt | 12 ++++---- .../org/cryptobiotic/eg/decrypt/Guardians.kt | 6 ++-- .../org/cryptobiotic/eg/encrypt/Encryptor.kt | 2 +- .../preencrypt/DecryptPreencryptWithNonce.kt | 2 +- .../cryptobiotic/eg/preencrypt/Recorder.kt | 4 +-- .../org/cryptobiotic/eg/core/GroupTest.kt | 8 ++--- .../eg/core/intgroup/TinyGroup.kt | 8 ++--- .../eg/decrypt/EncryptDecryptTest.kt | 6 ++-- .../cryptobiotic/eg/decrypt/LagrangeTest.kt | 8 ++--- .../eg/workflow/FakeKeyCeremony.kt | 6 ++-- .../eg/workflow/TestWorkflowEncryptDecrypt.kt | 4 +-- .../kotlin/org/cryptobiotic/util/MiscTests.kt | 30 +++++++++++++++++++ 17 files changed, 89 insertions(+), 69 deletions(-) create mode 100644 src/test/kotlin/org/cryptobiotic/util/MiscTests.kt diff --git a/README.md b/README.md index 2d8eedc..67520d4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![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.6%25%20LOC%20(6958/7679)-blue) +![Coverage](https://img.shields.io/badge/coverage-90.7%25%20LOC%20(6944/7658)-blue) # ElectionGuard-Kotlin Elliptic Curve diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/ElGamalCiphertext.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ElGamalCiphertext.kt index 8762ed6..5c079f8 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ElGamalCiphertext.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ElGamalCiphertext.kt @@ -37,7 +37,7 @@ data class ElGamalCiphertext(val pad: ElementModP, val data: ElementModP) { fun decryptWithShares(publicKey: ElGamalPublicKey, shares: Iterable): Int? { val sharesList = shares.toList() val context = compatibleContextOrFail(pad, data, publicKey.key, *(sharesList.toTypedArray())) - val allSharesProductM: ElementModP = with(context) { sharesList.multP() } + val allSharesProductM: ElementModP = context.multP(sharesList) val decryptedValue: ElementModP = this.data / allSharesProductM return publicKey.dLog(decryptedValue) } diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt b/src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt index 9a68f6d..03ad9ec 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt @@ -91,13 +91,13 @@ interface GroupContext { * Computes the sum of the given elements, mod q; this can be faster than using the addition * operation for large numbers of inputs. */ - fun Iterable.addQ(): ElementModQ + fun addQ(cues: Iterable): ElementModQ /** * Computes the product of the given elements, mod p; this can be faster than using the * multiplication operation for large numbers of inputs. */ - fun Iterable.multP(): ElementModP + fun multP(pees: Iterable): ElementModP /** Computes G^e mod p, where G is our generator */ fun gPowP(exp: ElementModQ): ElementModP @@ -263,18 +263,6 @@ fun compatibleContextOrFail(vararg elements: Element): GroupContext { return headContext } -/** - * Computes the sum of the given elements, mod q; this can be faster than using the addition - * operation for large enough numbers of inputs. - */ -fun GroupContext.addQ(vararg elements: ElementModQ) = elements.asIterable().addQ() - -/** - * Computes the product of the given elements, mod p; this can be faster than using the - * multiplication operation for large enough numbers of inputs. - */ -fun GroupContext.multP(vararg elements: ElementModP) = elements.asIterable().multP() - fun GroupContext.showOpCountResults(where: String): String { val opCounts = this.getAndClearOpCounts() return buildString { 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 a4aeaa5..be72f83 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcGroupContext.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcGroupContext.kt @@ -1,6 +1,5 @@ package org.cryptobiotic.eg.core.ecgroup - import org.cryptobiotic.eg.core.* import java.math.BigInteger import java.util.concurrent.atomic.AtomicInteger @@ -77,23 +76,17 @@ class EcGroupContext(val name: String, useNative: Boolean = true): GroupContext return EcElementModQ(this, BigInteger.valueOf(i.toLong())) } - override fun Iterable.addQ(): ElementModQ { - return addQQ(this) + override fun addQ(cues: Iterable): ElementModQ { + val sum = cues.fold(BigInteger.ZERO) { a, b -> a.plus((b as EcElementModQ).element) } + return EcElementModQ(this, sum.mod(vecGroup.order)) } - override fun Iterable.multP(): ElementModP { - // TODO what if this.isEmpty() ? - return this.reduce { a, b -> a * b } + override fun multP(pees: Iterable): ElementModP { + return pees.fold(ONE_MOD_P) { a, b -> a * b } } override fun randomElementModP(minimum: Int) = EcElementModP(this, vecGroup.randomElement()) - fun addQQ(cues: Iterable): ElementModQ { - // TODO what if cues.isEmpty() ? - val sum = cues.fold(BigInteger.ZERO) { a, b -> a.plus((b as EcElementModQ).element) } - return EcElementModQ(this, sum.mod(vecGroup.order)) - } - override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false 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 a6e746c..5c7cea3 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroup.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroup.kt @@ -65,7 +65,7 @@ class ProductionGroupContext( override fun isProductionStrength() = true override fun toString() : String = name - override val ONE_MOD_P + override val ONE_MOD_P : ElementModP get() = oneModP override val G_MOD_P @@ -149,12 +149,17 @@ class ProductionGroupContext( else -> ProductionElementModQ(i.toBigInteger(), this) } + override fun addQ(cues: Iterable): ElementModQ { + val sum = cues.fold(BigInteger.ZERO) { a, b -> a.plus((b as ProductionElementModQ).element) } + return ProductionElementModQ(sum.mod(q), this) + } + + /* override fun Iterable.addQ(): ElementModQ { val input = iterator().asSequence().toList() - // TODO why not return 0 ? if (input.isEmpty()) { - throw ArithmeticException("addQ not defined on empty lists") + return ZERO_MOD_Q } if (input.count() == 1) { @@ -170,12 +175,18 @@ class ProductionGroupContext( return ProductionElementModQ(result, this@ProductionGroupContext) } + */ + + override fun multP(pees: Iterable): ElementModP { + return pees.fold(ONE_MOD_P) { a, b -> a * b } + } + + /* override fun Iterable.multP(): ElementModP { val input = iterator().asSequence().toList() - // TODO why not return 1 ? if (input.isEmpty()) { - throw ArithmeticException("multP not defined on empty lists") + return ONE_MOD_P } if (input.count() == 1) { @@ -191,6 +202,8 @@ class ProductionGroupContext( return ProductionElementModP(result, this@ProductionGroupContext) } + */ + override fun gPowP(exp: ElementModQ) = gModP powP exp override fun dLogG(p: ElementModP, maxResult: Int): Int? = dlogger.dLog(p, maxResult) @@ -278,7 +291,7 @@ open class ProductionElementModP(internal val element: BigInteger, val groupCont override fun isValidResidue(): Boolean { groupContext.opCounts.getOrPut("exp") { AtomicInteger(0) }.incrementAndGet() - val residue = this.element.modPow(groupContext.q, groupContext.p) == groupContext.ONE_MOD_P.element + val residue = this.element.modPow(groupContext.q, groupContext.p) == groupContext.oneModP.element return inBounds() && residue } diff --git a/src/main/kotlin/org/cryptobiotic/eg/decrypt/CipherDecryptor.kt b/src/main/kotlin/org/cryptobiotic/eg/decrypt/CipherDecryptor.kt index ce8af08..7fb554c 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/decrypt/CipherDecryptor.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/decrypt/CipherDecryptor.kt @@ -33,19 +33,19 @@ class CipherDecryptor( val decryptions = texts.mapIndexed { idx, text -> // lagrange weighted product of the shares, M = Prod(M_i^w_i) mod p; spec 2.0.0, eq 68 - val weightedProduct = with (group) { + val weightedProduct = group.multP( // for this idx, run over all the trustees partialDecryptions.mapIndexed { tidx, pds -> val trustee = decryptingTrustees[tidx] val lagrange = lagrangeCoordinates[trustee.id()]!! // buildLagrangeCoordinates() guarentees exists pds.partial[idx].Mi powP lagrange.lagrangeCoefficient - }.multP() - } + } + ) // compute the collective challenge, needed for the collective proof; spec 2.0.0 eq 70 val shares: List = partialDecryptions.map { it.partial[idx] } // for this text, one from each trustee - val a: ElementModP = with(group) { shares.map { it.a }.multP() } // Prod(ai) - val b: ElementModP = with(group) { shares.map { it.b }.multP() } // Prod(bi) + val a: ElementModP = group.multP(shares.map { it.a }) // Prod(ai) + val b: ElementModP = group.multP(shares.map { it.b }) // Prod(bi) val collectiveChallenge = text.collectiveChallenge(extendedBaseHash, publicKey, a, b, weightedProduct) @@ -115,7 +115,7 @@ class CipherDecryptor( challengeResponses: List, // across trustees for this decryption ): CipherDecryptionAndProof { // v = Sum(v_i mod q); spec 2.0.0 eq 76 - val response: ElementModQ = with(group) { challengeResponses.map { it }.addQ() } + val response: ElementModQ = group.addQ(challengeResponses) val proof = ChaumPedersenProof(decryption.collectiveChallenge.toElementModQ(group), response) return CipherDecryptionAndProof(decryption, proof) } diff --git a/src/main/kotlin/org/cryptobiotic/eg/decrypt/Guardians.kt b/src/main/kotlin/org/cryptobiotic/eg/decrypt/Guardians.kt index 312ad12..f51d550 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/decrypt/Guardians.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/decrypt/Guardians.kt @@ -34,9 +34,9 @@ data class Guardians(val group : GroupContext, val guardians: List) { val guardian = guardianMap[guardianId] ?: throw IllegalStateException("Guardians.getGexpP doesnt have guardian id = '$guardianId'") - with(group) { - guardians.map { calculateGexpPiAtL(it.guardianId, guardian.xCoordinate, it.coefficientCommitments()) }.multP() - } + group.multP( + guardians.map { calculateGexpPiAtL(it.guardianId, guardian.xCoordinate, it.coefficientCommitments()) } + ) } } diff --git a/src/main/kotlin/org/cryptobiotic/eg/encrypt/Encryptor.kt b/src/main/kotlin/org/cryptobiotic/eg/encrypt/Encryptor.kt index db42a74..7e518c2 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/encrypt/Encryptor.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/encrypt/Encryptor.kt @@ -191,7 +191,7 @@ fun PlaintextBallot.Contest.encryptContest( val ciphertexts: List = encryptedSelections.map { it.ciphertext } val ciphertextAccumulation: ElGamalCiphertext = ciphertexts.encryptedSum()?: 0.encrypt(jointPublicKey) val nonces: Iterable = encryptedSelections.map { it.selectionNonce } - val aggNonce: ElementModQ = with(group) { nonces.addQ() } + val aggNonce: ElementModQ = group.addQ(nonces) val proof = ciphertextAccumulation.makeChaumPedersen( totalVotedFor, // (ℓ in the spec) diff --git a/src/main/kotlin/org/cryptobiotic/eg/preencrypt/DecryptPreencryptWithNonce.kt b/src/main/kotlin/org/cryptobiotic/eg/preencrypt/DecryptPreencryptWithNonce.kt index 2e61639..887022c 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/preencrypt/DecryptPreencryptWithNonce.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/preencrypt/DecryptPreencryptWithNonce.kt @@ -98,7 +98,7 @@ class DecryptPreencryptWithNonce( val pv: PreEncryptedSelection = preeContest.selections.find { it.shortCode == selected.shortCode }!! pv.selectionNonces[idx] } - val aggNonce: ElementModQ = with(group) { componentNonces.addQ() } + val aggNonce: ElementModQ = group.addQ(componentNonces) combinedNonces.add( aggNonce ) } diff --git a/src/main/kotlin/org/cryptobiotic/eg/preencrypt/Recorder.kt b/src/main/kotlin/org/cryptobiotic/eg/preencrypt/Recorder.kt index 41323c5..91fd6d3 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/preencrypt/Recorder.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/preencrypt/Recorder.kt @@ -83,7 +83,7 @@ class Recorder( val texts: List = selections.map { it.ciphertext } val ciphertextAccumulation: ElGamalCiphertext = texts.encryptedSum()?: 0.encrypt(publicKey) val nonces: Iterable = selections.map { it.selectionNonce } - val aggNonce: ElementModQ = with(group) { nonces.addQ() } + val aggNonce: ElementModQ = group.addQ(nonces) val totalVotes = votedFor.map{ if (it) 1 else 0 }.sum() val proof = ciphertextAccumulation.makeChaumPedersen( @@ -135,7 +135,7 @@ class Recorder( val combinedNonces = mutableListOf() repeat(nselections) { idx -> val componentNonces : List = this.selectedVectors.map { it.nonces[idx] } - val aggNonce: ElementModQ = with(group) { componentNonces.addQ() } + val aggNonce: ElementModQ = group.addQ(componentNonces) combinedNonces.add( aggNonce ) } diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/GroupTest.kt b/src/test/kotlin/org/cryptobiotic/eg/core/GroupTest.kt index 4441e6f..5f0789b 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/GroupTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/GroupTest.kt @@ -291,14 +291,12 @@ class GroupTest { fun iterableAddition(context: GroupContext) { runTest { checkAll( - propTestFastConfig, elementsModQ(context), elementsModQ(context), elementsModQ(context) ) { a, b, c -> val expected = a + b + c - assertEquals(expected, context.addQ(a, b, c)) - assertEquals(expected, with(context) { listOf(a, b, c).addQ() }) + assertEquals(expected, context.addQ(listOf(a, b, c))) } } } @@ -311,14 +309,12 @@ class GroupTest { fun iterableMultiplication(context: GroupContext) { runTest { checkAll( - propTestFastConfig, validResiduesOfP(context), validResiduesOfP(context), validResiduesOfP(context) ) { a, b, c -> val expected = a * b * c - assertEquals(expected, context.multP(a, b, c)) - assertEquals(expected, with(context) { listOf(a, b, c).multP() }) + assertEquals(expected, context.multP(listOf(a, b, c))) } } } 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 5df7132..ba1e778 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/TinyGroup.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/TinyGroup.kt @@ -164,12 +164,12 @@ internal class TinyGroupContext( else TinyElementModP(i, this) - override fun Iterable.addQ(): ElementModQ = - uIntToElementModQ(fold(0U) { a, b -> (a + b.getCompat(this@TinyGroupContext)) % q }) + override fun addQ(cues: Iterable): ElementModQ = + uIntToElementModQ(cues.fold(0U) { a, b -> (a + b.getCompat(this@TinyGroupContext)) % q }) - override fun Iterable.multP(): ElementModP = + override fun multP(pees: Iterable): ElementModP = uIntToElementModP( - fold(1UL) { a, b -> (a * b.getCompat(this@TinyGroupContext).toULong()) % p }.toUInt() + pees.fold(1UL) { a, b -> (a * b.getCompat(this@TinyGroupContext).toULong()) % p }.toUInt() ) override fun gPowP(exp: ElementModQ): ElementModP = gModP powP exp diff --git a/src/test/kotlin/org/cryptobiotic/eg/decrypt/EncryptDecryptTest.kt b/src/test/kotlin/org/cryptobiotic/eg/decrypt/EncryptDecryptTest.kt index ce8e5ab..1e6c64f 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/decrypt/EncryptDecryptTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/decrypt/EncryptDecryptTest.kt @@ -76,13 +76,13 @@ fun encryptDecrypt( pd.partial[0] } - val weightedProduct = with(group) { + val weightedProduct = group.multP( shares.mapIndexed { idx, it -> val trustee = available[idx] val coeff = lagrangeCoefficients[trustee.id()] ?: throw IllegalArgumentException() it.Mi powP coeff - }.multP() // eq 7 - } + } // eq 7 + ) val bm = evote.data / weightedProduct val expected = publicKey powP vote.toElementModQ(group) assertEquals(expected, bm) diff --git a/src/test/kotlin/org/cryptobiotic/eg/decrypt/LagrangeTest.kt b/src/test/kotlin/org/cryptobiotic/eg/decrypt/LagrangeTest.kt index 2f2aad1..ae0acc1 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/decrypt/LagrangeTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/decrypt/LagrangeTest.kt @@ -112,13 +112,13 @@ class LagrangeTest { val lagrangeCoefficients = available.associate { it.id to computeLagrangeCoefficient(group, it.xCoordinate, present) } lagrangeCoefficients.values.forEach { assertTrue( it.inBounds()) } - val weightedSum = with(group) { + val weightedSum = group.addQ( trustees.map { - assertTrue( it.computeSecretKeyShare().inBounds()) + assertTrue(it.computeSecretKeyShare().inBounds()) val coeff = lagrangeCoefficients[it.id] ?: throw IllegalArgumentException() it.computeSecretKeyShare() * coeff - }.addQ() // eq 7 - } + } + ) // eq 7 var weightedSum2 = group.ZERO_MOD_Q trustees.forEach { diff --git a/src/test/kotlin/org/cryptobiotic/eg/workflow/FakeKeyCeremony.kt b/src/test/kotlin/org/cryptobiotic/eg/workflow/FakeKeyCeremony.kt index 40bc6ed..28f471e 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/workflow/FakeKeyCeremony.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/workflow/FakeKeyCeremony.kt @@ -145,13 +145,13 @@ fun testDoerreDecrypt(group: GroupContext, pd.partial[0] } - val weightedProduct = with(group) { + val weightedProduct = group.multP( shares.mapIndexed { idx, it -> val trustee = available[idx] val coeff = lagrangeCoefficients[trustee.id()] ?: throw IllegalArgumentException() it.Mi powP coeff - }.multP() // eq 7 - } + } // eq 7 + ) val bm = evote.data / weightedProduct val expected = publicKey powP vote.toElementModQ(group) assertEquals(expected, bm) diff --git a/src/test/kotlin/org/cryptobiotic/eg/workflow/TestWorkflowEncryptDecrypt.kt b/src/test/kotlin/org/cryptobiotic/eg/workflow/TestWorkflowEncryptDecrypt.kt index 4a22e63..c9532c4 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/workflow/TestWorkflowEncryptDecrypt.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/workflow/TestWorkflowEncryptDecrypt.kt @@ -99,7 +99,7 @@ class TestWorkflowEncryptDecrypt { elGamalKeyPairFromRandom(group), ) val pkeys: Iterable = trustees.map { it.publicKey.key} - val publicKey = ElGamalPublicKey(with (group) { pkeys.multP()} ) + val publicKey = ElGamalPublicKey( group.multP(pkeys) ) val vote = 1 val evote1 = vote.encrypt(publicKey, group.randomElementModQ(minimum = 1)) @@ -112,7 +112,7 @@ class TestWorkflowEncryptDecrypt { //decrypt val shares = trustees.map { eAccum.pad powP it.secretKey.key } - val allSharesProductM: ElementModP = with (group) { shares.multP() } + val allSharesProductM: ElementModP = group.multP(shares) val decryptedValue: ElementModP = eAccum.data / allSharesProductM val dlogM: Int = publicKey.dLog(decryptedValue)?: throw RuntimeException("dlog error") assertEquals(3, dlogM) diff --git a/src/test/kotlin/org/cryptobiotic/util/MiscTests.kt b/src/test/kotlin/org/cryptobiotic/util/MiscTests.kt new file mode 100644 index 0000000..0905c5b --- /dev/null +++ b/src/test/kotlin/org/cryptobiotic/util/MiscTests.kt @@ -0,0 +1,30 @@ +package org.cryptobiotic.util + +import kotlin.test.Test +import kotlin.test.assertEquals + +class MiscTests { + + @Test + fun testFoldAdd() { + val cues = listOf(1,2,3) + val sum = cues.fold(0) { a, b -> a + b } + assertEquals(6, sum) + + val cues2 = emptyList() + val sum2 = cues2.fold(0) { a, b -> a + b } + assertEquals(0, sum2) + } + + @Test + fun testFoldMul() { + val cues = listOf(1,2,3,4) + val sum = cues.fold(1) { a, b -> a * b } + assertEquals(24, sum) + + val cues2 = emptyList() + val sum2 = cues2.fold(1) { a, b -> a * b } + assertEquals(1, sum2) + } + +} \ No newline at end of file