Skip to content

Commit

Permalink
Merge pull request #439 from JohnLCaron/protoremove1
Browse files Browse the repository at this point in the history
Remove protobuf serialization for ContestData, use ad-hoc ByteArraySerialization
  • Loading branch information
JohnLCaron authored Dec 13, 2023
2 parents 8e95baf + 417dd8a commit 23b318f
Show file tree
Hide file tree
Showing 15 changed files with 311 additions and 162 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package electionguard.ballot

import com.github.michaelbull.result.Result

expect fun ContestData.encodeToByteArray(fill: String? = null): ByteArray

expect fun ByteArray.decodeToContestData() : Result<ContestData, String>

71 changes: 7 additions & 64 deletions egklib/src/commonMain/kotlin/electionguard/ballot/ContestData.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package electionguard.ballot

import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import electionguard.core.*
import io.github.oshai.kotlinlogging.KotlinLogging
import pbandk.decodeFromByteArray
import pbandk.encodeToByteArray
import kotlin.math.max

private val logger = KotlinLogging.logger("ContestData")
Expand All @@ -27,30 +24,6 @@ data class ContestData(
val writeIns: List<String>,
val status: ContestDataStatus = if (overvotes.isNotEmpty()) ContestDataStatus.over_vote else ContestDataStatus.normal,
) {

fun publish(filler: String = ""): electionguard.protogen.ContestData {
return publish(
this.status,
this.overvotes,
this.writeIns,
filler,
)
}

fun publish(
status: ContestDataStatus,
overvotes: List<Int>,
writeIns: List<String>,
filler: String = ""
): electionguard.protogen.ContestData {
return electionguard.protogen.ContestData(
status.publishContestDataStatus(),
overvotes,
writeIns,
filler,
)
}

// Make sure that the HashedElGamalCiphertext message is exactly (votesAllowed + 1) * BLOCK_SIZE
// If too large, remove extra writeIns, add "*" to list to indicate some were removed
// If still too large, truncate writeIns to CHOP_WRITE_INS characters, append "*" to string to indicate truncated
Expand All @@ -68,7 +41,7 @@ data class ContestData(
val messageSize = (1 + contestLimit) * BLOCK_SIZE

var trialContestData = this
var trialContestDataBA = trialContestData.publish().encodeToByteArray()
var trialContestDataBA = trialContestData.encodeToByteArray()
var trialSize = trialContestDataBA.size
val trialSizes = mutableListOf<Int>()
trialSizes.add(trialSize)
Expand All @@ -81,7 +54,7 @@ data class ContestData(
trialContestData = trialContestData.copy(
writeIns = truncateWriteIns,
)
trialContestDataBA = trialContestData.publish().encodeToByteArray()
trialContestDataBA = trialContestData.encodeToByteArray()
trialSize = trialContestDataBA.size
trialSizes.add(trialSize)
}
Expand All @@ -93,7 +66,7 @@ data class ContestData(
if (it.length <= CHOP_WRITE_INS) it else it.substring(0, chop) + "*"
}
trialContestData = trialContestData.copy(writeIns = truncateWriteIns)
trialContestDataBA = trialContestData.publish().encodeToByteArray()
trialContestDataBA = trialContestData.encodeToByteArray()
trialSize = trialContestDataBA.size
trialSizes.add(trialSize)
}
Expand All @@ -102,7 +75,7 @@ data class ContestData(
while (trialSize > messageSize && (trialContestData.overvotes.size > contestLimit + 1)) {
val chopList = trialContestData.overvotes.subList(0, contestLimit + 1) + (-1)
trialContestData = trialContestData.copy(overvotes = chopList)
trialContestDataBA = trialContestData.publish().encodeToByteArray()
trialContestDataBA = trialContestData.encodeToByteArray()
trialSize = trialContestDataBA.size
trialSizes.add(trialSize)
}
Expand All @@ -112,7 +85,7 @@ data class ContestData(
val filler = StringBuilder().apply {
repeat(messageSize - trialSize - 2) { append("*") }
}
trialContestDataBA = trialContestData.publish(filler.toString()).encodeToByteArray()
trialContestDataBA = trialContestData.encodeToByteArray(filler.toString())
trialSize = trialContestDataBA.size
trialSizes.add(trialSize)
}
Expand Down Expand Up @@ -204,8 +177,7 @@ fun HashedElGamalCiphertext.decryptWithBetaToContestData(

val ba: ByteArray = this.decryptContestData(publicKey, extendedBaseHash, contestId, c0, beta) ?:
return Err( "decryptWithBetaToContestData did not succeed")
val proto = electionguard.protogen.ContestData.decodeFromByteArray(ba)
return importContestData(proto)
return ba.decodeToContestData()
}

fun HashedElGamalCiphertext.decryptWithNonceToContestData(
Expand All @@ -220,8 +192,7 @@ fun HashedElGamalCiphertext.decryptWithNonceToContestData(
val (alpha, beta) = 0.encrypt(publicKey, contestDataNonce.toElementModQ(group))
val ba: ByteArray = this.decryptContestData(publicKey, extendedBaseHash, contestId, alpha, beta) ?:
return Err( "decryptWithNonceToContestData did not succeed")
val proto = electionguard.protogen.ContestData.decodeFromByteArray(ba)
return importContestData(proto)
return ba.decodeToContestData()
}

fun HashedElGamalCiphertext.decryptWithSecretKey(
Expand Down Expand Up @@ -266,32 +237,4 @@ fun HashedElGamalCiphertext.decryptContestData(
}
}

//////////////////////////////////////////////////////////////////
// LOOK maybe move to protoconvert

fun importContestData(proto : electionguard.protogen.ContestData?): Result<ContestData, String> {
if (proto == null) return Err( "ContestData is missing")
return Ok(ContestData(
proto.overVotes,
proto.writeIns,
importContestDataStatus(proto.status)?: ContestDataStatus.normal,
))
}

private fun importContestDataStatus(proto: electionguard.protogen.ContestData.Status): ContestDataStatus? {
val result = safeEnumValueOf<ContestDataStatus>(proto.name)
if (result == null) {
logger.error { "ContestDataStatus $proto has missing or unknown name" }
}
return result
}

private fun ContestDataStatus.publishContestDataStatus(): electionguard.protogen.ContestData.Status {
return try {
electionguard.protogen.ContestData.Status.fromName(this.name)
} catch (e: IllegalArgumentException) {
logger.error { "ContestDataStatus $this has missing or unknown name" }
electionguard.protogen.ContestData.Status.NORMAL
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ data class DecryptedTallyOrBallot(

data class DecryptedContestData(
val contestData: ContestData,
val encryptedContestData : HashedElGamalCiphertext, // same as EncryptedTally.Selection.ciphertext
val encryptedContestData : HashedElGamalCiphertext, // same as EncryptedTally.Contest.contestData
val proof: ChaumPedersenProof,
var beta: ElementModP, // needed to verify 10.2
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import electionguard.ballot.decryptWithBetaToContestData
import electionguard.core.*
import electionguard.util.ErrorMessages
import electionguard.util.Stats
import io.github.oshai.kotlinlogging.KotlinLogging

private const val doVerifierSelectionProof = true

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import electionguard.ballot.EncryptedBallot
import electionguard.core.*
import electionguard.core.Base16.fromHex
import electionguard.core.Base16.toHex
import electionguard.preencrypt.RecordedPreBallot
import electionguard.preencrypt.RecordedPreEncryption
import electionguard.preencrypt.RecordedSelectionVector
import electionguard.util.ErrorMessages
import kotlinx.serialization.Serializable

Expand Down Expand Up @@ -139,6 +142,64 @@ fun EncryptedSelectionJson.import(group : GroupContext, errs : ErrorMessages): E
)
}

////////////////////////////////////////////////////////////////////////////////////////////////
// preencrypt

fun EncryptedBallot.publishJson(recordedPreBallot: RecordedPreBallot) = EncryptedBallotJson(
this.ballotId,
this.ballotStyleId,
this.encryptingDevice,
this.timestamp,
this.codeBaux.toHex(),
this.confirmationCode.publishJson(),
this.electionId.publishJson(),
this.contests.map { it.publishJson(recordedPreBallot) },
this.state.name,
this.encryptedSn?.publishJson(),
true,
null,
)

private fun EncryptedBallot.Contest.publishJson(recordedPreBallot: RecordedPreBallot): EncryptedContestJson {

val rcontest = recordedPreBallot.contests.find { it.contestId == this.contestId }
?: throw IllegalArgumentException("Cant find ${this.contestId}")

return EncryptedContestJson(
this.contestId,
this.sequenceOrder,
this.votesAllowed,
this.contestHash.publishJson(),
this.selections.map {
EncryptedSelectionJson(
it.selectionId,
it.sequenceOrder,
it.encryptedVote.publishJson(),
it.proof.publishJson(),
)
},
this.proof.publishJson(),
this.contestData.publishJson(),
rcontest.publishJson(),
)
}

private fun RecordedPreEncryption.publishJson(): PreEncryptionJson {
return PreEncryptionJson(
this.preencryptionHash.publishJson(),
this.allSelectionHashes.map { it.publishJson() },
this.selectedVectors.map { it.publishJson() },
)
}

private fun RecordedSelectionVector.publishJson(): SelectionVectorJson {
return SelectionVectorJson(
this.selectionHash.toUInt256safe().publishJson(),
this.shortCode,
this.encryptions.map { it.publishJson() },
)
}

@Serializable
data class PreEncryptionJson(
val preencryption_hash: UInt256Json,
Expand Down Expand Up @@ -192,4 +253,4 @@ fun SelectionVectorJson.import(group: GroupContext, errs: ErrorMessages): Encryp
this.short_code,
encryptions.filterNotNull(),
)
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package electionguard.protoconvert

import com.github.michaelbull.result.Err
import com.github.michaelbull.result.Ok
import com.github.michaelbull.result.Result
import com.github.michaelbull.result.unwrap
import electionguard.ballot.DecryptedTallyOrBallot
import electionguard.ballot.importContestData
import electionguard.core.ElGamalCiphertext
import electionguard.core.ElementModP
import electionguard.core.GroupContext
import electionguard.core.UInt256
import electionguard.ballot.*
import electionguard.core.*
import electionguard.util.ErrorMessages

fun electionguard.protogen.DecryptedTallyOrBallot.import(group: GroupContext, errs : ErrorMessages): DecryptedTallyOrBallot? {
Expand Down Expand Up @@ -109,4 +107,36 @@ private fun DecryptedTallyOrBallot.DecryptedContestData.publishProto() =
this.encryptedContestData.publishProto(),
this.proof.publishProto(),
this.beta.publishProto(),
)
)

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

fun ContestData.publish(filler: String = ""): electionguard.protogen.ContestData {
return electionguard.protogen.ContestData(
status.publishContestDataStatus(),
overvotes,
writeIns,
filler,
)
}

fun importContestData(proto : electionguard.protogen.ContestData?): Result<ContestData, String> {
if (proto == null) return Err( "ContestData is missing")
return Ok(ContestData(
proto.overVotes,
proto.writeIns,
importContestDataStatus(proto.status)?: ContestDataStatus.normal,
))
}

private fun importContestDataStatus(proto: electionguard.protogen.ContestData.Status): ContestDataStatus? {
return safeEnumValueOf<ContestDataStatus>(proto.name)
}

private fun ContestDataStatus.publishContestDataStatus(): electionguard.protogen.ContestData.Status {
return try {
electionguard.protogen.ContestData.Status.fromName(this.name)
} catch (e: IllegalArgumentException) {
electionguard.protogen.ContestData.Status.NORMAL
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import electionguard.ballot.*
import electionguard.core.UInt256
import electionguard.keyceremony.KeyCeremonyTrustee

/** Read/write the Election Record as protobuf files. */
/** Read/write the Election Record as JSON files. */
expect class PublisherJson(topDir: String, createNew: Boolean = false) : Publisher {
override fun isJson() : Boolean

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package electionguard.preencrypt

import com.github.michaelbull.result.unwrap
import electionguard.ballot.Manifest
import electionguard.core.*
import electionguard.encrypt.cast
import electionguard.cli.ManifestBuilder
import electionguard.protoconvert.import
import electionguard.protoconvert.publishProto
import electionguard.json2.import
import electionguard.json2.publishJson
import electionguard.publish.makePublisher
import electionguard.publish.readElectionRecord
import electionguard.util.ErrorMessages
Expand All @@ -15,7 +14,7 @@ import kotlin.test.Test

private val random = Random

internal class PreEncryptorOutputTest {
class PreEncryptorOutputTest {

// multiple selections per contest
@Test
Expand Down Expand Up @@ -100,10 +99,10 @@ internal class PreEncryptorOutputTest {
println()
}

// roundtrip through the proto, combines the recordedBallot
// roundtrip through the serialization, which combines the recordedBallot
val encryptedBallot = ciphertextBallot.cast()
val proto = encryptedBallot.publishProto(recordedBallot)
val fullEncryptedBallot = proto.import(group, ErrorMessages(""))!!
val json = encryptedBallot.publishJson(recordedBallot)
val fullEncryptedBallot = json.import(group, ErrorMessages(""))!!

// show what ends up in the election record
if (show) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import electionguard.core.*
import electionguard.decryptBallot.DecryptPreencryptWithNonce
import electionguard.encrypt.cast
import electionguard.cli.ManifestBuilder
import electionguard.protoconvert.import
import electionguard.protoconvert.publishProto
import electionguard.json2.import
import electionguard.json2.publishJson
import electionguard.publish.readElectionRecord
import electionguard.util.ErrorMessages
import electionguard.util.Stats
Expand All @@ -26,8 +26,8 @@ import kotlin.test.*

private val random = Random

internal class PreEncryptorTest {
val input = "src/commonTest/data/workflow/allAvailableProto"
class PreEncryptorTest {
val input = "src/commonTest/data/workflow/allAvailableJson"
val group = productionGroup()

// sanity check that PreEncryptor.preencrypt doesnt barf
Expand Down Expand Up @@ -227,10 +227,10 @@ internal fun runComplete(
println()
}

// roundtrip through the proto, combines the recordedBallot
// roundtrip through the serialization, which combines the recordedBallot
val encryptedBallot = ciphertextBallot.cast()
val proto = encryptedBallot.publishProto(recordedBallot)
val fullEncryptedBallot = proto.import(group, ErrorMessages(""))!!
val json = encryptedBallot.publishJson(recordedBallot)
val fullEncryptedBallot = json.import(group, ErrorMessages(""))!!

// show what ends up in the election record
if (show) {
Expand Down
Loading

0 comments on commit 23b318f

Please sign in to comment.