From 4507d8d166e6e8fa567a6804db1fd991fcb6f815 Mon Sep 17 00:00:00 2001 From: JohnLCaron Date: Tue, 9 Apr 2024 15:49:14 -0600 Subject: [PATCH] Add tests to increase coverage. Remove Consumer.readDecryptedTallyFromFile, iterateEncryptedBallotsFromDir. --- README.md | 9 +- htmlReport/index.html | 50 ++++----- .../org/cryptobiotic/eg/publish/Consumer.kt | 4 - .../eg/publish/json/ConsumerJson.kt | 27 ----- .../eg/publish/json/WebappDecryptionJson.kt | 4 +- src/test/data/workflow/allAvailableEc.zip | Bin 374068 -> 374068 bytes .../eg/publish/json/KeyCeremonyJsonTest.kt | 95 ++++++++++++++++++ .../eg/publish/json/PublicKeysTest.kt | 52 ---------- .../eg/publish/json/ReadIteratorsTest.kt | 46 --------- .../eg/publish/json/WebappDecryptionTest.kt | 50 ++++++++- 10 files changed, 174 insertions(+), 163 deletions(-) create mode 100644 src/test/kotlin/org/cryptobiotic/eg/publish/json/KeyCeremonyJsonTest.kt delete mode 100644 src/test/kotlin/org/cryptobiotic/eg/publish/json/PublicKeysTest.kt delete mode 100644 src/test/kotlin/org/cryptobiotic/eg/publish/json/ReadIteratorsTest.kt diff --git a/README.md b/README.md index 2106ab9..19dba84 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ -[![License](https://img.shields.io/github/license/JohnLCaron/egk-ec)](https://github.com/JohnLCaron/egk-ec/main/LICENSE.txt) +[![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-89.1%25%20(6819/7649)-blue) +[![Coverage](https://img.shields.io/badge/coverage-90.2%25%20LOC%20(6875/7623)-blue)](https://github.com/JohnLCaron/egk-ec/blob/main/htmlReport/index.html) # ElectionGuard-Kotlin Elliptic Curve - -_last update 04/08/2024_ +_last update 04/09/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), @@ -26,8 +25,6 @@ See [EGK EC mixnet](https://github.com/JohnLCaron/egk-ec-mixnet) for an implemen See [EGK webapps](https://github.com/JohnLCaron/egk-webapps) for HTTP client/server applications that use this library to allow remote workflows. -Currently we have 89.1% (6819/7649) LOC test coverage. - ## Documentation * [Getting Started](docs/GettingStarted.md) * [Workflow and Command Line Programs](docs/CommandLineInterface.md) diff --git a/htmlReport/index.html b/htmlReport/index.html index 7aeb163..26f95d4 100644 --- a/htmlReport/index.html +++ b/htmlReport/index.html @@ -44,34 +44,34 @@

Overall Coverage Summary

all classes - 81.4% + 83.1% - (464/570) + (473/569) - 85.7% + 87.1% - (1249/1458) + (1266/1454) - 65.9% + 66.5% - (1787/2710) + (1794/2696) - 89.2% + 90.2% - (6826/7655) + (6875/7623) @@ -106,34 +106,34 @@

Coverage Breakdown

org.cryptobiotic.eg.cli - 60.9% + 60.2% - (81/133) + (80/133) - 69.5% + 69% - (141/203) + (140/203) - 69.3% + 69% - (201/290) + (200/290) - 90% + 89.8% - (1246/1385) + (1244/1385) @@ -491,34 +491,34 @@

Coverage Breakdown

org.cryptobiotic.eg.publish.json - 86.4% + 93.8% - (127/147) + (137/146) - 87.3% + 94.4% - (268/307) + (286/303) - 55.7% + 59.3% - (246/442) + (254/428) - 87.7% + 93.9% - (1157/1319) + (1208/1287) @@ -649,7 +649,7 @@

Coverage Breakdown

diff --git a/src/main/kotlin/org/cryptobiotic/eg/publish/Consumer.kt b/src/main/kotlin/org/cryptobiotic/eg/publish/Consumer.kt index 9ff7088..03a0a4e 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/publish/Consumer.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/publish/Consumer.kt @@ -31,7 +31,6 @@ interface Consumer { fun readTallyResult(): Result fun readEncryptedTallyFromFile(filename: String): Result fun readDecryptionResult(): Result - fun readDecryptedTallyFromFile(filename: String): Result /** Are there any encrypted ballots? */ fun hasEncryptedBallots() : Boolean @@ -53,9 +52,6 @@ interface Consumer { fun iterateDecryptedBallots(ballotOverrideDir: String? = null): Iterable //// may be outside the election record - /** read encrypted ballots in given directory. */ - @Deprecated("to be removed") - fun iterateEncryptedBallotsFromDir(ballotDir: String, filter : Predicate? ): Iterable // TODO remove? /** read plaintext ballots in given directory, private data. */ fun iteratePlaintextBallots(ballotDir: String, filter : ((PlaintextBallot) -> Boolean)? ): Iterable /** read trustee in given directory for given guardianId, private data. */ diff --git a/src/main/kotlin/org/cryptobiotic/eg/publish/json/ConsumerJson.kt b/src/main/kotlin/org/cryptobiotic/eg/publish/json/ConsumerJson.kt index 6f88ee8..c32feac 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/publish/json/ConsumerJson.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/publish/json/ConsumerJson.kt @@ -136,23 +136,6 @@ class ConsumerJson(val topDir: String, usegroup: GroupContext? = null) : Consume ) } - override fun readDecryptedTallyFromFile(filename: String): Result { - val errs = ErrorMessages("DecryptedTallyFromFile '$filename'") - val decryptedTallyPath = Path.of(filename) - if (!Files.exists(decryptedTallyPath)) { - return errs.add("file does not exist ") - } - return try { - fileSystemProvider.newInputStream(decryptedTallyPath, StandardOpenOption.READ).use { inp -> - val json = jsonReader.decodeFromStream(inp) - val tally = json.import(group, errs) - if (errs.hasErrors()) Err(errs) else Ok(tally!!) - } - } catch (t: Throwable) { - errs.add("Exception= ${t.message} ${t.stackTraceToString()}") - } - } - ////////////////////////////////////////////////////////////////////////////////////////////////// override fun hasEncryptedBallots(): Boolean { @@ -179,16 +162,6 @@ class ConsumerJson(val topDir: String, usegroup: GroupContext? = null) : Consume return Iterable { DeviceIterator(devices.iterator(), filter) } } - // TODO - - override fun iterateEncryptedBallotsFromDir(ballotDir: String, filter: Predicate? ): Iterable { - val path = fileSystem.getPath(ballotDir) - if (!Files.exists(path)) { - return emptyList() - } - return Iterable { EncryptedBallotFileIterator(path, filter) } - } - ///////////////////////////////////////////////////////////////////////////////////// override fun encryptingDevices(): List { diff --git a/src/main/kotlin/org/cryptobiotic/eg/publish/json/WebappDecryptionJson.kt b/src/main/kotlin/org/cryptobiotic/eg/publish/json/WebappDecryptionJson.kt index ff2ce8f..eab640a 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/publish/json/WebappDecryptionJson.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/publish/json/WebappDecryptionJson.kt @@ -47,9 +47,9 @@ data class DecryptRequestJson( val texts: List ) -fun DecryptRequest.publishJson() = DecryptRequestJson( this.texts.map { it.publishJson() } ) +fun DecryptRequest.publishJson() = DecryptRequestJson( this.texts.map { it.publishJson() } ) -fun DecryptRequestJson.import(group: GroupContext): Result< List, String> { +fun DecryptRequestJson.import(group: GroupContext): Result, String> { val responses = this.texts.map { it.import(group) } val allgood = responses.map { it != null }.reduce { a, b -> a && b } diff --git a/src/test/data/workflow/allAvailableEc.zip b/src/test/data/workflow/allAvailableEc.zip index 463fbca961d883be880099ae2b2efa8810d9106b..e3b184c0348e626bc866c9f53195a189f1a113d3 100644 GIT binary patch delta 2467 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 SOJRq?=w_^cs<5pYQvL^Y;!bz~ diff --git a/src/test/kotlin/org/cryptobiotic/eg/publish/json/KeyCeremonyJsonTest.kt b/src/test/kotlin/org/cryptobiotic/eg/publish/json/KeyCeremonyJsonTest.kt new file mode 100644 index 0000000..02b8a5b --- /dev/null +++ b/src/test/kotlin/org/cryptobiotic/eg/publish/json/KeyCeremonyJsonTest.kt @@ -0,0 +1,95 @@ +package org.cryptobiotic.eg.publish.json + +import org.cryptobiotic.eg.core.* +import org.cryptobiotic.util.ErrorMessages +import org.cryptobiotic.eg.core.productionGroup + +import io.kotest.property.Arb +import io.kotest.property.arbitrary.int +import io.kotest.property.arbitrary.single +import io.kotest.property.arbitrary.string +import io.kotest.property.checkAll +import org.cryptobiotic.eg.core.ecgroup.EcGroupContext +import org.cryptobiotic.eg.core.intgroup.tinyGroup +import org.cryptobiotic.eg.keyceremony.EncryptedKeyShare +import org.cryptobiotic.eg.keyceremony.KeyShare +import org.cryptobiotic.eg.keyceremony.PublicKeys +import kotlin.test.* + +class KeyCeremonyJsonTest { + val groups = listOf( + tinyGroup(), + productionGroup("Integer3072"), + productionGroup("Integer4096"), + EcGroupContext("P-256") + ) + + @Test + fun testPublicKeysRoundtrip() { + groups.forEach { testPublicKeysRoundtrip(it) } + } + + fun testPublicKeysRoundtrip(group: GroupContext) { + runTest { + checkAll( + iterations = 33, + Arb.string(minSize = 3), + Arb.int(min = 1, max = 10), + Arb.int(min = 1, max = 10), + ) { id, xcoord, quota, -> + + val proofs = mutableListOf() + repeat(quota) { + val kp = elGamalKeypairs(group).single() + val nonce = elementsModQ(group).single() + proofs.add(kp.schnorrProof(xcoord, it, nonce)) + } + val publicKey = PublicKeys(id, xcoord, proofs) + assertEquals(publicKey, publicKey.publishJson().import(group, ErrorMessages("round"))) + assertEquals(publicKey, jsonRoundTrip(publicKey.publishJson()).import(group, ErrorMessages("trip"))) + } + } + } + + @Test + fun testKeyShareJsonRoundtrip() { + groups.forEach { testKeyShareJsonRoundtrip(it) } + } + + fun testKeyShareJsonRoundtrip(group: GroupContext) { + runTest { + checkAll( + iterations = 33, + Arb.int(min = 1, max = 10), + Arb.string(minSize = 3), + Arb.string(minSize = 3), + ) { id, owner, secretShareFor, -> + val kshare = KeyShare(id, owner, secretShareFor, group.randomElementModQ(2)) + assertEquals(kshare, kshare.publishJson().import(group)) + } + } + } + + @Test + fun testKeyEncryptedShareJsonRoundtrip() { + groups.forEach { testEncryptedKeyShareJsonRoundtrip(it) } + } + + fun testEncryptedKeyShareJsonRoundtrip(group: GroupContext) { + runTest { + checkAll( + iterations = 33, + Arb.int(min = 1, max = 10), + Arb.string(minSize = 3), + Arb.string(minSize = 3), + ) { id, owner, secretShareFor, -> + // val ownerXcoord: Int, // guardian i (owns the polynomial Pi) xCoordinate + // val polynomialOwner: String, // guardian i (owns the polynomial Pi) + // val secretShareFor: String, // guardian l with coordinate ℓ + // val encryptedCoordinate: HashedElGamalCiphertext, // El(Pi_(ℓ)), spec 2.0, eq 18 + val eshare = EncryptedKeyShare(id, owner, secretShareFor, generateHashedCiphertext(group)) + assertEquals(eshare, eshare.publishJson().import(group)) + } + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/org/cryptobiotic/eg/publish/json/PublicKeysTest.kt b/src/test/kotlin/org/cryptobiotic/eg/publish/json/PublicKeysTest.kt deleted file mode 100644 index 0bfd069..0000000 --- a/src/test/kotlin/org/cryptobiotic/eg/publish/json/PublicKeysTest.kt +++ /dev/null @@ -1,52 +0,0 @@ -package org.cryptobiotic.eg.publish.json - -import org.cryptobiotic.eg.core.* -import org.cryptobiotic.util.ErrorMessages -import org.cryptobiotic.eg.core.productionGroup - -import io.kotest.property.Arb -import io.kotest.property.arbitrary.int -import io.kotest.property.arbitrary.single -import io.kotest.property.arbitrary.string -import io.kotest.property.checkAll -import org.cryptobiotic.eg.core.ecgroup.EcGroupContext -import org.cryptobiotic.eg.core.intgroup.ProductionMode -import org.cryptobiotic.eg.core.intgroup.tinyGroup -import org.cryptobiotic.eg.keyceremony.PublicKeys -import kotlin.test.* - -class PublicKeysTest { - val groups = listOf( - tinyGroup(), - productionGroup("Integer3072"), - productionGroup("Integer4096"), - EcGroupContext("P-256") - ) - - @Test - fun testRoundtrip() { - groups.forEach { testRoundtrip(it) } - } - - fun testRoundtrip(group: GroupContext) { - runTest { - checkAll( - iterations = 33, - Arb.string(minSize = 3), - Arb.int(min = 1, max = 10), - Arb.int(min = 1, max = 10), - ) { id, xcoord, quota, -> - - val proofs = mutableListOf() - repeat(quota) { - val kp = elGamalKeypairs(group).single() - val nonce = elementsModQ(group).single() - proofs.add(kp.schnorrProof(xcoord, it, nonce)) - } - val publicKey = PublicKeys(id, xcoord, proofs) - assertEquals(publicKey, publicKey.publishJson().import(group, ErrorMessages("round"))) - assertEquals(publicKey, jsonRoundTrip(publicKey.publishJson()).import(group, ErrorMessages("trip"))) - } - } - } -} \ No newline at end of file diff --git a/src/test/kotlin/org/cryptobiotic/eg/publish/json/ReadIteratorsTest.kt b/src/test/kotlin/org/cryptobiotic/eg/publish/json/ReadIteratorsTest.kt deleted file mode 100644 index 988b089..0000000 --- a/src/test/kotlin/org/cryptobiotic/eg/publish/json/ReadIteratorsTest.kt +++ /dev/null @@ -1,46 +0,0 @@ -package org.cryptobiotic.eg.publish.json - -import org.cryptobiotic.eg.core.ElGamalPublicKey -import org.cryptobiotic.eg.publish.makeConsumer -import org.cryptobiotic.eg.publish.readElectionRecord -import org.cryptobiotic.eg.verifier.VerifyEncryptedBallots -import org.cryptobiotic.util.ErrorMessages -import java.nio.file.Path -import java.util.function.Predicate -import kotlin.test.assertFalse - -// TODO -class ReadIteratorsTest { - - fun verifyOutput(inputDir: String, ballotDir: String, chained: Boolean = false) { - val consumer = makeConsumer(inputDir) - val record = readElectionRecord(consumer) - - val verifier = VerifyEncryptedBallots( - consumer.group, - record.manifest(), - record.jointPublicKey()!!, - record.extendedBaseHash()!!, - record.config(), 1 - ) - - val consumerBallots = makeConsumer(ballotDir, consumer.group) - val ballots = consumerBallots.iterateEncryptedBallotsFromDir(ballotDir, null ) - ballots.forEach { - println(" ballot = ${it.ballotId}") - } - val errs = ErrorMessages("verifyBallots") - val ok = verifier.verifyBallots(ballots, errs) - println(" verifyEncryptedBallots: ok= $ok result= $errs") - assertFalse(errs.hasErrors()) - - if (chained) { - val chainErrs = ErrorMessages("verifyConfirmationChain") - val chainOk = verifier.assembleAndVerifyChains(consumerBallots, chainErrs) - println(" verifyConfirmationChain: ok= $chainOk result= $errs") - assertFalse(chainErrs.hasErrors()) - } - println("Success") - } - -} \ No newline at end of file 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 56a57fc..c569a70 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/publish/json/WebappDecryptionTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/publish/json/WebappDecryptionTest.kt @@ -47,7 +47,33 @@ class WebappDecryptionTest { } @Test - fun testChallengeRequests() { + fun testDecryptRequest() { + groups.forEach { testDecryptRequest(it) } + } + + fun testDecryptRequest(group: GroupContext) { + runTest { + checkAll( + propTestFastConfig, + Arb.string(minSize = 3), + Arb.int(min = 1, max = 11), + ) { name, nrequests -> + val drequest = + DecryptRequest( + listOf( elementsModP(group, minimum = 2).single(), + elementsModP(group, minimum = 2).single(), + elementsModP(group, minimum = 2).single()) + ) + val drequestj = drequest.publishJson() + val roundtrip = drequestj.import(group) + assert (roundtrip is Ok) + assertEquals(drequest.texts, roundtrip.unwrap()) + } + } + } + + @Test + fun testPartialDecryptions() { groups.forEach { testPartialDecryptions(it) } } @@ -73,6 +99,28 @@ class WebappDecryptionTest { } } + @Test + fun testChallengeRequest() { + groups.forEach { testChallengeRequest(it) } + } + + fun testChallengeRequest(group: GroupContext) { + runTest { + checkAll( + propTestFastConfig, + Arb.int(min = 1, max = 122221), + Arb.int(min = 1, max = 11), + ) { batchId, nrequests -> + val crs = List(nrequests) { elementsModQ(group, minimum = 2).single() } + // ChallengeRequest(val batchId: Int, val texts: List) + val org = ChallengeRequest(batchId, crs) + val responses = org.publishJson().import(group) + assertTrue(responses is Ok) + assertEquals(org, responses.unwrap()) + } + } + } + @Test fun testChallengeResponses() { groups.forEach { testChallengeResponses(it) }