Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finish adding return values for CLIs. #77

Merged
merged 1 commit into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading