Skip to content

Commit

Permalink
Simplify GroupContext.addQ and multP().
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnLCaron committed Apr 18, 2024
1 parent cba346a commit ac22834
Show file tree
Hide file tree
Showing 17 changed files with 89 additions and 69 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ data class ElGamalCiphertext(val pad: ElementModP, val data: ElementModP) {
fun decryptWithShares(publicKey: ElGamalPublicKey, shares: Iterable<ElementModP>): 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)
}
Expand Down
16 changes: 2 additions & 14 deletions src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<ElementModQ>.addQ(): ElementModQ
fun addQ(cues: Iterable<ElementModQ>): 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<ElementModP>.multP(): ElementModP
fun multP(pees: Iterable<ElementModP>): ElementModP

/** Computes G^e mod p, where G is our generator */
fun gPowP(exp: ElementModQ): ElementModP
Expand Down Expand Up @@ -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 {
Expand Down
17 changes: 5 additions & 12 deletions src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcGroupContext.kt
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -77,23 +76,17 @@ class EcGroupContext(val name: String, useNative: Boolean = true): GroupContext
return EcElementModQ(this, BigInteger.valueOf(i.toLong()))
}

override fun Iterable<ElementModQ>.addQ(): ElementModQ {
return addQQ(this)
override fun addQ(cues: Iterable<ElementModQ>): 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<ElementModP>.multP(): ElementModP {
// TODO what if this.isEmpty() ?
return this.reduce { a, b -> a * b }
override fun multP(pees: Iterable<ElementModP>): 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>): 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
Expand Down
25 changes: 19 additions & 6 deletions src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -149,12 +149,17 @@ class ProductionGroupContext(
else -> ProductionElementModQ(i.toBigInteger(), this)
}

override fun addQ(cues: Iterable<ElementModQ>): ElementModQ {
val sum = cues.fold(BigInteger.ZERO) { a, b -> a.plus((b as ProductionElementModQ).element) }
return ProductionElementModQ(sum.mod(q), this)
}

/*
override fun Iterable<ElementModQ>.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) {
Expand All @@ -170,12 +175,18 @@ class ProductionGroupContext(
return ProductionElementModQ(result, this@ProductionGroupContext)
}
*/

override fun multP(pees: Iterable<ElementModP>): ElementModP {
return pees.fold(ONE_MOD_P) { a, b -> a * b }
}

/*
override fun Iterable<ElementModP>.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) {
Expand All @@ -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)
Expand Down Expand Up @@ -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
}

Expand Down
12 changes: 6 additions & 6 deletions src/main/kotlin/org/cryptobiotic/eg/decrypt/CipherDecryptor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<PartialDecryption> = 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)

Expand Down Expand Up @@ -115,7 +115,7 @@ class CipherDecryptor(
challengeResponses: List<ElementModQ>, // 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)
}
Expand Down
6 changes: 3 additions & 3 deletions src/main/kotlin/org/cryptobiotic/eg/decrypt/Guardians.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ data class Guardians(val group : GroupContext, val guardians: List<Guardian>) {
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()) }
)
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/org/cryptobiotic/eg/encrypt/Encryptor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ fun PlaintextBallot.Contest.encryptContest(
val ciphertexts: List<ElGamalCiphertext> = encryptedSelections.map { it.ciphertext }
val ciphertextAccumulation: ElGamalCiphertext = ciphertexts.encryptedSum()?: 0.encrypt(jointPublicKey)
val nonces: Iterable<ElementModQ> = 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 )
}

Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/org/cryptobiotic/eg/preencrypt/Recorder.kt
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class Recorder(
val texts: List<ElGamalCiphertext> = selections.map { it.ciphertext }
val ciphertextAccumulation: ElGamalCiphertext = texts.encryptedSum()?: 0.encrypt(publicKey)
val nonces: Iterable<ElementModQ> = 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(
Expand Down Expand Up @@ -135,7 +135,7 @@ class Recorder(
val combinedNonces = mutableListOf<ElementModQ>()
repeat(nselections) { idx ->
val componentNonces : List<ElementModQ> = this.selectedVectors.map { it.nonces[idx] }
val aggNonce: ElementModQ = with(group) { componentNonces.addQ() }
val aggNonce: ElementModQ = group.addQ(componentNonces)
combinedNonces.add( aggNonce )
}

Expand Down
8 changes: 2 additions & 6 deletions src/test/kotlin/org/cryptobiotic/eg/core/GroupTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)))
}
}
}
Expand All @@ -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)))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,12 @@ internal class TinyGroupContext(
else
TinyElementModP(i, this)

override fun Iterable<ElementModQ>.addQ(): ElementModQ =
uIntToElementModQ(fold(0U) { a, b -> (a + b.getCompat(this@TinyGroupContext)) % q })
override fun addQ(cues: Iterable<ElementModQ>): ElementModQ =
uIntToElementModQ(cues.fold(0U) { a, b -> (a + b.getCompat(this@TinyGroupContext)) % q })

override fun Iterable<ElementModP>.multP(): ElementModP =
override fun multP(pees: Iterable<ElementModP>): 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions src/test/kotlin/org/cryptobiotic/eg/decrypt/LagrangeTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ class TestWorkflowEncryptDecrypt {
elGamalKeyPairFromRandom(group),
)
val pkeys: Iterable<ElementModP> = 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))
Expand All @@ -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)
Expand Down
30 changes: 30 additions & 0 deletions src/test/kotlin/org/cryptobiotic/util/MiscTests.kt
Original file line number Diff line number Diff line change
@@ -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<Int>()
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<Int>()
val sum2 = cues2.fold(1) { a, b -> a * b }
assertEquals(1, sum2)
}

}

0 comments on commit ac22834

Please sign in to comment.