diff --git a/docs/ElectionRecordJson.md b/docs/ElectionRecordJson.md index 50c325d..3388973 100644 --- a/docs/ElectionRecordJson.md +++ b/docs/ElectionRecordJson.md @@ -1,6 +1,6 @@ # EGK Election Record JSON directory and file layout, version 2.1 -draft 04/04/2024 +draft 04/13/2024 ## Public Election Record files @@ -27,7 +27,8 @@ topdir/ ```` The encrypted_ballots directory may optionally be divided into "device" subdirectories. -If using ballot chaining, each such subdirectory is a separate ballot chain. + +If using ballot chaining, each such subdirectory is a separate ballot chain, like this: ```` topdir/ @@ -66,6 +67,8 @@ Files/directories may be absent, depending on the workflow stage: * The challenged_ballots directory contain only challenged ballots that have been decrypted. * DecryptedTallyJson and DecryptedBallotJson use the same schema (DecryptedTallyOrBallotJson) +The entire election record may be zipped; the library can read from the zip file, eg for verification. + ## Private files These files are not part of the election record, but are generated for internal use. diff --git a/htmlReport/index.html b/htmlReport/index.html index d7a4cea..8b978e9 100644 --- a/htmlReport/index.html +++ b/htmlReport/index.html @@ -44,18 +44,18 @@

Overall Coverage Summary

all classes - 83.2% + 83.4% - (475/571) + (476/571) - 87% + 87.1% - (1270/1460) + (1271/1460) @@ -71,7 +71,7 @@

Overall Coverage Summary

90.2% - (6890/7642) + (6892/7642) @@ -106,34 +106,34 @@

Coverage Breakdown

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

Coverage Breakdown

- 61.2% + 60.5% - (93/152) + (92/152) @@ -649,7 +649,7 @@

Coverage Breakdown

diff --git a/src/main/kotlin/org/cryptobiotic/eg/cli/RunEncryptBallot.kt b/src/main/kotlin/org/cryptobiotic/eg/cli/RunEncryptBallot.kt index 8dfa1c4..30b9be8 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/cli/RunEncryptBallot.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/cli/RunEncryptBallot.kt @@ -74,13 +74,11 @@ class RunEncryptBallot { ) if (retval != 0) { logger.info { "encryptBallot retval=$retval" } - // exitProcess(retval) } } } catch (t: Throwable) { logger.error(t) { "failed ${t.message}" } - // exitProcess(10) } } diff --git a/src/main/kotlin/org/cryptobiotic/eg/cli/RunExampleEncryption.kt b/src/main/kotlin/org/cryptobiotic/eg/cli/RunExampleEncryption.kt index beaa2a4..fe1fecd 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/cli/RunExampleEncryption.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/cli/RunExampleEncryption.kt @@ -13,13 +13,13 @@ import org.cryptobiotic.eg.input.RandomBallotProvider import org.cryptobiotic.eg.publish.makeConsumer import org.cryptobiotic.eg.publish.makePublisher import kotlin.random.Random -import kotlin.system.exitProcess /** * Simulates using RunEncryptBallot one ballot at a time. * Note that chaining is controlled by config.chainConfirmationCodes, and handled by RunEncryptBallot. * Note that this does not allow for benolah challenge, ie voter submits a ballot, gets a confirmation code - * (with or without ballot chaining), then decide to challenge or cast. So all ballots are cast. */ + * (with or without ballot chaining), then decide to challenge or cast. So all ballots are cast. + */ class RunExampleEncryption { companion object { @@ -112,7 +112,6 @@ class RunExampleEncryption { logger.info { "success" } } else { logger.error { "failure" } - exitProcess(10) } } } diff --git a/src/main/kotlin/org/cryptobiotic/eg/election/ElectionConfig.kt b/src/main/kotlin/org/cryptobiotic/eg/election/ElectionConfig.kt index 9b92a4e..9844817 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/election/ElectionConfig.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/election/ElectionConfig.kt @@ -19,7 +19,7 @@ data class ElectionConfig( val manifestHash : UInt256, // Hm val electionBaseHash : UInt256, // Hb // the raw bytes of the manifest. You must regenerate the manifest from this. - val manifestBytes: ByteArray, // TODO may need to specify serialization form, or detect it. + val manifestBytes: ByteArray, val chainConfirmationCodes: Boolean = false, val configBaux0: ByteArray, // B_aux,0 from eq 59,60 may be empty diff --git a/src/main/kotlin/org/cryptobiotic/eg/encrypt/AddEncryptedBallot.kt b/src/main/kotlin/org/cryptobiotic/eg/encrypt/AddEncryptedBallot.kt index 7278640..6571d2a 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/encrypt/AddEncryptedBallot.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/encrypt/AddEncryptedBallot.kt @@ -19,7 +19,7 @@ import java.io.Closeable private val logger = KotlinLogging.logger("AddEncryptedBallot") /** - * Encrypt a ballot and add to election record. TODO Single threaded only?. + * Encrypt a ballot and add to election record. Single threaded or thread confined only. * Note that chaining is controlled by config.chainConfirmationCodes, and handled here. * Note that this allows for benolah challenge, ie voter submits a ballot, gets a confirmation code * (with or without ballot chaining), then decide to challenge or cast. @@ -132,7 +132,7 @@ class AddEncryptedBallot( sink.writeEncryptedBallot(eballot) Ok(eballot) } catch (t: Throwable) { - logger.throwing(t) // TODO + logger.throwing(t) Err("Tried to submit Ciphertext ballot state=$state ccode=$ccode error = ${t.message}") } } @@ -164,7 +164,7 @@ class AddEncryptedBallot( } } } catch (t: Throwable) { - logger.throwing(t) // TODO + logger.throwing(t) return Err("Tried to challenge Ciphertext ballot ccode=$ccode error = ${t.message}") } } diff --git a/src/main/kotlin/org/cryptobiotic/eg/encrypt/EncryptedBallotChain.kt b/src/main/kotlin/org/cryptobiotic/eg/encrypt/EncryptedBallotChain.kt index 7564588..83195b2 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/encrypt/EncryptedBallotChain.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/encrypt/EncryptedBallotChain.kt @@ -16,18 +16,13 @@ import org.cryptobiotic.util.ErrorMessages // TODO error if ballotChain is already closed ?? // Let Baux,0 = "Baux,0 must contain at least a unique voting device identifier and possibly other voting device -// information as described above and as specified in the election manifest file. +// information as described above and as specified in the election manifest file." p 36. // // Then: -// // H0 = H(HE ; 0x24, Baux,0) (59) -// // Baux,1 = H0 ∥ Baux,0 -// // H(B1) = H(HE ; 0x24, χ1 , χ2 , . . . , χmB , Baux,1 ). -// // Baux,j = Hj−1 ∥ Baux,0 (60) -// // H(Bj) = H(HE ; 0x24, χ1 , χ2 , . . . , χmB , Baux,j ). data class EncryptedBallotChain( diff --git a/src/main/kotlin/org/cryptobiotic/eg/encrypt/Encryptor.kt b/src/main/kotlin/org/cryptobiotic/eg/encrypt/Encryptor.kt index f347766..1de5bca 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/encrypt/Encryptor.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/encrypt/Encryptor.kt @@ -5,10 +5,10 @@ import org.cryptobiotic.eg.election.* import org.cryptobiotic.util.ErrorMessages /** - * Encrypt Plaintext Ballots into Ciphertext Ballots. + * Encrypt Plaintext Ballots into PendingEncryptedBallot. * The manifest is expected to have passed manifest validation (see ManifestInputValidation). - * The input ballots are expected to have passed ballot validation [TODO missing contests added? overvotes checked?] - * See RunBatchEncryption and BallotInputValidation to validate ballots before passing them to this class. + * The input ballots are expected to have passed ballot validation + * See RunExampleEncryption and BallotInputValidation to validate ballots before passing them to this class. */ class Encryptor( val group: GroupContext, diff --git a/src/main/kotlin/org/cryptobiotic/eg/keyceremony/KeyCeremonyTrustee.kt b/src/main/kotlin/org/cryptobiotic/eg/keyceremony/KeyCeremonyTrustee.kt index be8691a..91e74fa 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/keyceremony/KeyCeremonyTrustee.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/keyceremony/KeyCeremonyTrustee.kt @@ -212,7 +212,7 @@ open class KeyCeremonyTrustee( // (αi,ℓ , βi,ℓ ) = (g^ξi,ℓ mod p, K^ξi,ℓ mod p), ξi,ℓ = nonce // (α_i,ℓ , β_i,ℓ ) = (g^nonce mod p, Kℓ^nonce mod p) ; spec 2.0.0, eq 14 // by encrypting a zero, we achieve exactly this - val K_l = ElGamalPublicKey(other.publicKey) // TODO is it worth turning this into an accelerated elementP? + val K_l = ElGamalPublicKey(other.publicKey) val (alpha, beta) = 0.encrypt(K_l, nonce) // ki,ℓ = H(HP ; 0x11, i, ℓ, Kℓ , αi,ℓ , βi,ℓ ) ; eq 15 "secret key" val kil = hashFunction(hp, 0x11.toByte(), i, l, other.publicKey, alpha, beta).bytes diff --git a/src/test/data/workflow/allAvailableEc.zip b/src/test/data/workflow/allAvailableEc.zip deleted file mode 100644 index 8287fa9..0000000 Binary files a/src/test/data/workflow/allAvailableEc.zip and /dev/null differ diff --git a/src/test/kotlin/org/cryptobiotic/eg/encrypt/AddEncryptedBallotTest.kt b/src/test/kotlin/org/cryptobiotic/eg/encrypt/AddEncryptedBallotTest.kt index 3472238..7d9f105 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/encrypt/AddEncryptedBallotTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/encrypt/AddEncryptedBallotTest.kt @@ -18,12 +18,12 @@ import org.cryptobiotic.util.Testing class AddEncryptedBallotTest { val input = "src/test/data/workflow/allAvailableEc" - val outputDir = "${Testing.testOut}/encrypt/addEncryptedBallot/Plain" + val testDir = "${Testing.testOut}/encrypt/addEncryptedBallot/Plain" val nballots = 4 @Test fun testJustOne() { - val outputDir = "$outputDir/testJustOne" + val outputDir = "$testDir/testJustOne" val device = "device0" val electionRecord = readElectionRecord(input) @@ -57,7 +57,7 @@ class AddEncryptedBallotTest { @Test fun testEncryptAndCast() { - val outputDir = "$outputDir/testEncryptAndCast" + val outputDir = "$testDir/testEncryptAndCast" val device = "device0" val electionRecord = readElectionRecord(input) @@ -82,7 +82,10 @@ class AddEncryptedBallotTest { val ballot = ballotProvider.makeBallot() val result = encryptor.encryptAndCast(ballot, ErrorMessages("testEncryptAndCast")) assertNotNull(result) - assertTrue(encryptor.submit(result.confirmationCode, EncryptedBallot.BallotState.CAST) is Err) + // expect submitting again to fail + val submitAgain = encryptor.submit(result.confirmationCode, EncryptedBallot.BallotState.CAST) + assertTrue(submitAgain is Err) + assertTrue(submitAgain.error.contains("unknown ballot ccode")) } encryptor.close() @@ -91,7 +94,7 @@ class AddEncryptedBallotTest { @Test fun testEncryptAndCastNoWrite() { - val outputDir = "$outputDir/testEncryptAndCastNoWrite" + val outputDir = "$testDir/testEncryptAndCastNoWrite" val device = "device0" val electionRecord = readElectionRecord(input) @@ -116,16 +119,21 @@ class AddEncryptedBallotTest { val ballot = ballotProvider.makeBallot() val result = encryptor.encryptAndCast(ballot, ErrorMessages("testEncryptAndCastNoWrite"), false) assertNotNull(result) - assertTrue(encryptor.submit(result.confirmationCode, EncryptedBallot.BallotState.CAST) is Err) + // expect submitting again to fail + val submitAgain = encryptor.submit(result.confirmationCode, EncryptedBallot.BallotState.CAST) + assertTrue(submitAgain is Err) + assertTrue(submitAgain.error.contains("unknown ballot ccode")) } encryptor.close() - // TODO make sure no ballots were written + // make sure no ballots were written + val consumer = makeConsumer(outputDir) + assertFalse(consumer.hasEncryptedBallots()) } @Test fun testCallMultipleTimes() { - val outputDir = "$outputDir/testCallMultipleTimes" + val outputDir = "$testDir/testCallMultipleTimes" val device = "device1" val electionRecord = readElectionRecord(input) @@ -161,7 +169,7 @@ class AddEncryptedBallotTest { @Test fun testMultipleDevices() { - val outputDir = "$outputDir/testMultipleDevices" + val outputDir = "$testDir/testMultipleDevices" val electionRecord = readElectionRecord(input) val electionInit = electionRecord.electionInit()!! @@ -196,7 +204,7 @@ class AddEncryptedBallotTest { @Test fun testOneWithChain() { - val outputDir = "$outputDir/testOneWithChain" + val outputDir = "$testDir/testOneWithChain" val device = "device0" val electionRecord = readElectionRecord(input) @@ -232,7 +240,7 @@ class AddEncryptedBallotTest { @Test fun testCallMultipleTimesChaining() { - val outputDir = "$outputDir/testCallMultipleTimesChaining" + val outputDir = "$testDir/testCallMultipleTimesChaining" val device = "device1" val electionRecord = readElectionRecord(input) @@ -270,7 +278,7 @@ class AddEncryptedBallotTest { @Test fun testMultipleDevicesChaining() { - val outputDir = "$outputDir/testMultipleDevicesChaining" + val outputDir = "$testDir/testMultipleDevicesChaining" val electionRecord = readElectionRecord( input) val configWithChaining = electionRecord.config().copy(chainConfirmationCodes = true) diff --git a/src/test/kotlin/org/cryptobiotic/eg/publish/TestZippedJson.kt b/src/test/kotlin/org/cryptobiotic/eg/publish/TestZippedJson.kt index 78f1c1a..4fac378 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/publish/TestZippedJson.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/publish/TestZippedJson.kt @@ -4,6 +4,7 @@ import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream import org.cryptobiotic.eg.cli.RunVerifier +import org.cryptobiotic.eg.core.createDirectories import java.io.BufferedOutputStream import java.io.File import java.io.FileOutputStream @@ -19,6 +20,7 @@ import kotlin.test.assertNotNull import org.cryptobiotic.eg.publish.json.* import org.cryptobiotic.util.ErrorMessages +import org.cryptobiotic.util.Testing // run verifier on zipped JSON record @@ -27,11 +29,12 @@ class TestZippedJson { val jsonReader = Json { explicitNulls = false; ignoreUnknownKeys = true; prettyPrint = true } val inputDir = "src/test/data/workflow/allAvailableEc" - val zippedJson = "src/test/data/workflow/allAvailableEc.zip" + val zippedJson = "${Testing.testOut}/zip/allAvailableEc.zip" val fs: FileSystem val fsp: FileSystemProvider init { + createDirectories("${Testing.testOut}/zip/") zipFolder(File(inputDir), File(zippedJson)) fs = FileSystems.newFileSystem(Path.of(zippedJson), mutableMapOf()) fsp = fs.provider() @@ -60,6 +63,7 @@ class TestZippedJson { fun testVerifyEncryptedBallots() { RunVerifier.verifyEncryptedBallots(zippedJson, 11) RunVerifier.verifyChallengedBallots(zippedJson) + RunVerifier.runVerifier(zippedJson, 11) } }