diff --git a/README.md b/README.md index 2d8eedc..fd1c21c 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(6931/7639)-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/ElGamalKeys.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ElGamalKeys.kt index 009ac1d..ecb7eec 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ElGamalKeys.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ElGamalKeys.kt @@ -60,7 +60,7 @@ class ElGamalPublicKey(inputKey: ElementModP) { class ElGamalSecretKey(val key: ElementModQ) { init { if (key < key.context.TWO_MOD_Q) - throw ArithmeticException("secret key must be in [2, Q)") + throw ArithmeticException("secret key must be in [2, Q) group=${key.context.javaClass.name}") } val negativeKey: ElementModQ = -key val context: GroupContext 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/EcElementModP.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModP.kt index 4c7f5e5..bb5a091 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModP.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModP.kt @@ -24,7 +24,7 @@ class EcElementModP(val group: EcGroupContext, val ec: VecElementP): ElementModP } // TODO what does it mean to be in bounds ?? - override fun inBounds(): Boolean = true // TODO("Not yet implemented") + override fun inBounds(): Boolean = true // TODO check this override fun isValidResidue(): Boolean { 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/election/ContestData.kt b/src/main/kotlin/org/cryptobiotic/eg/election/ContestData.kt index de31278..6ebaf6a 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/election/ContestData.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/election/ContestData.kt @@ -86,97 +86,89 @@ data class ContestData( trialSize = trialContestDataBA.size trialSizes.add(trialSize) } - logger.debug{ "encodedData = $trialContestData trialSizes = $trialSizes" } + logger.debug { "encodedData = $trialContestData trialSizes = $trialSizes" } return trialContestDataBA.encryptContestData(publicKey, extendedBaseHash, contestId, contestIndex, ballotNonce) } companion object { val logger = KotlinLogging.logger("ContestData") - private const val BLOCK_SIZE : Int = 32 - private const val CHOP_WRITE_INS : Int = 30 + private const val BLOCK_SIZE: Int = 32 + private const val CHOP_WRITE_INS: Int = 30 const val label = "share_enc_keys" const val contestDataLabel = "contest_data" } } -// TODO can be replaced by ByteArray.encryptToHashedElGamal() fun ByteArray.encryptContestData( publicKey: ElGamalPublicKey, // aka K extendedBaseHash: UInt256, // aka He contestId: String, // aka Λ contestIndex: Int, // ind_c(Λ) - ballotNonce: UInt256): HashedElGamalCiphertext { - - // D = D_1 ∥ D_2 ∥ · · · ∥ D_bD ; (spec 2.0, eq 49) - val messageBlocks: List = - this.toList() - .chunked(32) { block -> - // pad each block of the message to 32 bytes - val result = ByteArray(32) { 0 } - block.forEachIndexed { index, byte -> result[index] = byte } - UInt256(result) - } + ballotNonce: UInt256 +): HashedElGamalCiphertext { val group = compatibleContextOrFail(publicKey.key) - - // ξ = H(HE ; 0x20, ξB , indc (Λ), “contest data”) (spec 2.0, eq 50) - val contestDataNonce = hashFunction(extendedBaseHash.bytes, 0x20.toByte(), ballotNonce, contestIndex, ContestData.contestDataLabel) - - // ElectionGuard spec: (α, β) = (g^ξ mod p, K^ξ mod p); by encrypting a zero, we achieve exactly this - val (alpha, beta) = 0.encrypt(publicKey, contestDataNonce.toElementModQ(group)) - // k = H(HE ; 0x22, K, α, β) ; (spec 2.0, eq 51) - val kdfKey = hashFunction(extendedBaseHash.bytes, 0x22.toByte(), publicKey, alpha, beta) - - // TODO check - // context = b(”contest_data”) ∥ b(Λ). - val context = "${ContestData.contestDataLabel}$contestId" - val kdf = KDF(kdfKey, ContestData.label, context, this.size * 8) // TODO is this eq(52) ?? - - val k0 = kdf[0] - val c0 = alpha.byteArray() // (53) - val encryptedBlocks = messageBlocks.mapIndexed { i, p -> (p xor kdf[i + 1]).bytes }.toTypedArray() - val c1 = concatByteArrays(*encryptedBlocks) // (54) - val c2 = (c0 + c1).hmacSha256(k0) // ; eq (55) TODO can we use hmacFunction() ?? - - return HashedElGamalCiphertext(alpha, c1, c2, this.size) + val contestDataNonce = + hashFunction(extendedBaseHash.bytes, 0x20.toByte(), ballotNonce, contestIndex, ContestData.contestDataLabel) + + return this.encryptToHashedElGamal( + group, + publicKey, + extendedBaseHash, + 0x22.toByte(), + ContestData.label, + "${ContestData.contestDataLabel}$contestId", + contestDataNonce.toElementModQ(group), + ) } -// TODO could be used in Encryptor ?? fun makeContestData( - votesAllowed: Int, + contestLimit: Int, + optionLimit: Int, selections: List, writeIns: List -): ContestData { +): Pair { + // count the number of votes val votedFor = mutableListOf() - for (selection in selections) { + var selectionOvervote = false + selections.forEach { selection -> if (selection.vote > 0) { votedFor.add(selection.sequenceOrder) + if (selection.vote > optionLimit) { + selectionOvervote = true + } } } + // Compute the contest status val totalVotedFor = votedFor.size + writeIns.size val status = if (totalVotedFor == 0) ContestDataStatus.null_vote - else if (totalVotedFor < votesAllowed) ContestDataStatus.under_vote - else if (totalVotedFor > votesAllowed) ContestDataStatus.over_vote - else ContestDataStatus.normal + else if (selectionOvervote || totalVotedFor > contestLimit) ContestDataStatus.over_vote + else if (totalVotedFor < contestLimit) ContestDataStatus.under_vote + else ContestDataStatus.normal - return ContestData( + val votes = if (status == ContestDataStatus.over_vote) 0 else totalVotedFor + + val contestData = ContestData( if (status == ContestDataStatus.over_vote) votedFor else emptyList(), writeIns, status ) + + return Pair(contestData, votes) } fun HashedElGamalCiphertext.decryptWithBetaToContestData( publicKey: ElGamalPublicKey, // aka K extendedBaseHash: UInt256, // aka He contestId: String, // aka Λ - beta : ElementModP) : Result { + beta: ElementModP +): Result { - val ba: ByteArray = this.decryptContestData(publicKey, extendedBaseHash, contestId, c0, beta) ?: - return Err( "decryptWithBetaToContestData did not succeed") + val ba: ByteArray = this.decryptContestData(publicKey, extendedBaseHash, contestId, c0, beta) + ?: return Err("decryptWithBetaToContestData did not succeed") return ba.decodeToContestData() } @@ -185,13 +177,15 @@ fun HashedElGamalCiphertext.decryptWithNonceToContestData( extendedBaseHash: UInt256, // aka He contestId: String, // aka Λ contestIndex: Int, - ballotNonce: UInt256) : Result { + ballotNonce: UInt256 +): Result { val group = compatibleContextOrFail(publicKey.key) - val contestDataNonce = hashFunction(extendedBaseHash.bytes, 0x20.toByte(), ballotNonce, contestIndex, ContestData.contestDataLabel) + val contestDataNonce = + hashFunction(extendedBaseHash.bytes, 0x20.toByte(), ballotNonce, contestIndex, ContestData.contestDataLabel) val (alpha, beta) = 0.encrypt(publicKey, contestDataNonce.toElementModQ(group)) - val ba: ByteArray = this.decryptContestData(publicKey, extendedBaseHash, contestId, alpha, beta) ?: - return Err( "decryptWithNonceToContestData did not succeed") + val ba: ByteArray = this.decryptContestData(publicKey, extendedBaseHash, contestId, alpha, beta) + ?: return Err("decryptWithNonceToContestData did not succeed") return ba.decodeToContestData() } @@ -199,42 +193,24 @@ fun HashedElGamalCiphertext.decryptWithSecretKey( publicKey: ElGamalPublicKey, // aka K extendedBaseHash: UInt256, // aka He contestId: String, // aka Λ - secretKey: ElGamalSecretKey): ByteArray? = decryptContestData(publicKey, extendedBaseHash, contestId, c0, c0 powP secretKey.key) + secretKey: ElGamalSecretKey +): ByteArray? = decryptContestData(publicKey, extendedBaseHash, contestId, c0, c0 powP secretKey.key) -// TODO can be replaced by HashedElGamalCiphertext.decryptToByteArray() fun HashedElGamalCiphertext.decryptContestData( publicKey: ElGamalPublicKey, // aka K extendedBaseHash: UInt256, // aka He contestId: String, // aka Λ alpha: ElementModP, - beta: ElementModP): ByteArray? { - - // k = H(HE ; 22, K, α, β). (51) - val kdfKey = hashFunction(extendedBaseHash.bytes, 0x22.toByte(), publicKey, alpha, beta) - - // context = b(”contest_data”) ∥ b(Λ). - val context = "${ContestData.contestDataLabel}$contestId" - val kdf = KDF(kdfKey, ContestData.label, context, numBytes * 8) // TODO check this (86, 87) ?? - val k0 = kdf[0] - - val expectedHmac = (c0.byteArray() + c1).hmacSha256(k0) // TODO use hmacFunction() ? - - if (expectedHmac != c2) { - ContestData.logger.error { "HashedElGamalCiphertext decryptContestData failure: HMAC doesn't match" } - return null - } - - val ciphertextBlocks = c1.toList().chunked(32) { it.toByteArray().toUInt256safe() } // eq 88 - val plaintextBlocks = ciphertextBlocks.mapIndexed { i, c -> (c xor kdf[i + 1]).bytes }.toTypedArray() - val plaintext = concatByteArrays(*plaintextBlocks) // eq 89 - - return if (plaintext.size == numBytes) { - plaintext - } else { - // Truncate trailing values, which should be zeros. - // No need to check, because we've already validated the HMAC on the data. - plaintext.copyOfRange(0, numBytes) - } + beta: ElementModP +): ByteArray? { + + return this.decryptToByteArray( + publicKey, + extendedBaseHash, + 0x22.toByte(), + ContestData.label, + "${ContestData.contestDataLabel}$contestId", + alpha, + beta, + ) } - - diff --git a/src/main/kotlin/org/cryptobiotic/eg/encrypt/Encryptor.kt b/src/main/kotlin/org/cryptobiotic/eg/encrypt/Encryptor.kt index db42a74..10b4733 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/encrypt/Encryptor.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/encrypt/Encryptor.kt @@ -94,34 +94,16 @@ class Encryptor( optionLimit: Int, ballotNonce: UInt256, ): PendingEncryptedBallot.Contest { - val ballotSelections = this.selections.associateBy { it.selectionId } - - // count the number of votes - val votedFor = mutableListOf() - var selectionOvervote = false - for (mselection: ManifestIF.Selection in mcontest.selections) { - val plaintextSelection = ballotSelections[mselection.selectionId] // Find the plaintext selection matching the manifest selectionId. - if (plaintextSelection != null && plaintextSelection.vote > 0) { - votedFor.add(plaintextSelection.sequenceOrder) - if (plaintextSelection.vote > optionLimit) { - selectionOvervote = true - } - } - } - // Compute the contest status - val totalVotedFor = votedFor.size + this.writeIns.size - val status = if (totalVotedFor == 0) ContestDataStatus.null_vote - else if (selectionOvervote || totalVotedFor > contestLimit) ContestDataStatus.over_vote - else if (totalVotedFor < contestLimit) ContestDataStatus.under_vote - else ContestDataStatus.normal + val (contestData, votes) = makeContestData(contestLimit, optionLimit, this.selections, this.writeIns) + val ballotSelections = this.selections.associateBy { it.selectionId } val encryptedSelections = mutableListOf() for (mselection: ManifestIF.Selection in mcontest.selections) { var plaintextSelection = ballotSelections[mselection.selectionId] // Set vote to zero if not in manifest or this contest is overvoted. See 3.3.3 "Overvotes". - if (plaintextSelection == null || (status == ContestDataStatus.over_vote)) { + if (plaintextSelection == null || (contestData.status == ContestDataStatus.over_vote)) { plaintextSelection = makeZeroSelection(mselection.selectionId, mselection.sequenceOrder) } encryptedSelections.add( plaintextSelection.encryptSelection( @@ -131,21 +113,20 @@ class Encryptor( )) } - val contestData = ContestData( - if (status == ContestDataStatus.over_vote) votedFor else emptyList(), - this.writeIns, - status - ) - - val contestDataEncrypted = contestData.encrypt(jointPublicKey, extendedBaseHash, mcontest.contestId, - mcontest.sequenceOrder, ballotNonce, contestLimit) + val contestDataEncrypted = contestData.encrypt( + jointPublicKey, + extendedBaseHash, + mcontest.contestId, + mcontest.sequenceOrder, + ballotNonce, + contestLimit) return this.encryptContest( group, jointPublicKey, extendedBaseHash, contestLimit, - if (status == ContestDataStatus.over_vote) 0 else totalVotedFor, + votes, encryptedSelections.sortedBy { it.sequenceOrder }, contestDataEncrypted, ) @@ -191,7 +172,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/DecryptionWithNonceTest.kt b/src/test/kotlin/org/cryptobiotic/eg/decrypt/DecryptionWithNonceTest.kt index 6991b18..b7b0ff5 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/decrypt/DecryptionWithNonceTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/decrypt/DecryptionWithNonceTest.kt @@ -90,7 +90,7 @@ class DecryptionWithNonceTest { // contestData matches ballot.contests.forEach { orgContest -> val mcontest = electionRecord.manifest().contests.find { it.contestId == orgContest.contestId }!! - val orgContestData = makeContestData(mcontest.contestSelectionLimit, orgContest.selections, orgContest.writeIns) + val (orgContestData, _) = makeContestData(mcontest.contestSelectionLimit, mcontest.optionSelectionLimit, orgContest.selections, orgContest.writeIns) val dcontest = decryptedBallot.contests.find { it.contestId == orgContest.contestId }!! assertEquals(dcontest.writeIns, orgContestData.writeIns) diff --git a/src/test/kotlin/org/cryptobiotic/eg/decrypt/EncryptDecryptBallotTest.kt b/src/test/kotlin/org/cryptobiotic/eg/decrypt/EncryptDecryptBallotTest.kt index 869270f..4051995 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/decrypt/EncryptDecryptBallotTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/decrypt/EncryptDecryptBallotTest.kt @@ -149,8 +149,8 @@ fun testEncryptDecryptVerify( ballot.contests.forEach { orgContest -> val mcontest = manifest.contests.find { it.contestId == orgContest.contestId }!! - val orgContestData = - makeContestData(mcontest.contestSelectionLimit, orgContest.selections, orgContest.writeIns) + val (orgContestData, _) = + makeContestData(mcontest.contestSelectionLimit, mcontest.optionSelectionLimit, orgContest.selections, orgContest.writeIns) val dcontest = decryptedBallot.contests.find { it.contestId == orgContest.contestId } assertNotNull(dcontest) 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