Skip to content

Commit

Permalink
Finish adding return values for CLIs.
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnLCaron committed Apr 30, 2024
1 parent 9baa56e commit 442f167
Show file tree
Hide file tree
Showing 12 changed files with 233 additions and 146 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[![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.7%25%20LOC%20(6967/7680)-blue)
![Coverage](https://img.shields.io/badge/coverage-90.4%25%20LOC%20(6997/7741)-blue)

# ElectionGuard-Kotlin Elliptic Curve

_last update 04/25/2024_
_last update 04/30/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),
Expand Down
100 changes: 55 additions & 45 deletions src/main/kotlin/org/cryptobiotic/eg/cli/RunAddEncryptedBallots.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ import org.cryptobiotic.eg.input.ManifestInputValidation
import org.cryptobiotic.eg.publish.readElectionRecord
import org.cryptobiotic.util.ErrorMessages
import kotlin.random.Random
import kotlin.system.exitProcess

/**
* This reads plaintext ballots from ballotDir and writes their encryptions into the specified election record.
*/
/** This reads plaintext ballots from ballotDir and writes their encryptions into the specified election record. */
class RunAddEncryptedBallots {

companion object {
Expand Down Expand Up @@ -48,6 +47,11 @@ class RunAddEncryptedBallots {
shortName = "challenge",
description = "Challenge percent of ballots"
).default(0)
val noexit by parser.option(
ArgType.Boolean,
shortName = "noexit",
description = "Dont call System.exit"
).default(false)

parser.parse(args)

Expand All @@ -56,56 +60,62 @@ class RunAddEncryptedBallots {
" outputDir = $outputDir\n challengePct = $challengePct"
}

val electionRecord = readElectionRecord(inputDir)
val electionInit = electionRecord.electionInit()!!
val consumerIn = electionRecord.consumer()
try {
val electionRecord = readElectionRecord(inputDir)
val electionInit = electionRecord.electionInit()!!
val consumerIn = electionRecord.consumer()

val manifest = consumerIn.makeManifest(electionInit.config.manifestBytes)
val errors = ManifestInputValidation(manifest).validate()
if (ManifestInputValidation(manifest).validate().hasErrors()) {
logger.error { "ManifestInputValidation error ${errors}" }
throw RuntimeException("ManifestInputValidation error $errors")
}
val chaining = electionInit.config.chainConfirmationCodes
val manifest = consumerIn.makeManifest(electionInit.config.manifestBytes)
val errors = ManifestInputValidation(manifest).validate()
if (ManifestInputValidation(manifest).validate().hasErrors()) {
logger.error { "ManifestInputValidation error ${errors}" }
if (!noexit) exitProcess(1)
else throw RuntimeException("ManifestInputValidation error $errors")
}
val chaining = electionInit.config.chainConfirmationCodes

var allOk = true
var allOk = true

val encryptor = AddEncryptedBallot(
electionRecord.manifest(),
BallotInputValidation(electionRecord.manifest()),
electionInit.config.chainConfirmationCodes,
electionInit.config.configBaux0,
electionInit.jointPublicKey,
electionInit.extendedBaseHash,
device,
outputDir,
"${outputDir}/invalidDir",
noDeviceNameInDir = !chaining
)
val encryptor = AddEncryptedBallot(
electionRecord.manifest(),
BallotInputValidation(electionRecord.manifest()),
electionInit.config.chainConfirmationCodes,
electionInit.config.configBaux0,
electionInit.jointPublicKey,
electionInit.extendedBaseHash,
device,
outputDir,
"${outputDir}/invalidDir",
noDeviceNameInDir = !chaining
)

var countChallenge = 0
consumerIn.iteratePlaintextBallots(ballotDir, null).forEach { pballot ->
val errs = ErrorMessages("AddEncryptedBallot ${pballot.ballotId}")
val encrypted = encryptor.encrypt(pballot, errs)
if (encrypted == null) {
logger.error{ "failed errors = $errs"}
allOk = false
} else {
val challengeThisOne = (challengePct != 0) && (Random.nextInt(100) > (100 - challengePct))
if (challengeThisOne) {
encryptor.challenge(encrypted.confirmationCode)
countChallenge++
var countChallenge = 0
consumerIn.iteratePlaintextBallots(ballotDir, null).forEach { pballot ->
val errs = ErrorMessages("AddEncryptedBallot ${pballot.ballotId}")
val encrypted = encryptor.encrypt(pballot, errs)
if (encrypted == null) {
logger.error { "failed errors = $errs" }
allOk = false
} else {
encryptor.cast(encrypted.confirmationCode)
val challengeThisOne = (challengePct != 0) && (Random.nextInt(100) > (100 - challengePct))
if (challengeThisOne) {
encryptor.challenge(encrypted.confirmationCode)
countChallenge++
} else {
encryptor.cast(encrypted.confirmationCode)
}
}
}
}
encryptor.close()
encryptor.close()

if (allOk) {
logger.info { "success" }
} else {
logger.error { "failure" }
if (allOk) {
logger.info { "success" }
} else {
if (!noexit) exitProcess(2)
}
} catch (t: Throwable) {
logger.error { "Exception= ${t.message} ${t.stackTraceToString()}" }
if (!noexit) exitProcess(-1)
}
}
}
Expand Down
84 changes: 56 additions & 28 deletions src/main/kotlin/org/cryptobiotic/eg/cli/RunBatchEncryption.kt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import org.cryptobiotic.eg.verifier.VerifyEncryptedBallots
import org.cryptobiotic.util.ErrorMessages
import org.cryptobiotic.util.Stats
import org.cryptobiotic.util.Stopwatch
import kotlin.system.exitProcess

/**
* Run ballot encryption in multithreaded batch mode CLI.
Expand Down Expand Up @@ -107,6 +108,11 @@ class RunBatchEncryption {
shortName = "anon",
description = "anonymize ballot"
).default(false)
val noexit by parser.option(
ArgType.Boolean,
shortName = "noexit",
description = "Dont call System.exit"
).default(false)

parser.parse(args)
val startupInfo =
Expand All @@ -120,23 +126,35 @@ class RunBatchEncryption {
println(startupInfo)

if (outputDir == null && encryptDir == null) {
throw RuntimeException("Must specify outputDir or encryptDir")
logger.error { "Must specify outputDir or encryptDir" }
if (!noexit) exitProcess(1)
else throw RuntimeException("Must specify outputDir or encryptDir")
}

batchEncryption(
inputDir,
ballotDir,
device = device,
outputDir,
encryptDir,
invalidDir,
nthreads,
createdBy,
check,
cleanOutput,
anonymize,
)
logger.info { "success" }
try {
val retval = batchEncryption(
inputDir,
ballotDir,
device = device,
outputDir,
encryptDir,
invalidDir,
nthreads,
createdBy,
check,
cleanOutput,
anonymize,
)
if (retval == 0) {
logger.info { "success" }
} else {
if (!noexit) exitProcess(retval)
}

} catch (t: Throwable) {
logger.error { "Exception= ${t.message} ${t.stackTraceToString()}" }
if (!noexit) exitProcess(-1)
}
}

enum class CheckType { None, Verify, EncryptTwice, DecryptNonce }
Expand All @@ -154,7 +172,7 @@ class RunBatchEncryption {
check: CheckType = CheckType.None,
cleanOutput: Boolean = false,
anonymize: Boolean = false,
) {
): Int {
// ballots can be in either format
val consumer = makeConsumer(inputDir)

Expand All @@ -180,13 +198,13 @@ class RunBatchEncryption {
check: CheckType = CheckType.None,
cleanOutput: Boolean = false,
anonymize: Boolean = false,
) {
): Int {
count = 0 // start over each batch
val consumerIn = makeConsumer(inputDir)
val initResult = consumerIn.readElectionInitialized()
if (initResult is Err) {
logger.error{ "readElectionInitialized error ${initResult.error}" }
return
logger.error { "readElectionInitialized error ${initResult.error}" }
return 2
}
val electionInit = initResult.unwrap()
val manifest = consumerIn.makeManifest(electionInit.config.manifestBytes)
Expand All @@ -195,8 +213,8 @@ class RunBatchEncryption {
val manifestValidator = ManifestInputValidation(manifest)
val errors = manifestValidator.validate()
if (errors.hasErrors()) {
logger.error{ "ManifestInputValidation error on election record in $inputDir errs=$errors}" }
return
logger.error { "ManifestInputValidation error on election record in $inputDir errs=$errors}" }
return 3
}
// debugging
// Map<BallotStyle: String, selectionCount: Int>
Expand Down Expand Up @@ -271,15 +289,24 @@ class RunBatchEncryption {
}
// Must save invalid ballots
if (invalidBallots.isNotEmpty()) {
val useInvalidDir = if (invalidDir != null) invalidDir else if (outputDir != null) "$outputDir/invalid" else "$encryptDir/invalid"
val useInvalidDir =
if (invalidDir != null) invalidDir else if (outputDir != null) "$outputDir/invalid" else "$encryptDir/invalid"
createDirectories(useInvalidDir)
publisher.writePlaintextBallot(useInvalidDir, invalidBallots)
logger.info{ " wrote ${invalidBallots.size} invalid ballots to $useInvalidDir" }
logger.info { " wrote ${invalidBallots.size} invalid ballots to $useInvalidDir" }
}

logger.debug{ "Encryption with nthreads = $nthreads ${stopwatch.tookPer(count, "ballot")}" }
logger.debug { "Encryption with nthreads = $nthreads ${stopwatch.tookPer(count, "ballot")}" }
val encryptionPerBallot = if (count == 0) 0 else (countEncryptions / count)
logger.debug{ " $countEncryptions total encryptions = $encryptionPerBallot per ballot ${stopwatch.tookPer(countEncryptions, "encryptions")}"}
logger.debug {
" $countEncryptions total encryptions = $encryptionPerBallot per ballot ${
stopwatch.tookPer(
countEncryptions,
"encryptions"
)
}"
}
return 0
}

private var codeBaux = ByteArray(0)
Expand Down Expand Up @@ -312,7 +339,8 @@ class RunBatchEncryption {
// experiments in testing the encryption
val errs2 = ErrorMessages("Ballot ${ballot.ballotId}")
if (check == CheckType.EncryptTwice) {
val encrypted2 = encryptor.encrypt(ballot, config.configBaux0, errs2, ciphertextBallot.ballotNonce)!!
val encrypted2 =
encryptor.encrypt(ballot, config.configBaux0, errs2, ciphertextBallot.ballotNonce)!!
if (encrypted2.confirmationCode != ciphertextBallot.confirmationCode) {
logger.warn { "CheckType.EncryptTwice: encrypted.confirmationCode doesnt match" }
}
Expand All @@ -332,7 +360,7 @@ class RunBatchEncryption {
val encryptedBallot = ciphertextBallot.submit(EncryptedBallot.BallotState.CAST)

val decryptionWithPrimaryNonce = DecryptBallotWithNonce(group, publicKey, extendedBaseHash)
val decryptResult = with( decryptionWithPrimaryNonce) { encryptedBallot.decrypt(primaryNonce) }
val decryptResult = with(decryptionWithPrimaryNonce) { encryptedBallot.decrypt(primaryNonce) }
if (decryptResult is Err) {
logger.warn { "CheckType.DecryptNonce: encrypted ballot fails decryption = $decryptResult" }
}
Expand Down Expand Up @@ -379,7 +407,7 @@ class RunBatchEncryption {
input: Channel<EncryptedBallot>, sink: EncryptedBallotSinkIF, anonymize: Boolean,
) = launch {
for (ballot in input) {
val useBallot = if (!anonymize) ballot else ballot.copy(ballotId = (count+1).toString())
val useBallot = if (!anonymize) ballot else ballot.copy(ballotId = (count + 1).toString())
sink.writeEncryptedBallot(useBallot)
logger.debug { " Sink wrote $count submitted ballot ${useBallot.ballotId}" }
count++
Expand Down
38 changes: 23 additions & 15 deletions src/main/kotlin/org/cryptobiotic/eg/cli/RunCreateInputBallots.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.cryptobiotic.eg.cli

import io.github.oshai.kotlinlogging.KotlinLogging
import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlinx.cli.default
Expand All @@ -8,11 +9,14 @@ import org.cryptobiotic.eg.election.PlaintextBallot
import org.cryptobiotic.eg.input.RandomBallotProvider
import org.cryptobiotic.eg.publish.makePublisher
import org.cryptobiotic.eg.publish.readAndCheckManifest
import kotlin.system.exitProcess

/** Run Create Input Ballots CLI. */
class RunCreateInputBallots {

companion object {
private val logger = KotlinLogging.logger("RunCreateInputBallots")

@JvmStatic
fun main(args: Array<String>) {
val parser = ArgParser("RunCreateInputBallots")
Expand All @@ -31,32 +35,36 @@ class RunCreateInputBallots {
shortName = "n",
description = "Number of ballots to generate"
).default(11)
val isJson by parser.option(
val noexit by parser.option(
ArgType.Boolean,
shortName = "json",
description = "Generate Json ballots (default to manifest type)"
)
shortName = "noexit",
description = "Dont call System.exit"
).default(false)

parser.parse(args)

println(
"RunCreateInputBallots\n" +
" electionManifest = '$manifestDirOrFile'\n" +
" outputDir = '$outputDir'\n" +
" nballots = '$nballots'\n" +
" isJson = '$isJson'\n"
" nballots = '$nballots'\n"
)

val (manifestIsJson, manifest, _) = readAndCheckManifest(manifestDirOrFile)
val useJson = isJson ?: manifestIsJson
val publisher = makePublisher(outputDir, true)
try {
val (_, manifest, _) = readAndCheckManifest(manifestDirOrFile)
val publisher = makePublisher(outputDir, true)

val ballots = mutableListOf<PlaintextBallot>()
val ballotProvider = RandomBallotProvider(manifest)
repeat(nballots) {
ballots.add(ballotProvider.makeBallot())
}
val ballots = mutableListOf<PlaintextBallot>()
val ballotProvider = RandomBallotProvider(manifest)
repeat(nballots) {
ballots.add(ballotProvider.makeBallot())
}
publisher.writePlaintextBallot(outputDir, ballots)

publisher.writePlaintextBallot(outputDir, ballots)
} catch (t: Throwable) {
logger.error { "Exception= ${t.message} ${t.stackTraceToString()}" }
if (!noexit) exitProcess(-1)
}
}
}
}
Loading

0 comments on commit 442f167

Please sign in to comment.