Skip to content

Commit

Permalink
Merge pull request #425 from JohnLCaron/encryptErrors2
Browse files Browse the repository at this point in the history
Encrypt uses ErrMessages (part 2)
  • Loading branch information
JohnLCaron authored Nov 8, 2023
2 parents c308003 + 2ffe46e commit 1231d17
Show file tree
Hide file tree
Showing 175 changed files with 31,674 additions and 31,495 deletions.
2 changes: 1 addition & 1 deletion egklib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ kotlin {
kotlinOptions.jvmTarget = "1.8"
kotlinOptions.freeCompilerArgs = listOf(
"-Xexpect-actual-classes",
"-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi,kotlinx.serialization.ExperimentalSerializationApi"
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi,kotlinx.serialization.ExperimentalSerializationApi"
)
}

Expand Down
8 changes: 4 additions & 4 deletions egklib/src/commonMain/kotlin/electionguard/ballot/Manifest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,16 @@ data class Manifest(
}
}

override fun contestsForBallotStyle(ballotStyle: String): List<ManifestIF.Contest> {
return styleToContestsMap[ballotStyle] ?: throw RuntimeException("unknown ballotStyle $ballotStyle")
override fun contestsForBallotStyle(ballotStyle: String): List<ManifestIF.Contest>? {
return styleToContestsMap[ballotStyle]
}

override fun contestLimit(contestId : String) : Int {
return contestIdToContestLimit[contestId] ?: throw RuntimeException("manifest missing contestLimit for $contestId")
return contestIdToContestLimit[contestId]?: 1
}

override fun optionLimit(contestId : String) : Int {
return contestIdToOptionLimit[contestId] ?: throw RuntimeException("manifest missing optionLimit for $contestId")
return contestIdToOptionLimit[contestId]?: 1
}

/** Map of contestId to contest selection limit. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface ManifestIF {
}

/** get the list of valid contests for the given ballotStyle */
fun contestsForBallotStyle(ballotStyle : String): List<Contest>
fun contestsForBallotStyle(ballotStyle : String): List<Contest>?

/** get the contest selection limit (aka votesAllowed) for the given contest id */
fun contestLimit(contestId : String): Int
Expand Down
2 changes: 1 addition & 1 deletion egklib/src/commonMain/kotlin/electionguard/core/UInt256.kt
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ fun ByteArray.normalize(nbytes: Int): ByteArray {
val leading = size - nbytes
for (idx in 0 until leading) {
if (this[idx].compareTo(0) != 0) {
throw IllegalArgumentException("ByteArray.normalize failed; has $size bytes, want $nbytes, leading zeroes stop at $idx")
throw IllegalArgumentException("ByteArray.normalize error; has $size bytes, want $nbytes, leading zeroes stop at $idx")
}
}
this.copyOfRange(leading, this.size)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ internal class TallyDecryptor(

if (doVerifierSelectionProof) {
if (!decryptedContestData.verifyContestData()) {
errs.add("verifyContestData failed for $contestId")
errs.add("verifyContestData error on $contestId")
contestDataDecryptions.checkIndividualResponses(errs.nested("checkIndividualResponses"))
}
} else { // Otherwise do the individual guardian verifications, which costs 4*n exponents
Expand Down Expand Up @@ -132,7 +132,7 @@ internal class TallyDecryptor(
// If it succeeds, dont have to do the individual verification
if (doVerifierSelectionProof) {
if (!decrypytedSelection.verifySelection()) {
errs.add("verifySelection failed for $contestId/${selection.selectionId}")
errs.add("verifySelection error on $contestId/${selection.selectionId}")
selectionDecryptions.checkIndividualResponses(errs.nested("checkIndividualResponses"))
}
} else { // Otherwise do the individual guardian verifications, which costs 4*n exponents
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,11 @@ import electionguard.ballot.*
import electionguard.core.ElGamalPublicKey
import electionguard.core.GroupContext
import electionguard.core.UInt256
import electionguard.core.Base16.fromHex
import electionguard.core.hashFunction
import electionguard.decryptBallot.DecryptWithNonce
import electionguard.input.BallotInputValidation
import electionguard.input.ManifestInputValidation
import electionguard.publish.*
import electionguard.util.ErrorMessages
import io.github.oshai.kotlinlogging.KotlinLogging

private val logger = KotlinLogging.logger("AddEncryptedBallot")
Expand All @@ -22,7 +21,10 @@ private val logger = KotlinLogging.logger("AddEncryptedBallot")
class AddEncryptedBallot(
val group: GroupContext,
val manifest: Manifest, // should already be validated
val electionInit: ElectionInitialized,
val configChaining: Boolean,
val configBaux0: ByteArray,
val jointPublicKey: ElGamalPublicKey,
val extendedBaseHash: UInt256,
val deviceName: String,
val outputDir: String, // write ballots to outputDir/encrypted_ballots/deviceName, must not have multiple writers to same directory
val invalidDir: String, // write plaintext ballots that fail validation
Expand All @@ -34,20 +36,18 @@ class AddEncryptedBallot(
val encryptor = Encryptor(
group,
manifest,
ElGamalPublicKey(electionInit.jointPublicKey),
electionInit.extendedBaseHash,
jointPublicKey,
extendedBaseHash,
deviceName,
)
val decryptor = DecryptWithNonce(
group,
ElGamalPublicKey(electionInit.jointPublicKey),
electionInit.extendedBaseHash
jointPublicKey,
extendedBaseHash
)

val publisher = makePublisher(outputDir, false, isJson)
val sink: EncryptedBallotSinkIF = publisher.encryptedBallotSink(deviceName)
val configBaux0: ByteArray = electionInit.config.configBaux0
val configChaining: Boolean = electionInit.config.chainConfirmationCodes
val baux0: ByteArray

private val ballotIds = mutableListOf<String>()
Expand All @@ -72,56 +72,57 @@ class AddEncryptedBallot(

} else {
baux0 = if (!configChaining) configBaux0 else
hashFunction(electionInit.extendedBaseHash.bytes, 0x24.toByte(), configBaux0).bytes // spec 2.0 eq 60
// H0 = H(HE ; 0x24, Baux,0 ), eq (59)
hashFunction(extendedBaseHash.bytes, 0x24.toByte(), configBaux0).bytes
}
}

fun encrypt(ballot: PlaintextBallot): Result<CiphertextBallot, String> {
fun encrypt(ballot: PlaintextBallot, errs : ErrorMessages): CiphertextBallot? {
if (closed) {
val message = "Trying to add ballot after chain has been closed"
logger.warn { message }
return Err(message)
errs.add("Trying to add ballot after chain has been closed")
return null
}

val mess = ballotValidator.validate(ballot)
if (mess.hasErrors()) {
val validation = ballotValidator.validate(ballot)
if (validation.hasErrors()) {
publisher.writePlaintextBallot(invalidDir, listOf(ballot))
val message = "${ballot.ballotId} did not validate (wrote to invalidDir=$invalidDir) because $mess"
logger.warn { message }
return Err(message)
errs.add("${ballot.ballotId} did not validate (wrote to invalidDir=$invalidDir) because $validation")
return null
}

val bauxj: ByteArray = if (!configChaining || first) baux0 else
hashFunction(lastConfirmationCode.bytes, configBaux0).bytes // spec 2.0 eq 61
// Baux,j = Hj−1 ∥ Baux,0 eq (60)
val bauxj: ByteArray = if (!configChaining || first) baux0 else lastConfirmationCode.bytes + configBaux0
first = false

val ciphertextBallot = encryptor.encrypt(ballot, bauxj)
ballotIds.add(ciphertextBallot.ballotId)
val ciphertextBallot = encryptor.encrypt(ballot, bauxj, errs)
if (errs.hasErrors()) {
return null
}
ballotIds.add(ciphertextBallot!!.ballotId)
this.lastConfirmationCode = ciphertextBallot.confirmationCode

// hmmm you could write CiphertextBallot to a log, in case of crash
pending[ciphertextBallot.confirmationCode] = ciphertextBallot
return Ok(ciphertextBallot)
return ciphertextBallot
}

/** encrypt and cast, does not leave in pending. optional write. */
fun encryptAndCast(ballot: PlaintextBallot, writeToDisk: Boolean = true): Result<EncryptedBallot, String> {
val resultEncrypt = encrypt(ballot)
if (resultEncrypt is Err) {
return Err(resultEncrypt.error)
fun encryptAndCast(ballot: PlaintextBallot, errs : ErrorMessages, writeToDisk: Boolean = true): EncryptedBallot? {
val cballot = encrypt(ballot, errs)
if (errs.hasErrors()) {
return null
}
val cballot = resultEncrypt.unwrap()
val eballot = cballot.submit(EncryptedBallot.BallotState.CAST)
val eballot = cballot!!.submit(EncryptedBallot.BallotState.CAST)
if (writeToDisk) {
submit(eballot.confirmationCode, EncryptedBallot.BallotState.CAST)
} else {
// remove from pending
pending.remove(eballot.confirmationCode)
}
return Ok(eballot)
return eballot
}

fun submit(ccode: UInt256, state: EncryptedBallot.BallotState): Result<Boolean, String> {
fun submit(ccode: UInt256, state: EncryptedBallot.BallotState): Result<EncryptedBallot, String> {
val cballot = pending.remove(ccode)
if (cballot == null) {
logger.error { "Tried to submit state=$state unknown ballot ccode=$ccode" }
Expand All @@ -130,18 +131,18 @@ class AddEncryptedBallot(
return try {
val eballot = cballot.submit(state)
sink.writeEncryptedBallot(eballot)
Ok(true)
Ok(eballot)
} catch (t: Throwable) {
logger.throwing(t) // TODO
Err("Tried to submit Ciphertext ballot state=$state ccode=$ccode error = ${t.message}")
}
}

fun cast(ccode: UInt256): Result<Boolean, String> {
fun cast(ccode: UInt256): Result<EncryptedBallot, String> {
return submit(ccode, EncryptedBallot.BallotState.CAST)
}

fun challenge(ccode: UInt256): Result<Boolean, String> {
fun challenge(ccode: UInt256): Result<EncryptedBallot, String> {
return submit(ccode, EncryptedBallot.BallotState.SPOILED)
}

Expand Down Expand Up @@ -171,7 +172,8 @@ class AddEncryptedBallot(
// write out pending encryptedBallots, and chain (if chainCodes is true)
fun sync() {
if (pending.isNotEmpty()) {
pending.keys.forEach {
val copyPending = pending.toMap() // make copy so it can be modified
copyPending.keys.forEach {
logger.error { "pending Ciphertext ballot ${it} was not submitted, marking 'UNKNOWN'" }
submit(it, EncryptedBallot.BallotState.UNKNOWN)
}
Expand All @@ -184,11 +186,11 @@ class AddEncryptedBallot(
fun closeChain(): UInt256? {
if (!configChaining) return null

// Hbar = H(HE ; 24, Baux )
// Baux = H(Bℓ ) ∥ Baux,0 ∥ b("CLOSE") (62)
// H(Bℓ ) is the final confirmation code in the chain
val bauxFinal = hashFunction(lastConfirmationCode.bytes, configBaux0, "CLOSE")
return hashFunction(electionInit.extendedBaseHash.bytes, 0x24.toByte(), bauxFinal)
// Hbar = H(HE ; 0x24, Baux )
// Baux = H(Bℓ) ∥ Baux,0 ∥ b("CLOSE") (eq 61)
// H(Bℓ) is the final confirmation code in the chain
val bauxFinal = lastConfirmationCode.bytes + configBaux0 + "CLOSE".encodeToByteArray()
return hashFunction(extendedBaseHash.bytes, 0x24.toByte(), bauxFinal)
}

override fun close() {
Expand Down
33 changes: 17 additions & 16 deletions egklib/src/commonMain/kotlin/electionguard/encrypt/Encryptor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import electionguard.ballot.ContestDataStatus
import electionguard.ballot.ManifestIF
import electionguard.ballot.PlaintextBallot
import electionguard.core.*
import electionguard.util.ErrorMessages

/**
* Encrypt Plaintext Ballots into Ciphertext Ballots.
Expand All @@ -21,31 +22,31 @@ class Encryptor(
) {
private val extendedBaseHashB = extendedBaseHash.bytes

/** TODO get rid of this */
fun encryptChain(ballots: Iterable<PlaintextBallot>, codeSeed: UInt256): List<CiphertextBallot> {
var previousTrackingHash = codeSeed
val encryptedBallots = mutableListOf<CiphertextBallot>()
for (ballot in ballots) {
val encryptedBallot = ballot.encryptBallot(previousTrackingHash.bytes, UInt256.random(), null)
encryptedBallots.add(encryptedBallot)
previousTrackingHash = encryptedBallot.confirmationCode
}
return encryptedBallots
}

fun encrypt(ballot: PlaintextBallot, codeBaux : ByteArray, ballotNonce: UInt256? = null, timestampOverride: Long? = null): CiphertextBallot {
return ballot.encryptBallot(codeBaux, ballotNonce ?: UInt256.random(), timestampOverride)
fun encrypt(
ballot: PlaintextBallot,
codeBaux : ByteArray,
errs: ErrorMessages,
ballotNonce: UInt256? = null,
timestampOverride: Long? = null
): CiphertextBallot? {
return ballot.encryptBallot(codeBaux, errs, ballotNonce ?: UInt256.random(), timestampOverride)
}

private fun PlaintextBallot.encryptBallot(
codeBaux : ByteArray,
errs: ErrorMessages,
ballotNonce: UInt256,
timestampOverride: Long? = null,
): CiphertextBallot {
): CiphertextBallot? {
val plaintextContests = this.contests.associateBy { it.contestId }

val encryptedContests = mutableListOf<CiphertextBallot.Contest>()
for (mcontest in manifest.contestsForBallotStyle(this.ballotStyle)) {
val manifestContests = manifest.contestsForBallotStyle(this.ballotStyle)
if (manifestContests == null || manifestContests.isEmpty()) {
errs.add("Manifest does not have ballotStyle ${this.ballotStyle} or it has no contests for that ballotStyle")
return null
}
for (mcontest in manifestContests) {
// If no contest on the ballot, create a well formed contest with all zeroes
val pcontest = plaintextContests[mcontest.contestId] ?: makeZeroContest(mcontest)
encryptedContests.add(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class RandomBallotProvider(val manifest: Manifest, val nballots: Int = 11) {
}

fun ballots(ballotStyleId: String? = null): List<PlaintextBallot> {
val ballots: MutableList<PlaintextBallot> = ArrayList()
val ballots = mutableListOf<PlaintextBallot>()
val useStyle = ballotStyleId ?: manifest.ballotStyles[0].ballotStyleId
for (i in 0 until nballots) {
val ballotId = if (useSequential) "id-" + sequentialId++ else "id" + Random.nextInt()
Expand All @@ -40,16 +40,16 @@ class RandomBallotProvider(val manifest: Manifest, val nballots: Int = 11) {
if (manifest.ballotStyles.find { it.ballotStyleId == ballotStyleId } == null) {
throw RuntimeException("BallotStyle '$ballotStyleId' not in the given manifest styles = ${manifest.ballotStyles}")
}
val contests: MutableList<PlaintextBallot.Contest> = ArrayList()
for (contestp in manifest.contestsForBallotStyle(ballotStyleId)) {
val contests = mutableListOf<PlaintextBallot.Contest>()
for (contestp in manifest.contestsForBallotStyle(ballotStyleId)!!) {
contests.add(makeContestFrom(contestp as Manifest.ContestDescription))
}
return PlaintextBallot(ballotId, ballotStyleId, contests)
}

fun makeContestFrom(contest: Manifest.ContestDescription): PlaintextBallot.Contest {
var voted = 0
val selections: MutableList<PlaintextBallot.Selection> = ArrayList()
val selections = mutableListOf<PlaintextBallot.Selection>()
val nselections = contest.selections.size

for (selection_description in contest.selections) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import kotlin.experimental.xor

/**
* A Trustee that knows its own secret key and polynomial, used during the Key Ceremony.
* This must stay private. Guardian is its public info in the election record.
* This must stay private. Guardian contains its public info in the election record.
* DecryptingTrustee is its instantiation for decrypting.
*/
open class KeyCeremonyTrustee(
val group: GroupContext,
Expand Down Expand Up @@ -284,7 +285,7 @@ open class KeyCeremonyTrustee(
// == (P1 (ℓ) + P2 (ℓ) + · · · + Pn (ℓ)) mod q. spec 2.0.0, eq 65.
internal fun computeSecretKeyShare(): ElementModQ {
if (nguardians != myShareOfOthers.size + 1) {
throw RuntimeException("requires nguardians ${nguardians} but have ${myShareOfOthers.size + 1} shares")
throw RuntimeException("KeyCeremonyTrustee.computeSecretKeyShare: requires nguardians ${nguardians} but have ${myShareOfOthers.size + 1} shares")
}
var result: ElementModQ = polynomial.valueAt(group, xCoordinate)
myShareOfOthers.values.forEach{ result += it.yCoordinate }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class PreEncryptor(
codeBaux : ByteArray = ByteArray(0)
): PreEncryptedBallot {

val mcontests = manifest.contestsForBallotStyle(ballotStyleId)
val mcontests = manifest.contestsForBallotStyle(ballotStyleId)!!
val preeContests = mcontests.sortedBy { it.sequenceOrder }.map {
it.preencryptContest(manifest.contestLimit(it.contestId), primaryNonce)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ private fun electionguard.protogen.EncryptedKeyShare.import(id: String, group: G

///////////////////////////////////////////////////////////////////////////////

fun KeyCeremonyTrustee.publishDecryptingTrusteeProto() =
fun KeyCeremonyTrustee.publishProto() =
electionguard.protogen.DecryptingTrustee(
this.id(),
this.xCoordinate(),
Expand Down
Loading

0 comments on commit 1231d17

Please sign in to comment.