diff --git a/egklib/src/commonMain/kotlin/electionguard/encrypt/AddEncryptedBallot.kt b/egklib/src/commonMain/kotlin/electionguard/encrypt/AddEncryptedBallot.kt index 784c80a2..7919b6b4 100644 --- a/egklib/src/commonMain/kotlin/electionguard/encrypt/AddEncryptedBallot.kt +++ b/egklib/src/commonMain/kotlin/electionguard/encrypt/AddEncryptedBallot.kt @@ -63,7 +63,7 @@ class AddEncryptedBallot( throw RuntimeException("ManifestInputValidation FAILED $errors") } - val consumer = makeConsumer(outputDir, group, isJson) + val consumer = makeConsumer(group, outputDir, isJson) val chainResult = consumer.readEncryptedBallotChain(deviceName) if (chainResult is Ok) { // this is a restart on an existing chain diff --git a/egklib/src/commonMain/kotlin/electionguard/pep/BallotPep.kt b/egklib/src/commonMain/kotlin/electionguard/pep/BallotPep.kt new file mode 100644 index 00000000..e927c9b9 --- /dev/null +++ b/egklib/src/commonMain/kotlin/electionguard/pep/BallotPep.kt @@ -0,0 +1,125 @@ +package electionguard.pep + +import electionguard.ballot.DecryptedTallyOrBallot +import electionguard.core.* +import electionguard.json2.* +import kotlinx.serialization.Serializable + +data class BallotPep( + val ballotId: String, + val isEq: Boolean, + val contests: List, +) + +data class ContestPep( + val contestId: String, + val selections: List, +) + +data class SelectionPep( + val selectionId: String, + val ciphertextRatio: ElGamalCiphertext, // α, β + val ciphertextAB: ElGamalCiphertext, // A, B + val blindingProof: ChaumPedersenProof, + val T: ElementModP, + val decryptionProof: ChaumPedersenProof, +) { + + constructor(step1: PepSimple.SelectionStep1, dselection: DecryptedTallyOrBallot.Selection) : this ( + dselection.selectionId, + step1.ciphertextRatio, + step1.ciphertextAB, + ChaumPedersenProof(step1.c, step1.v), + dselection.bOverM, + dselection.proof, + ) + constructor(selection: PepTrusted.SelectionWorking, dselection: DecryptedTallyOrBallot.Selection) : this( + dselection.selectionId, + selection.ciphertextRatio, + selection.ciphertextAB, + ChaumPedersenProof(selection.c, selection.v), + dselection.bOverM, + dselection.proof, + ) + constructor(work: BlindWorking, dselection: DecryptedTallyOrBallot.Selection) : this ( + dselection.selectionId, + work.ciphertextRatio, + work.ciphertextAB!!, + ChaumPedersenProof(work.c, work.v!!), + dselection.bOverM, + dselection.proof, + ) +} + +@Serializable +data class BallotPepJson( + val ballot_id: String, + val is_equal: Boolean, + val contests: List, +) + +@Serializable +data class ContestPepJson( + val contest_id: String, + val selections: List, +) + +@Serializable +data class SelectionPepJson( + val selection_id: String, + val ciphertext_ratio: ElGamalCiphertextJson, + val ciphertext_AB: ElGamalCiphertextJson, + val blinding_proof: ChaumPedersenJson, + val T: ElementModPJson, + val decryption_proof: ChaumPedersenJson, +) + +fun BallotPep.publishJson(): BallotPepJson { + val contests = this.contests.map { pcontest -> + + // val selectionId: String, + // val ciphertextRatio: ElGamalCiphertext, // α, β + // val ciphertextAB: ElGamalCiphertext, // A, B + // val blindingProof: ChaumPedersenProof, + // val T: ElementModP, + // val decryptionProof: ChaumPedersenProof, + ContestPepJson( + pcontest.contestId, + pcontest.selections.map { + SelectionPepJson( + it.selectionId, + it.ciphertextRatio.publishJson(), + it.ciphertextAB.publishJson(), + it.blindingProof.publishJson(), + it.T.publishJson(), + it.decryptionProof.publishJson(), + ) + }) + } + return BallotPepJson(this.ballotId, this.isEq, contests) +} + +fun BallotPepJson.import(group: GroupContext): BallotPep { + val contests = this.contests.map { pcontest -> + + // val selection_id: String, + // val ciphertext_ratio: ElGamalCiphertextJson, + // val ciphertext_AB: ElGamalCiphertextJson, + // val blinding_proof: ChaumPedersenJson, + // val T: ElementModPJson, + // val decryption_proof: ChaumPedersenJson, + ContestPep( + pcontest.contest_id, + pcontest.selections.map { + SelectionPep( + it.selection_id, + it.ciphertext_ratio.import(group), + it.ciphertext_AB.import(group), + it.blinding_proof.import(group), + it.T.import(group), + it.decryption_proof.import(group), + ) + }) + } + return BallotPep(this.ballot_id, this.is_equal, contests) +} \ No newline at end of file diff --git a/egklib/src/commonMain/kotlin/electionguard/pep/PepBlindTrust.kt b/egklib/src/commonMain/kotlin/electionguard/pep/PepBlindTrust.kt index c91f9fdc..760ce9f3 100644 --- a/egklib/src/commonMain/kotlin/electionguard/pep/PepBlindTrust.kt +++ b/egklib/src/commonMain/kotlin/electionguard/pep/PepBlindTrust.kt @@ -22,6 +22,7 @@ class PepBlindTrust( decryptingTrustees: List, // the trustees available to decrypt ) : PepAlgorithm { val decryptor = DecryptorDoerre(group, extendedBaseHash, jointPublicKey, guardians, decryptingTrustees) + val stats = Stats() // test if ballot1 and ballot2 are equivalent or not. override fun testEquivalent(ballot1: EncryptedBallot, ballot2: EncryptedBallot): Result { @@ -34,6 +35,8 @@ class PepBlindTrust( } override fun doEgkPep(ballot1: EncryptedBallot, ballot2: EncryptedBallot): Result { + val startPep = System.currentTimeMillis() + // LOOK check ballotIds match, styleIds? val errorMesses = ValidationMessages("Ballot '${ballot1.ballotId}'", 1) if (ballot1.ballotId != ballot2.ballotId) { @@ -64,7 +67,7 @@ class PepBlindTrust( // (d) Compute aj = α^uj mod p and bj = β^uj mod p // (e) Send (Aj, Bj, aj, bj) to admin // step 1: for each trustee, a list of its responses - val step1s : List> = blindTrustees.map { it.blind(ratioCiphertexts) } + val step1s: List> = blindTrustees.map { it.blind(ratioCiphertexts) } require(step1s.size == nb) // list(nd, texts) // for each ciphertext, a list of responses from all the trustees @@ -123,7 +126,7 @@ class PepBlindTrust( // step 3: form all the challenges to each trustee, put them in working // while were at it , we also need to know the original BlindResponse, so put those in working - blindResponses.zip(work23s).map { (responsesForTrustees : List, work23 : BlindWorking) -> + blindResponses.zip(work23s).map { (responsesForTrustees: List, work23: BlindWorking) -> responsesForTrustees.forEach { br -> work23.blindChallenges.add(BlindChallenge(work23.c, br.eps, br.u)) } @@ -136,7 +139,7 @@ class PepBlindTrust( // send to admin // step 4: gather all challenges, get response from each trustee - val step4 : List> = blindTrustees.mapIndexed { idx, trustee -> + val step4: List> = blindTrustees.mapIndexed { idx, trustee -> val allChallengesForIthTrustee = work23s.map { it.blindChallenges[idx] } trustee.challenge(allChallengesForIthTrustee) } @@ -165,7 +168,7 @@ class PepBlindTrust( require(bjp == br.bj) } // cough, cough, while were at it: 6(b) v = Sum_dj(vj) - work23.v = with (group) { step4.map { it.response }.addQ() } + work23.v = with(group) { step4.map { it.response }.addQ() } } // create an EncryptedBallot with the ciphertexts = (A, B), this is what we decrypt @@ -173,7 +176,7 @@ class PepBlindTrust( val contestsAB = ballot1.contests.map { contest -> val selectionsAB = - contest.selections.map{ selection -> + contest.selections.map { selection -> val work = workIterator.next() work.ciphertextAB = ElGamalCiphertext(work.bigA, work.bigB) selection.copy(encryptedVote = work.ciphertextAB!!) @@ -184,7 +187,7 @@ class PepBlindTrust( //6. admin: // (a) decrypt (A, B): (T, ChaumPedersenProof(c',v')) = EGDecrypt(A, B) // 8*nd - val decryption : DecryptedTallyOrBallot = decryptor.decryptPep(ballotAB) + val decryption: DecryptedTallyOrBallot = decryptor.decryptPep(ballotAB) // (b) v = Sum_dj(vj), IsEq = (T == 1) // (c) Send (IsEq, c, v, α, β, c′, v′, A, B, T) to V and publish to BB. @@ -193,40 +196,20 @@ class PepBlindTrust( val contestsPEP = decryption.contests.map { dContest -> val selectionsPEP = - dContest.selections.map{ dSelection -> + dContest.selections.map { dSelection -> isEq = isEq && (dSelection.bOverM == group.ONE_MOD_P) - if (!isEq) - println("wtf") val work = workIterator.next() SelectionPep(work, dSelection) } ContestPep(dContest.contestId, selectionsPEP) } - val ballotPEP = BallotPep(isEq, decryption.id, contestsPEP) + val ballotPEP = BallotPep(decryption.id, isEq, contestsPEP) + + stats.of("egkPep", "selection").accum(getSystemTimeInMillis() - startPep, ntexts) // step 6: verify val verifyResult = VerifierPep(group, extendedBaseHash, jointPublicKey).verify(ballotPEP) return if (verifyResult is Ok) Ok(ballotPEP) else Err(verifyResult.getError()!!) -} - - data class BallotWorking( - val ballotId: String, - val contests: List, - ) - - data class ContestWorking( - val contestId: String, - val selections: List, - ) - - inner class SelectionWorking( - val selectionId: String, - val ciphertextRatio: ElGamalCiphertext, - val ciphertextAB: ElGamalCiphertext, - val a: ElementModP, - val b: ElementModP - ) { - var c: ElementModQ = group.ZERO_MOD_Q - var v: ElementModQ = group.ZERO_MOD_Q } + } diff --git a/egklib/src/commonMain/kotlin/electionguard/pep/PepSimple.kt b/egklib/src/commonMain/kotlin/electionguard/pep/PepSimple.kt index ab0ec5c1..282ba9b2 100644 --- a/egklib/src/commonMain/kotlin/electionguard/pep/PepSimple.kt +++ b/egklib/src/commonMain/kotlin/electionguard/pep/PepSimple.kt @@ -93,7 +93,7 @@ class PepSimple( } ContestPep(dContest.contestId, selectionsPEP) } - val ballotPEP = BallotPep(isEq, decryption.id, contestsPEP) + val ballotPEP = BallotPep(decryption.id, isEq, contestsPEP) // step 6: verify val verifyResult = VerifierPep(group, extendedBaseHash, jointPublicKey).verify(ballotPEP) diff --git a/egklib/src/commonMain/kotlin/electionguard/pep/PepTrusted.kt b/egklib/src/commonMain/kotlin/electionguard/pep/PepTrusted.kt index 0f857d82..8e03ade4 100644 --- a/egklib/src/commonMain/kotlin/electionguard/pep/PepTrusted.kt +++ b/egklib/src/commonMain/kotlin/electionguard/pep/PepTrusted.kt @@ -11,7 +11,6 @@ import electionguard.input.ValidationMessages import mu.KotlinLogging private val logger = KotlinLogging.logger("DistPep") -private var first = false // "Distributed Plaintext Equivalence Proof", distributed with trusted admin. class PepTrusted( @@ -20,7 +19,7 @@ class PepTrusted( val jointPublicKey: ElGamalPublicKey, val guardians: Guardians, // all guardians decryptingTrustees: List, // the trustees available to decrypt - val nag : Int, // number of admin guardians + val nag: Int, // number of admin guardians ) : PepAlgorithm { val decryptor = DecryptorDoerre(group, extendedBaseHash, jointPublicKey, guardians, decryptingTrustees) @@ -40,7 +39,7 @@ class PepTrusted( * Each decrypted selection has T == 1 (or not) and a corresponding proof. * Note that the two encrypted ballots must have been decrypted by the same encryptor, using * the same parameters (extendedBaseHash, jointPublicKey, guardians, decryptingTrustees). - */ + */ override fun doEgkPep(ballot1: EncryptedBallot, ballot2: EncryptedBallot): Result { // LOOK check ballotIds match, styleIds? val errorMesses = ValidationMessages("Ballot '${ballot1.ballotId}'", 1) @@ -99,62 +98,61 @@ class PepTrusted( // step 2. Use standard algorithm to decrypt ciphertextAB, and get the decryption proof: // (T, ChaumPedersenProof(c',v')) = EGDecrypt(A, B) - val decryption : DecryptedTallyOrBallot = decryptor.decryptPep(ballotAB) + val decryption: DecryptedTallyOrBallot = decryptor.decryptPep(ballotAB) // step 3,4,5 compute challenge and get guardian responses and verify them ballotWorking.contests.map { contest -> - val selectionsW = - contest.selections.map { selection -> - // collective challenge - val c = hashFunction( - extendedBaseHash.bytes, - 0x42.toByte(), - jointPublicKey.key, - selection.ciphertextRatio.pad, selection.ciphertextRatio.data, - selection.ciphertextAB.pad, selection.ciphertextAB.data, - selection.a, - selection.b, - ).toElementModQ(group) + contest.selections.map { selection -> + // collective challenge + val c = hashFunction( + extendedBaseHash.bytes, + 0x42.toByte(), + jointPublicKey.key, + selection.ciphertextRatio.pad, selection.ciphertextRatio.data, + selection.ciphertextAB.pad, selection.ciphertextAB.data, + selection.a, + selection.b, + ).toElementModQ(group) - var vSum = group.ZERO_MOD_Q - admins.forEach { admin -> - val selectionKey = "${contest.contestId}#${selection.selectionId}" - val sel = admin.selectionMap[selectionKey]!! - val vi = sel.challenge(c) - vSum = vSum + vi + var vSum = group.ZERO_MOD_Q + admins.forEach { admin -> + val selectionKey = "${contest.contestId}#${selection.selectionId}" + val sel = admin.selectionMap[selectionKey]!! + val vi = sel.challenge(c) + vSum = vSum + vi - // (5.a) for each guardian, verify that aj = α^vj * Aj^cj and bj = β^vj * Bj^c for any j - val ratio = sel.ciphertextRatio - val AB = sel.ciphertextAB - val verifya = (ratio.pad powP vi) * (AB.pad powP c) - if ((verifya != sel.a)) { - println(" selection ${selectionKey} verifya failed for guardian ${admin.idx}") - errorMesses.add("verifya failed on '$selectionKey' for guardian ${admin.idx}") - } - val verifyb = (ratio.data powP vi) * (AB.data powP c) - if ((verifyb != sel.b)) { - println(" selection ${selectionKey} verifyb failed for guardian ${admin.idx}") - errorMesses.add("verifyb failed on '$selectionKey' for guardian ${admin.idx}") - } + // (5.a) for each guardian, verify that aj = α^vj * Aj^cj and bj = β^vj * Bj^c for any j + val ratio = sel.ciphertextRatio + val AB = sel.ciphertextAB + val verifya = (ratio.pad powP vi) * (AB.pad powP c) + if ((verifya != sel.a)) { + println(" selection ${selectionKey} verifya failed for guardian ${admin.idx}") + errorMesses.add("verifya failed on '$selectionKey' for guardian ${admin.idx}") + } + val verifyb = (ratio.data powP vi) * (AB.data powP c) + if ((verifyb != sel.b)) { + println(" selection ${selectionKey} verifyb failed for guardian ${admin.idx}") + errorMesses.add("verifyb failed on '$selectionKey' for guardian ${admin.idx}") + } - /* 5.a for each guardian: - // verify if ChaumPedersenProof(cj, vj).verify(cons0; {cons1, K}, α, β, Aj, Bj), otherwise, reject. [step 1.j in org] - val verify5a = ChaumPedersenProof(c, vi).verify( - extendedBaseHash, - 0x42.toByte(), - jointPublicKey.key, - sel.ciphertextRatio.pad,sel. ciphertextRatio.data, - sel.ciphertextAB.pad, sel.ciphertextAB.data, - ) - if (!verify5a) { - println(" selection ${selectionKey} verify5a = $verify5a for guardian ${admin.idx}") - // errorMesses.add("verify5a failed on '$selectionKey' for guardian ${admin.idx}") - } - */ + /* 5.a for each guardian: + // verify if ChaumPedersenProof(cj, vj).verify(cons0; {cons1, K}, α, β, Aj, Bj), otherwise, reject. [step 1.j in org] + val verify5a = ChaumPedersenProof(c, vi).verify( + extendedBaseHash, + 0x42.toByte(), + jointPublicKey.key, + sel.ciphertextRatio.pad,sel. ciphertextRatio.data, + sel.ciphertextAB.pad, sel.ciphertextAB.data, + ) + if (!verify5a) { + println(" selection ${selectionKey} verify5a = $verify5a for guardian ${admin.idx}") + // errorMesses.add("verify5a failed on '$selectionKey' for guardian ${admin.idx}") } - selection.c = c - selection.v = vSum + */ } + selection.c = c + selection.v = vSum + } } // 5. admin: @@ -174,7 +172,7 @@ class PepTrusted( } ContestPep(dContest.contestId, selectionsPEP) } - val ballotPEP = BallotPep(isEq, decryption.id, contestsPEP) + val ballotPEP = BallotPep(decryption.id, isEq, contestsPEP) // step 6: verify val verifyResult = VerifierPep(group, extendedBaseHash, jointPublicKey).verify(ballotPEP) @@ -203,7 +201,7 @@ class PepTrusted( } } -class AdminGuardian(val group: GroupContext, val idx: Int, ratioBallot : EncryptedBallot) { +class AdminGuardian(val group: GroupContext, val idx: Int, ratioBallot: EncryptedBallot) { private val workingValues: BallotValues val selectionMap = mutableMapOf() @@ -257,7 +255,7 @@ class AdminGuardian(val group: GroupContext, val idx: Int, ratioBallot : Encrypt val a: ElementModP, val b: ElementModP, ) { - fun challenge(c : ElementModQ) : ElementModQ { + fun challenge(c: ElementModQ): ElementModQ { return u - c * eps } } diff --git a/egklib/src/commonMain/kotlin/electionguard/pep/VerifyPep.kt b/egklib/src/commonMain/kotlin/electionguard/pep/VerifyPep.kt index e9c6dad2..058b6991 100644 --- a/egklib/src/commonMain/kotlin/electionguard/pep/VerifyPep.kt +++ b/egklib/src/commonMain/kotlin/electionguard/pep/VerifyPep.kt @@ -3,63 +3,8 @@ package electionguard.pep import com.github.michaelbull.result.Err import com.github.michaelbull.result.Ok import com.github.michaelbull.result.Result -import electionguard.ballot.DecryptedTallyOrBallot import electionguard.core.* -data class BallotPep( - val isEq: Boolean, - val ballotId: String, - val contests: List, -) - -data class ContestPep( - val contestId: String, - val selections: List, -) - -data class SelectionPep( - val selectionId: String, - val ciphertextRatio: ElGamalCiphertext, // α, β - val ciphertextAB: ElGamalCiphertext, // A, B - val c: ElementModQ, // 1.e - val v: ElementModQ, // 1.f - val T: ElementModP, // step 2, T - val c_prime: ElementModQ, // step 2, c' - val v_prime: ElementModQ, // step 2, v' -) { - - constructor(step1: PepSimple.SelectionStep1, dselection: DecryptedTallyOrBallot.Selection) : this( - dselection.selectionId, - step1.ciphertextRatio, - step1.ciphertextAB, - step1.c, - step1.v, - dselection.bOverM, - dselection.proof.c, - dselection.proof.r, - ) - constructor(selection: PepTrusted.SelectionWorking, dselection: DecryptedTallyOrBallot.Selection) : this( - dselection.selectionId, - selection.ciphertextRatio, - selection.ciphertextAB, - selection.c, - selection.v, - dselection.bOverM, - dselection.proof.c, - dselection.proof.r, - ) - constructor(work: BlindWorking, dselection: DecryptedTallyOrBallot.Selection) : this ( - dselection.selectionId, - work.ciphertextRatio, - work.ciphertextAB!!, - work.c, - work.v!!, - dselection.bOverM, - dselection.proof.c, - dselection.proof.r, - ) -} - class VerifierPep( val group: GroupContext, val extendedBaseHash: UInt256, @@ -77,7 +22,7 @@ class VerifierPep( contest.selections.forEach { pep -> val selectionKey = "${contest.contestId}#${pep.selectionId}" - val verifya = ChaumPedersenProof(pep.c, pep.v).verify( + val verifya = pep.blindingProof.verify( extendedBaseHash, 0x42.toByte(), jointPublicKey.key, @@ -86,7 +31,7 @@ class VerifierPep( ) if (!verifya) errors.add("PEP test 3.a failed on ${selectionKey}") - val verifyb = ChaumPedersenProof(pep.c_prime, pep.v_prime).verifyDecryption( + val verifyb = pep.decryptionProof.verifyDecryption( extendedBaseHash, jointPublicKey.key, pep.ciphertextAB, diff --git a/egklib/src/commonMain/kotlin/electionguard/publish/Consumer.kt b/egklib/src/commonMain/kotlin/electionguard/publish/Consumer.kt index e9e26052..3c97ad2e 100644 --- a/egklib/src/commonMain/kotlin/electionguard/publish/Consumer.kt +++ b/egklib/src/commonMain/kotlin/electionguard/publish/Consumer.kt @@ -7,6 +7,7 @@ import electionguard.core.isDirectory import electionguard.core.pathExists import electionguard.decrypt.DecryptingTrusteeIF import electionguard.input.ManifestInputValidation +import electionguard.pep.BallotPep /** public API to read from the election record */ interface Consumer { @@ -29,23 +30,21 @@ interface Consumer { fun iterateAllSpoiledBallots(): Iterable = iterateAllEncryptedBallots{ it.state == EncryptedBallot.BallotState.SPOILED } fun hasEncryptedBallots() : Boolean - //// TODO: remove - fun iterateEncryptedBallots(filter : ((EncryptedBallot) -> Boolean)? ): Iterable - fun iterateCastBallots(): Iterable // encrypted ballots that are CAST - fun iterateSpoiledBallots(): Iterable // encrypted ballots that are SPOILED - fun iterateDecryptedBallots(): Iterable + fun iteratePepBallots(pepDir : String): Iterable //// not part of the election record, private data // read plaintext ballots in given directory, with filter fun iteratePlaintextBallots(ballotDir: String, filter : ((PlaintextBallot) -> Boolean)? ): Iterable // trustee in given directory for given guardianId fun readTrustee(trusteeDir: String, guardianId: String): DecryptingTrusteeIF + + fun readEncryptedBallot(ballotDir: String, ballotId: String) : Result } fun makeConsumer( - topDir: String, group: GroupContext, + topDir: String, isJson: Boolean? = null, // if not set, check if manifest.json file exists ): Consumer { val useJson = isJson ?: topDir.endsWith(".zip") || diff --git a/egklib/src/commonMain/kotlin/electionguard/publish/ConsumerJson.kt b/egklib/src/commonMain/kotlin/electionguard/publish/ConsumerJson.kt index 8102d262..fc7bdcd0 100644 --- a/egklib/src/commonMain/kotlin/electionguard/publish/ConsumerJson.kt +++ b/egklib/src/commonMain/kotlin/electionguard/publish/ConsumerJson.kt @@ -4,6 +4,7 @@ import com.github.michaelbull.result.Result import electionguard.ballot.* import electionguard.core.GroupContext import electionguard.decrypt.DecryptingTrusteeIF +import electionguard.pep.BallotPep expect class ConsumerJson (topDir: String, group: GroupContext) : Consumer { override fun topdir() : String @@ -21,13 +22,13 @@ expect class ConsumerJson (topDir: String, group: GroupContext) : Consumer { override fun readEncryptedBallotChain(device: String) : Result override fun iterateEncryptedBallots(device: String, filter : ((EncryptedBallot) -> Boolean)? ): Iterable override fun iterateAllEncryptedBallots(filter : ((EncryptedBallot) -> Boolean)? ): Iterable - override fun hasEncryptedBallots() : Boolean - override fun iterateEncryptedBallots(filter : ((EncryptedBallot) -> Boolean)? ): Iterable - override fun iterateCastBallots(): Iterable - override fun iterateSpoiledBallots(): Iterable + override fun iterateDecryptedBallots(): Iterable + override fun iteratePepBallots(pepDir : String): Iterable override fun iteratePlaintextBallots(ballotDir: String, filter : ((PlaintextBallot) -> Boolean)? ): Iterable override fun readTrustee(trusteeDir: String, guardianId: String): DecryptingTrusteeIF + + override fun readEncryptedBallot(ballotDir: String, ballotId: String) : Result } \ No newline at end of file diff --git a/egklib/src/commonMain/kotlin/electionguard/publish/ConsumerProto.kt b/egklib/src/commonMain/kotlin/electionguard/publish/ConsumerProto.kt index a4f35821..2e5706f6 100644 --- a/egklib/src/commonMain/kotlin/electionguard/publish/ConsumerProto.kt +++ b/egklib/src/commonMain/kotlin/electionguard/publish/ConsumerProto.kt @@ -4,6 +4,7 @@ import com.github.michaelbull.result.Result import electionguard.ballot.* import electionguard.core.GroupContext import electionguard.decrypt.DecryptingTrusteeIF +import electionguard.pep.BallotPep expect class ConsumerProto (topDir: String, groupContext: GroupContext) : Consumer { override fun topdir() : String @@ -21,13 +22,13 @@ expect class ConsumerProto (topDir: String, groupContext: GroupContext) : Consum override fun readEncryptedBallotChain(device: String) : Result override fun iterateEncryptedBallots(device: String, filter : ((EncryptedBallot) -> Boolean)? ): Iterable override fun iterateAllEncryptedBallots(filter : ((EncryptedBallot) -> Boolean)? ): Iterable - override fun hasEncryptedBallots() : Boolean - override fun iterateEncryptedBallots(filter : ((EncryptedBallot) -> Boolean)? ): Iterable - override fun iterateCastBallots(): Iterable // state = CAST - override fun iterateSpoiledBallots(): Iterable // state = Spoiled + override fun iterateDecryptedBallots(): Iterable + override fun iteratePepBallots(pepDir : String): Iterable override fun iteratePlaintextBallots(ballotDir: String, filter : ((PlaintextBallot) -> Boolean)? ): Iterable override fun readTrustee(trusteeDir: String, guardianId: String): DecryptingTrusteeIF + + override fun readEncryptedBallot(ballotDir: String, ballotId: String) : Result } \ No newline at end of file diff --git a/egklib/src/commonMain/kotlin/electionguard/publish/ElectionRecordFactory.kt b/egklib/src/commonMain/kotlin/electionguard/publish/ElectionRecordFactory.kt index ddaeb8a0..caed4fea 100644 --- a/egklib/src/commonMain/kotlin/electionguard/publish/ElectionRecordFactory.kt +++ b/egklib/src/commonMain/kotlin/electionguard/publish/ElectionRecordFactory.kt @@ -9,7 +9,7 @@ import electionguard.core.UInt256 fun readElectionRecord(group : GroupContext, topDir: String) : ElectionRecord { - val consumerIn = makeConsumer(topDir, group) + val consumerIn = makeConsumer(group, topDir) return readElectionRecord(consumerIn) } diff --git a/egklib/src/commonMain/kotlin/electionguard/publish/ElectionRecordJsonPaths.kt b/egklib/src/commonMain/kotlin/electionguard/publish/ElectionRecordJsonPaths.kt index b84da8de..eefd1901 100644 --- a/egklib/src/commonMain/kotlin/electionguard/publish/ElectionRecordJsonPaths.kt +++ b/egklib/src/commonMain/kotlin/electionguard/publish/ElectionRecordJsonPaths.kt @@ -16,6 +16,7 @@ data class ElectionRecordJsonPaths(val topDir : String) { const val ENCRYPTED_BALLOT_PREFIX = "eballot-" const val DECRYPTED_BALLOT_PREFIX = "dballot-" const val PLAINTEXT_BALLOT_PREFIX = "pballot-" + const val PEP_BALLOT_PREFIX = "pepballot-" const val ENCRYPTED_DIR = "encrypted_ballots" const val CHALLENGED_DIR = "challenged_ballots" @@ -52,9 +53,14 @@ data class ElectionRecordJsonPaths(val topDir : String) { return "$ballotDir/$PLAINTEXT_BALLOT_PREFIX$id$JSON_SUFFIX" } - fun encryptedBallotPath(ballotId : String): String { + fun encryptedBallotPath(outputDir : String, ballotId : String): String { val id = ballotId.replace(" ", "_") - return "${encryptedBallotDir()}/$ENCRYPTED_BALLOT_PREFIX$id$JSON_SUFFIX" + return "${outputDir}/$ENCRYPTED_BALLOT_PREFIX$id$JSON_SUFFIX" + } + + fun pepBallotPath(outputDir : String, ballotId : String): String { + val id = ballotId.replace(" ", "_") + return "${outputDir}/$PEP_BALLOT_PREFIX$id$JSON_SUFFIX" } fun decryptedBallotPath(ballotId : String): String { @@ -82,10 +88,10 @@ data class ElectionRecordJsonPaths(val topDir : String) { return "${encryptedBallotDir()}/$useDevice/" } - fun encryptedBallotPath(device: String, ballotId: String): String { + fun encryptedBallotDevicePath(device: String, ballotId: String): String { val useDevice = device.replace(" ", "_") val id = ballotId.replace(" ", "_") - return "${encryptedBallotDir(device)}/${ENCRYPTED_BALLOT_PREFIX}$id${JSON_SUFFIX}" + return "${encryptedBallotDir(useDevice)}/${ENCRYPTED_BALLOT_PREFIX}$id${JSON_SUFFIX}" } fun encryptedBallotChain(device: String): String { diff --git a/egklib/src/commonMain/kotlin/electionguard/publish/Publisher.kt b/egklib/src/commonMain/kotlin/electionguard/publish/Publisher.kt index 08a87251..48d736d3 100644 --- a/egklib/src/commonMain/kotlin/electionguard/publish/Publisher.kt +++ b/egklib/src/commonMain/kotlin/electionguard/publish/Publisher.kt @@ -1,6 +1,7 @@ package electionguard.publish import electionguard.ballot.* +import electionguard.pep.BallotPep import electionguard.keyceremony.KeyCeremonyTrustee /** Write the Election Record as protobuf or json files. */ @@ -17,6 +18,7 @@ interface Publisher { fun writeEncryptedBallotChain(closing: EncryptedBallotChain) fun decryptedTallyOrBallotSink(): DecryptedTallyOrBallotSinkIF + fun pepBallotSink(outputDir: String): PepBallotSinkIF fun writePlaintextBallot(outputDir: String, plaintextBallots: List) fun writeTrustee(trusteeDir: String, trustee: KeyCeremonyTrustee) @@ -30,6 +32,10 @@ interface DecryptedTallyOrBallotSinkIF : Closeable { fun writeDecryptedTallyOrBallot(tally: DecryptedTallyOrBallot) } +interface PepBallotSinkIF : Closeable { + fun writePepBallot(ballotPep: BallotPep) +} + // copied from io.ktor.utils.io.core.Closeable to break package dependency interface Closeable { fun close() diff --git a/egklib/src/commonMain/kotlin/electionguard/publish/PublisherJson.kt b/egklib/src/commonMain/kotlin/electionguard/publish/PublisherJson.kt index 66a97382..b19ce335 100644 --- a/egklib/src/commonMain/kotlin/electionguard/publish/PublisherJson.kt +++ b/egklib/src/commonMain/kotlin/electionguard/publish/PublisherJson.kt @@ -15,7 +15,9 @@ expect class PublisherJson(topDir: String, createNew: Boolean = false) : Publish override fun encryptedBallotSink(device: String, batched: Boolean): EncryptedBallotSinkIF override fun writeEncryptedBallotChain(closing: EncryptedBallotChain) + override fun decryptedTallyOrBallotSink(): DecryptedTallyOrBallotSinkIF + override fun pepBallotSink(outputDir: String): PepBallotSinkIF override fun writePlaintextBallot(outputDir: String, plaintextBallots: List) override fun writeTrustee(trusteeDir: String, trustee: KeyCeremonyTrustee) diff --git a/egklib/src/commonMain/kotlin/electionguard/publish/PublisherProto.kt b/egklib/src/commonMain/kotlin/electionguard/publish/PublisherProto.kt index 817d6d10..5a0bf785 100644 --- a/egklib/src/commonMain/kotlin/electionguard/publish/PublisherProto.kt +++ b/egklib/src/commonMain/kotlin/electionguard/publish/PublisherProto.kt @@ -17,6 +17,7 @@ expect class PublisherProto(topDir: String, createNew: Boolean = false) : Publis override fun writeEncryptedBallotChain(closing: EncryptedBallotChain) override fun decryptedTallyOrBallotSink(): DecryptedTallyOrBallotSinkIF + override fun pepBallotSink(outputDir: String): PepBallotSinkIF override fun writePlaintextBallot(outputDir: String, plaintextBallots: List) override fun writeTrustee(trusteeDir: String, trustee: KeyCeremonyTrustee) diff --git a/egklib/src/commonMain/kotlin/electionguard/publish/RunElectionRecordConvert.kt b/egklib/src/commonMain/kotlin/electionguard/publish/RunElectionRecordConvert.kt index ae5b2d2e..47fe226e 100644 --- a/egklib/src/commonMain/kotlin/electionguard/publish/RunElectionRecordConvert.kt +++ b/egklib/src/commonMain/kotlin/electionguard/publish/RunElectionRecordConvert.kt @@ -34,7 +34,7 @@ fun main(args: Array) { } fun runElectionRecordConvert(group: GroupContext, inputDir: String, outputDir: String, createNew : Boolean) { - val consumer = makeConsumer(inputDir, group) + val consumer = makeConsumer(group, inputDir) val publisher = makePublisher(outputDir, createNew, !consumer.isJson()) val electionRecord = readElectionRecord(consumer) diff --git a/egklib/src/commonMain/kotlin/electionguard/show/RunElectionRecordShow.kt b/egklib/src/commonMain/kotlin/electionguard/show/RunElectionRecordShow.kt index 4d19f19f..3ff0470f 100644 --- a/egklib/src/commonMain/kotlin/electionguard/show/RunElectionRecordShow.kt +++ b/egklib/src/commonMain/kotlin/electionguard/show/RunElectionRecordShow.kt @@ -48,7 +48,7 @@ class ShowSet(val want: Set) { } fun runElectionRecordShow(group: GroupContext, inputDir: String, showSet: ShowSet, details : Boolean) { - val consumer = makeConsumer(inputDir, group) + val consumer = makeConsumer(group, inputDir) val electionRecord = readElectionRecord(consumer) println("RunElectionRecord from $inputDir, stage = ${electionRecord.stage()}\n") diff --git a/egklib/src/commonTest/kotlin/electionguard/cli/RunTrustedPepBatchTest.kt b/egklib/src/commonTest/kotlin/electionguard/cli/RunTrustedPepBatchTest.kt new file mode 100644 index 00000000..e5dec9dd --- /dev/null +++ b/egklib/src/commonTest/kotlin/electionguard/cli/RunTrustedPepBatchTest.kt @@ -0,0 +1,95 @@ +package electionguard.cli + +import kotlin.test.Test + +/** Test Decryption with in-process DecryptingTrustee's. */ +class RunTrustedPepBatchTest { + + @Test + fun testPepAllJson() { + val inputDir = "src/commonTest/data/workflow/allAvailableJson" + val ballotDir = "$inputDir/private_data/input/" + val outputDir = "testOut/pep/testPepAllJson" + val scannedDir = "$outputDir/scanned" + val invalidDir = "$outputDir/invalid" + + // run the extra encryption + RunBatchEncryption.main( + arrayOf( + "-in", inputDir, + "-ballots", ballotDir, + "-out", scannedDir, + "-invalid", invalidDir, + "-device", "scanned", + "--cleanOutput", + ) + ) + + RunTrustedPep.main( + arrayOf( + "-in", inputDir, + "-trustees", "src/commonTest/data/workflow/allAvailableJson/private_data/trustees", + "-out", outputDir, + "-scanned", "$scannedDir/encrypted_ballots/scanned/", + ) + ) + + RunVerifyPep.main( + arrayOf( + "-in", inputDir, + "-pep", outputDir, + ) + ) + } + + @Test + fun testPepSomeJson() { + val inputDir = "src/commonTest/data/workflow/someAvailableJson" + val ballotDir = "$inputDir/private_data/input/" + val outputDir = "testOut/pep/testPepSomeJson" + val scannedDir = "$outputDir/scanned" + val invalidDir = "$outputDir/invalid" + + // run the extra encryption + RunBatchEncryption.main( + arrayOf( + "-in", inputDir, + "-ballots", ballotDir, + "-out", scannedDir, + "-invalid", invalidDir, + "-device", "scanned", + "--cleanOutput", + ) + ) + + RunTrustedPep.main( + arrayOf( + "-in", inputDir, + "-trustees", "src/commonTest/data/workflow/someAvailableJson/private_data/trustees", + "-out", outputDir, + "-scanned", "$scannedDir/encrypted_ballots/scanned/", + ) + ) + + RunVerifyPep.main( + arrayOf( + "-in", inputDir, + "-pep", outputDir, + ) + ) + } + + @Test + fun testVerifyPep() { + val inputDir = "src/commonTest/data/workflow/allAvailableJson" + val outputDir = "testOut/pep/testPepAllJson" + + RunVerifyPep.main( + arrayOf( + "-in", inputDir, + "-pep", "$outputDir", + ) + ) + } + +} diff --git a/egklib/src/commonTest/kotlin/electionguard/decryptBallot/RunDecryptBallotsJsonTest.kt b/egklib/src/commonTest/kotlin/electionguard/decryptBallot/RunDecryptBallotsJsonTest.kt index 0aae33fa..cad38e1d 100644 --- a/egklib/src/commonTest/kotlin/electionguard/decryptBallot/RunDecryptBallotsJsonTest.kt +++ b/egklib/src/commonTest/kotlin/electionguard/decryptBallot/RunDecryptBallotsJsonTest.kt @@ -77,7 +77,7 @@ class RunDecryptBallotsJsonTest { val group = productionGroup() val inputDir = "src/commonTest/data/workflow/someAvailableJson" val ballotDir = "$inputDir/private_data/input/" - val consumerIn = makeConsumer(inputDir, group) + val consumerIn = makeConsumer(group, inputDir) consumerIn.iteratePlaintextBallots(ballotDir, null).forEach { println(it.ballotId) diff --git a/egklib/src/commonTest/kotlin/electionguard/decryptBallot/RunDecryptBallotsTest.kt b/egklib/src/commonTest/kotlin/electionguard/decryptBallot/RunDecryptBallotsTest.kt index b4a3092c..97927e19 100644 --- a/egklib/src/commonTest/kotlin/electionguard/decryptBallot/RunDecryptBallotsTest.kt +++ b/egklib/src/commonTest/kotlin/electionguard/decryptBallot/RunDecryptBallotsTest.kt @@ -113,7 +113,7 @@ class RunDecryptBallotsTest { val group = productionGroup() val inputDir = "src/commonTest/data/workflow/someAvailableProto" val ballotDir = "$inputDir/private_data/input/" - val consumerIn = makeConsumer(inputDir, group) + val consumerIn = makeConsumer(group, inputDir) consumerIn.iteratePlaintextBallots(ballotDir, null).forEach { println(it.ballotId) diff --git a/egklib/src/commonTest/kotlin/electionguard/encrypt/AddEncryptedBallotTest.kt b/egklib/src/commonTest/kotlin/electionguard/encrypt/AddEncryptedBallotTest.kt index 50d570db..294ec89a 100644 --- a/egklib/src/commonTest/kotlin/electionguard/encrypt/AddEncryptedBallotTest.kt +++ b/egklib/src/commonTest/kotlin/electionguard/encrypt/AddEncryptedBallotTest.kt @@ -291,7 +291,7 @@ class AddEncryptedBallotTest { } fun checkOutput(group : GroupContext, outputDir: String, expectedCount: Int, chained : Boolean) { - val consumer = makeConsumer(outputDir, group, false) + val consumer = makeConsumer(group, outputDir, false) var count = 0 consumer.iterateAllEncryptedBallots { true }.forEach { count++ diff --git a/egklib/src/commonTest/kotlin/electionguard/encrypt/RunBatchEncryptionTest.kt b/egklib/src/commonTest/kotlin/electionguard/encrypt/RunBatchEncryptionTest.kt index cd67c81b..ba16bdbe 100644 --- a/egklib/src/commonTest/kotlin/electionguard/encrypt/RunBatchEncryptionTest.kt +++ b/egklib/src/commonTest/kotlin/electionguard/encrypt/RunBatchEncryptionTest.kt @@ -148,7 +148,7 @@ RunBatchEncryptionTest { "testInvalidBallot", ) - val consumerOut = makeConsumer(invalidDir, group) + val consumerOut = makeConsumer(group, invalidDir) consumerOut.iteratePlaintextBallots(invalidDir, null).forEach { println("${it.errors}") assertContains(it.errors.toString(), "Ballot.A.1 Ballot Style 'badStyleId' does not exist in election") diff --git a/egklib/src/commonTest/kotlin/electionguard/publish/ConsumerTest.kt b/egklib/src/commonTest/kotlin/electionguard/publish/ConsumerTest.kt index 85c7591c..6faa1e82 100644 --- a/egklib/src/commonTest/kotlin/electionguard/publish/ConsumerTest.kt +++ b/egklib/src/commonTest/kotlin/electionguard/publish/ConsumerTest.kt @@ -29,8 +29,8 @@ class ConsumerTest { @Test fun readSpoiledBallotTallys() { runTest { - val context = productionGroup() - val consumerIn = makeConsumer(topdir, context) + val group = productionGroup() + val consumerIn = makeConsumer(group, topdir) var count = 0 for (tally in consumerIn.iterateDecryptedBallots()) { println("$count tally = ${tally.id}") @@ -42,8 +42,8 @@ class ConsumerTest { @Test fun readEncryptedBallots() { runTest { - val context = productionGroup() - val consumerIn = makeConsumer(topdir, context) + val group = productionGroup() + val consumerIn = makeConsumer(group, topdir) var count = 0 for (ballot in consumerIn.iterateAllEncryptedBallots { true} ) { println("$count ballot = ${ballot.ballotId}") @@ -55,8 +55,8 @@ class ConsumerTest { @Test fun readEncryptedBallotsCast() { runTest { - val context = productionGroup() - val consumerIn = makeConsumer(topdir, context) + val group = productionGroup() + val consumerIn = makeConsumer(group, topdir) var count = 0 for (ballot in consumerIn.iterateAllCastBallots()) { println("$count ballot = ${ballot.ballotId}") @@ -68,8 +68,8 @@ class ConsumerTest { @Test fun readSubmittedBallotsSpoiled() { runTest { - val context = productionGroup() - val consumerIn = makeConsumer(topdir, context) + val group = productionGroup() + val consumerIn = makeConsumer(group, topdir) var count = 0 for (ballot in consumerIn.iterateAllSpoiledBallots()) { println("$count ballot = ${ballot.ballotId}") @@ -81,8 +81,8 @@ class ConsumerTest { @Test fun readTrustee() { runTest { - val context = productionGroup() - val consumerIn = makeConsumer(topdir, context) + val group = productionGroup() + val consumerIn = makeConsumer(group, topdir) val init = consumerIn.readElectionInitialized().getOrThrow { IllegalStateException(it) } val trusteeDir = "$topdir/private_data/trustees" init.guardians.forEach { @@ -95,9 +95,9 @@ class ConsumerTest { @Test fun readBadTrustee() { runTest { - val context = productionGroup() val trusteeDir = "$topdir/private_data/trustees" - val consumerIn = makeConsumer(trusteeDir, context) + val group = productionGroup() + val consumerIn = makeConsumer(group, topdir) val result: Result = runCatching { consumerIn.readTrustee(trusteeDir, "badId") } @@ -110,10 +110,10 @@ class ConsumerTest { @Test fun readMissingTrustees() { runTest { - val context = productionGroup() val trusteeDir = "src/commonTest/data/testBad/nonexistant" + val group = productionGroup() + val consumerIn = makeConsumer(group, topdir) val result: Result = runCatching { - val consumerIn = makeConsumer(trusteeDir, context) consumerIn.readTrustee(trusteeDir, "randomName") } assertFalse(result is Ok) diff --git a/egklib/src/commonTest/kotlin/electionguard/publish/ElectionRecordIterablesTest.kt b/egklib/src/commonTest/kotlin/electionguard/publish/ElectionRecordIterablesTest.kt index 2b34b539..96bdd26b 100644 --- a/egklib/src/commonTest/kotlin/electionguard/publish/ElectionRecordIterablesTest.kt +++ b/egklib/src/commonTest/kotlin/electionguard/publish/ElectionRecordIterablesTest.kt @@ -31,7 +31,7 @@ class ElectionRecordIterablesTest { } fun readBallots(context: GroupContext, topdir: String, expected: Int) { - val consumerIn = makeConsumer(topdir, context) + val consumerIn = makeConsumer(context, topdir) val iterator = consumerIn.iterateAllEncryptedBallots { true } .iterator() var count = 0 for (ballot in iterator) { @@ -42,7 +42,7 @@ class ElectionRecordIterablesTest { } fun readCastBallots(context: GroupContext, topdir: String, expected: Int) { - val consumerIn = makeConsumer(topdir, context) + val consumerIn = makeConsumer(context, topdir) val iterator = consumerIn.iterateAllCastBallots().iterator() var count = 0 for (ballot in iterator) { @@ -53,7 +53,7 @@ class ElectionRecordIterablesTest { } fun readSpoiledBallots(context: GroupContext, topdir: String, expected: Int) { - val consumerIn = makeConsumer(topdir, context) + val consumerIn = makeConsumer(context, topdir) val iterator = consumerIn.iterateAllSpoiledBallots().iterator() var count = 0 for (ballot in iterator) { @@ -64,7 +64,7 @@ class ElectionRecordIterablesTest { } fun readDecryptedBallots(context: GroupContext, topdir: String, expected: Int) { - val consumerIn = makeConsumer(topdir, context) + val consumerIn = makeConsumer(context, topdir) val iterator = consumerIn.iterateDecryptedBallots().iterator() var count = 0 for (tally in iterator) { diff --git a/egklib/src/commonTest/kotlin/electionguard/publish/ElectionRecordTest.kt b/egklib/src/commonTest/kotlin/electionguard/publish/ElectionRecordTest.kt index 6b676cb2..31c841c9 100644 --- a/egklib/src/commonTest/kotlin/electionguard/publish/ElectionRecordTest.kt +++ b/egklib/src/commonTest/kotlin/electionguard/publish/ElectionRecordTest.kt @@ -43,7 +43,7 @@ class ElectionRecordTest { fun readElectionRecordAndValidate(topdir: String) { val group = productionGroup() - val consumerIn = makeConsumer(topdir, group) + val consumerIn = makeConsumer(group, topdir) assertNotNull(consumerIn) val decryption = consumerIn.readDecryptionResult().getOrThrow { IllegalStateException(it) } readDecryption(decryption) diff --git a/egklib/src/commonTest/kotlin/electionguard/publish/PublisherJsonTest.kt b/egklib/src/commonTest/kotlin/electionguard/publish/PublisherJsonTest.kt index e32c5b7c..7a6e96c4 100644 --- a/egklib/src/commonTest/kotlin/electionguard/publish/PublisherJsonTest.kt +++ b/egklib/src/commonTest/kotlin/electionguard/publish/PublisherJsonTest.kt @@ -18,13 +18,13 @@ class PublisherJsonTest { private val output = "testOut/publish/PublisherJsonTest" val group = productionGroup() - val consumerIn = makeConsumer(input, group) + val consumerIn = makeConsumer(group, input) @Test fun testRoundtripElectionConfig() { val output1 = output + "1" val publisher = makePublisher(output1, true, true) - val consumerOut = makeConsumer(output1, group, true) + val consumerOut = makeConsumer(group, output1, true) val (manifest, config) = generateElectionConfig(publisher, 3, 3) @@ -56,7 +56,7 @@ class PublisherJsonTest { fun testRoundtripElectionInit() { val output2 = output + "2" val publisher = makePublisher(output2, true, true) - val consumerOut = makeConsumer(output2, group, true) + val consumerOut = makeConsumer(group, output2, true) val (_, config) = generateElectionConfig(publisher, 6, 4) publisher.writeElectionConfig(config) @@ -86,7 +86,7 @@ class PublisherJsonTest { fun testWriteEncryptions() { val output3 = output + "3" val publisher = makePublisher(output3, true, true) - val consumerOut = makeConsumer(output3, group, true) + val consumerOut = makeConsumer(group, output3, true) val initResult = consumerIn.readElectionInitialized() assertTrue(initResult is Ok) @@ -118,7 +118,7 @@ class PublisherJsonTest { fun testWriteSpoiledBallots() { val output4 = output + "4" val publisher = makePublisher(output4, true, true) - val consumerOut = makeConsumer(output4, group, true) + val consumerOut = makeConsumer(group, output4, true) val dsink = publisher.decryptedTallyOrBallotSink() try { @@ -136,7 +136,7 @@ class PublisherJsonTest { fun testWriteTallyResults() { val output5 = output + "5" val publisher = makePublisher(output5, true, true) - val consumerOut = makeConsumer(output5, group, true) + val consumerOut = makeConsumer(group, output5, true) val tallyResult = consumerIn.readTallyResult() if (tallyResult is Err) { @@ -160,7 +160,7 @@ class PublisherJsonTest { fun testWriteDecryptionResults() { val output6 = output + "6" val publisher = makePublisher(output6, true, true) - val consumerOut = makeConsumer(output6, group, true) + val consumerOut = makeConsumer(group, output6, true) val tallyResult = consumerIn.readDecryptionResult() if (tallyResult is Err) { diff --git a/egklib/src/commonTest/kotlin/electionguard/publish/PublisherProtoTest.kt b/egklib/src/commonTest/kotlin/electionguard/publish/PublisherProtoTest.kt index 0f9aca97..9dc1e3e8 100644 --- a/egklib/src/commonTest/kotlin/electionguard/publish/PublisherProtoTest.kt +++ b/egklib/src/commonTest/kotlin/electionguard/publish/PublisherProtoTest.kt @@ -16,7 +16,7 @@ class PublisherProtoTest { private val output = "testOut/publish/PublisherProtoTest" val group = productionGroup() - val consumerIn = makeConsumer(input, group) + val consumerIn = makeConsumer(group, input) @Test fun testRoundtripElectionConfig() { @@ -33,7 +33,7 @@ class PublisherProtoTest { } publisher.writeElectionConfig(config) - val consumerOut = makeConsumer("$output/1", group) + val consumerOut = makeConsumer(group, "$output/1") val roundtripResult = consumerOut.readElectionConfig() assertNotNull(roundtripResult) @@ -66,7 +66,7 @@ class PublisherProtoTest { ) publisher.writeElectionInitialized(init) - val consumerOut = makeConsumer("$output/2", group) + val consumerOut = makeConsumer(group, "$output/2") val roundtripResult = consumerOut.readElectionInitialized() assertNotNull(roundtripResult) if (roundtripResult is Err) { @@ -91,7 +91,7 @@ class PublisherProtoTest { consumerIn.iterateAllEncryptedBallots { true }.forEach{ sink.writeEncryptedBallot(it) } sink.close() - val consumerOut = makeConsumer("$output/3", group) + val consumerOut = makeConsumer(group, "$output/3") val rtResult = consumerOut.readElectionInitialized() if (rtResult is Err) { println("testWriteEncryptions = $rtResult") @@ -119,7 +119,7 @@ class PublisherProtoTest { } val inBallots = consumerIn.iterateDecryptedBallots().associateBy { it.id } - val consumerOut = makeConsumer("$output/4", group) + val consumerOut = makeConsumer(group, "$output/4") consumerOut.iterateDecryptedBallots().forEach { val inBallot = inBallots[it.id] ?: throw RuntimeException("Cant find ${it.id}") diff --git a/egklib/src/jvmMain/kotlin/electionguard/cli/RunAccumulateTally.kt b/egklib/src/jvmMain/kotlin/electionguard/cli/RunAccumulateTally.kt index c28458de..646af0cd 100644 --- a/egklib/src/jvmMain/kotlin/electionguard/cli/RunAccumulateTally.kt +++ b/egklib/src/jvmMain/kotlin/electionguard/cli/RunAccumulateTally.kt @@ -67,7 +67,7 @@ class RunAccumulateTally { ) { val starting = getSystemTimeInMillis() - val consumerIn = makeConsumer(inputDir, group) + val consumerIn = makeConsumer(group, inputDir) val electionRecord = readElectionRecord(consumerIn) val electionInit = electionRecord.electionInit()!! diff --git a/egklib/src/jvmMain/kotlin/electionguard/cli/RunExampleEncryption.kt b/egklib/src/jvmMain/kotlin/electionguard/cli/RunExampleEncryption.kt index 6cdb4b06..7908085f 100644 --- a/egklib/src/jvmMain/kotlin/electionguard/cli/RunExampleEncryption.kt +++ b/egklib/src/jvmMain/kotlin/electionguard/cli/RunExampleEncryption.kt @@ -72,7 +72,7 @@ class RunExampleEncryption { } fun verifyOutput(group: GroupContext, outputDir: String, expectedCount : Int, chained: Boolean = false) { - val consumer = makeConsumer(outputDir, group, false) + val consumer = makeConsumer(group, outputDir, false) var count = 0 consumer.iterateAllEncryptedBallots { true }.forEach { count++ diff --git a/egklib/src/jvmMain/kotlin/electionguard/cli/RunTrustedBallotDecryption.kt b/egklib/src/jvmMain/kotlin/electionguard/cli/RunTrustedBallotDecryption.kt index 381f4980..d17d6e5a 100644 --- a/egklib/src/jvmMain/kotlin/electionguard/cli/RunTrustedBallotDecryption.kt +++ b/egklib/src/jvmMain/kotlin/electionguard/cli/RunTrustedBallotDecryption.kt @@ -98,7 +98,7 @@ class RunTrustedBallotDecryption { println(" runDecryptBallots on ballots in ${inputDir} with nthreads = $nthreads") val starting = getSystemTimeInMillis() // wall clock - val consumerIn = makeConsumer(inputDir, group) + val consumerIn = makeConsumer(group, inputDir) val tallyResult: TallyResult = consumerIn.readTallyResult().getOrThrow { IllegalStateException(it) } val guardians = Guardians(group, tallyResult.electionInitialized.guardians) diff --git a/egklib/src/jvmMain/kotlin/electionguard/cli/RunTrustedPep.kt b/egklib/src/jvmMain/kotlin/electionguard/cli/RunTrustedPep.kt new file mode 100644 index 00000000..d348a32b --- /dev/null +++ b/egklib/src/jvmMain/kotlin/electionguard/cli/RunTrustedPep.kt @@ -0,0 +1,190 @@ +package electionguard.cli + +import com.github.michaelbull.result.Result +import com.github.michaelbull.result.unwrap + +import electionguard.ballot.EncryptedBallot +import electionguard.core.* +import electionguard.decrypt.DecryptingTrusteeIF +import electionguard.decrypt.Guardians +import electionguard.pep.BallotPep +import electionguard.pep.PepAlgorithm +import electionguard.pep.PepBlindTrust +import electionguard.pep.PepTrustee +import electionguard.publish.* +import kotlinx.cli.ArgParser +import kotlinx.cli.ArgType +import kotlinx.cli.required +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.channels.SendChannel +import kotlinx.coroutines.channels.produce +import mu.KotlinLogging + +private val logger = KotlinLogging.logger("RunTrustedPep") +private val debug = false + +/** + * Compare encrypted ballots with local trustees CLI. + * Read election record from inputDir, write to outputDir. + * This has access to all the trustees, so is only used for testing, or in a use case of trust. + * A version of this where each Trustee is in its own process space is implemented in the webapps modules. + */ +class RunTrustedPep { + + companion object { + @JvmStatic + fun main(args: Array) { + val parser = ArgParser("RunTrustedPep") + val inputDir by parser.option( + ArgType.String, + shortName = "in", + description = "Directory containing input election record" + ).required() + val scannedBallotDir by parser.option( + ArgType.String, + shortName = "scanned", + description = "Directory containing scanned ballots" + ).required() + val trusteeDir by parser.option( + ArgType.String, + shortName = "trustees", + description = "Directory to read private trustees" + ).required() + val outputDir by parser.option( + ArgType.String, + shortName = "out", + description = "Directory to write output election record" + ).required() + val missing by parser.option( + ArgType.String, + shortName = "missing", + description = "missing guardians' xcoord, comma separated, eg '2,4'" + ) + val nthreads by parser.option( + ArgType.Int, + shortName = "nthreads", + description = "Number of parallel threads to use" + ) + parser.parse(args) + println( + "RunTrustedPep starting\n input= $inputDir\n scannedBallotDir= $scannedBallotDir\n trustees= $trusteeDir\n" + + " output = $outputDir\n nthreads = $nthreads\n" + ) + + val group = productionGroup() + runTrustedPep( + group, inputDir, scannedBallotDir, outputDir, + RunTrustedTallyDecryption.readDecryptingTrustees(group, inputDir, trusteeDir, missing), + nthreads ?: 11 + ) + } + + fun runTrustedPep( + group: GroupContext, + inputDir: String, + scannedBallotDir: String, + outputDir: String, + decryptingTrustees: List, + nthreads: Int, + ): Int { + println(" runTrustedPep on ballots in ${inputDir} compare to Ballots in $scannedBallotDir") + val starting = getSystemTimeInMillis() // wall clock + + val consumer = makeConsumer(group, inputDir) + val electionRecord = readElectionRecord(consumer) + val electionInitialized = electionRecord.electionInit()!! + val guardians = Guardians(group, electionInitialized.guardians) + + val btrustees = mutableListOf() + repeat(3) { + btrustees.add(PepTrustee(it, group)) + } + val pep = PepBlindTrust( + group, + electionInitialized.extendedBaseHash, + ElGamalPublicKey(electionInitialized.jointPublicKey), + guardians, // all guardians + btrustees, + decryptingTrustees, + ) + + val publisher = makePublisher(outputDir, false, true) // always json for now + val sink: DecryptedTallyOrBallotSinkIF = publisher.decryptedTallyOrBallotSink() + + try { + runBlocking { + val outputChannel = Channel>() + val pepJobs = mutableListOf() + val ballotProducer = produceBallots(consumer, scannedBallotDir) + repeat(nthreads) { + pepJobs.add( + launchPepWorker( + it, + ballotProducer, + pep, + outputChannel + ) + ) + } + launchSink(outputChannel, publisher.pepBallotSink(outputDir)) + + // wait for all decryptions to be done, then close everything + joinAll(*pepJobs.toTypedArray()) + outputChannel.close() + } + } finally { + sink.close() + } + + pep.stats.show(5) + val count = pep.stats.count() + + val took = getSystemTimeInMillis() - starting + val msecsPerBallot = (took.toDouble() / 1000 / count).sigfig() + println(" Pep compare ballots took ${took / 1000} wallclock secs for $count ballots = $msecsPerBallot secs/ballot") + + return count + } + + // parallelize over ballots + // place the ballot reading into its own coroutine + @OptIn(ExperimentalCoroutinesApi::class) + private fun CoroutineScope.produceBallots(consumer: Consumer, scannedBallotDir: String): ReceiveChannel> = + produce { + for (ballot in consumer.iterateAllCastBallots()) { + logger.debug { "Producer sending ballot ${ballot.ballotId}" } + val scannedBallot = consumer.readEncryptedBallot(scannedBallotDir,ballot.ballotId ) + send(Pair(ballot, scannedBallot.unwrap())) + yield() + } + channel.close() + } + + private fun CoroutineScope.launchPepWorker( + id: Int, + input: ReceiveChannel>, + pep: PepAlgorithm, + output: SendChannel>, + ) = launch(Dispatchers.Default) { + for (ballotPair in input) { + val decrypted: Result = pep.doEgkPep(ballotPair.first, ballotPair.second) + logger.debug { " coroutine #$id pep compare ${ballotPair.first.ballotId}" } + if (debug) println(" coroutine #$id pep compare ${ballotPair.first.ballotId}") + output.send(decrypted) + yield() + } + logger.debug { "Decryptor #$id done" } + } + + // place the output writing into its own coroutine + private fun CoroutineScope.launchSink( + input: Channel>, sink: PepBallotSinkIF, + ) = launch { + for (result in input) { + sink.writePepBallot(result.unwrap()) + } + } + } +} \ No newline at end of file diff --git a/egklib/src/jvmMain/kotlin/electionguard/cli/RunTrustedTallyDecryption.kt b/egklib/src/jvmMain/kotlin/electionguard/cli/RunTrustedTallyDecryption.kt index 5db21984..0fa1e807 100644 --- a/egklib/src/jvmMain/kotlin/electionguard/cli/RunTrustedTallyDecryption.kt +++ b/egklib/src/jvmMain/kotlin/electionguard/cli/RunTrustedTallyDecryption.kt @@ -74,7 +74,7 @@ class RunTrustedTallyDecryption { trusteeDir: String, missing: String? = null ): List { - val consumerIn = makeConsumer(inputDir, group) + val consumerIn = makeConsumer(group, inputDir) val init = consumerIn.readElectionInitialized().getOrThrow { IllegalStateException(it) } val trusteeSource = makeTrusteeSource(trusteeDir, group, consumerIn.isJson()) val allGuardians = init.guardians.map { trusteeSource.readTrustee(trusteeDir, it.guardianId) } diff --git a/egklib/src/jvmMain/kotlin/electionguard/cli/RunVerifyPep.kt b/egklib/src/jvmMain/kotlin/electionguard/cli/RunVerifyPep.kt new file mode 100644 index 00000000..11134b1a --- /dev/null +++ b/egklib/src/jvmMain/kotlin/electionguard/cli/RunVerifyPep.kt @@ -0,0 +1,71 @@ +package electionguard.cli + +import electionguard.core.* +import electionguard.pep.VerifierPep +import electionguard.publish.makeConsumer +import electionguard.publish.readElectionRecord +import kotlinx.cli.ArgParser +import kotlinx.cli.ArgType +import kotlinx.cli.required + +import com.github.michaelbull.result.Err +import com.github.michaelbull.result.Ok +import com.github.michaelbull.result.Result + +/** + * Run election record verification CLI. + */ +class RunVerifyPep { + + companion object { + @JvmStatic + fun main(args: Array) { + val parser = ArgParser("RunVerifyPep") + val inputDir by parser.option( + ArgType.String, + shortName = "in", + description = "Directory containing input election record" + ).required() + val pepDirectory by parser.option( + ArgType.String, + shortName = "pep", + description = "Directory containing PEP output" + ).required() + val nthreads by parser.option( + ArgType.Int, + shortName = "nthreads", + description = "Number of parallel threads to use" + ) + parser.parse(args) + println("RunVerifyPep starting\n input= $inputDir") + + runVerifyPep(productionGroup(), inputDir, pepDirectory, nthreads ?: 11) + } + + fun runVerifyPep(group: GroupContext, inputDir: String, pepDirectory : String, nthreads: Int): Boolean { + val starting = getSystemTimeInMillis() + + val consumer = makeConsumer(group, inputDir) + val record = readElectionRecord(consumer) + val verifier = VerifierPep(group, record.extendedBaseHash()!!, ElGamalPublicKey(record.jointPublicKey()!!)) + + var count = 0 + var allOk = true + consumer.iteratePepBallots(pepDirectory).forEach { ballotPEP -> + // fun verify(ballotPEP: BallotPep): Result { + val result = verifier.verify(ballotPEP) + if (result is Err) { + println(result) + allOk = false + } else { + println(" ${ballotPEP.ballotId} validates") + } + count++ + } + + val tookAll = (getSystemTimeInMillis() - starting) + println("RunVerifier = $allOk took $tookAll msecs for $count ballots; allOk = ${allOk}") + return allOk + } + } +} diff --git a/egklib/src/jvmMain/kotlin/electionguard/publish/ConsumerJson.kt b/egklib/src/jvmMain/kotlin/electionguard/publish/ConsumerJson.kt index f94bf2c1..1a1caeaf 100644 --- a/egklib/src/jvmMain/kotlin/electionguard/publish/ConsumerJson.kt +++ b/egklib/src/jvmMain/kotlin/electionguard/publish/ConsumerJson.kt @@ -8,6 +8,9 @@ import electionguard.core.GroupContext import electionguard.decrypt.DecryptingTrusteeDoerre import electionguard.decrypt.DecryptingTrusteeIF import electionguard.json2.* +import electionguard.pep.BallotPep +import electionguard.pep.BallotPepJson +import electionguard.pep.import import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream @@ -18,6 +21,7 @@ import java.nio.file.Files import java.nio.file.Path import java.util.function.Predicate import java.util.stream.Stream +import kotlin.io.path.isDirectory private val logger = KotlinLogging.logger("ConsumerJsonJvm") @@ -141,14 +145,11 @@ actual class ConsumerJson actual constructor(val topDir: String, val group: Grou override fun computeNext() { while (true) { if (ballotIds.hasNext()) { - val ballotFilePath = Path.of(jsonPaths.encryptedBallotPath(device, ballotIds.next())) - fileSystemProvider.newInputStream(ballotFilePath).use { inp -> - val json = Json.decodeFromStream(inp) - val encryptedBallot = json.import(group) - if (filter == null || filter.test(encryptedBallot)) { - setNext(encryptedBallot) - return - } + val ballotFilePath = Path.of(jsonPaths.encryptedBallotDevicePath(device, ballotIds.next())) + val encryptedBallot = readEncryptedBallot(ballotFilePath) + if (filter == null || filter.test(encryptedBallot)) { + setNext(encryptedBallot) + return } } else { return done() @@ -157,6 +158,13 @@ actual class ConsumerJson actual constructor(val topDir: String, val group: Grou } } + fun readEncryptedBallot(ballotFilePath : Path): EncryptedBallot{ + fileSystemProvider.newInputStream(ballotFilePath).use { inp -> + val json = Json.decodeFromStream(inp) + return json.import(group) + } + } + actual override fun iterateAllEncryptedBallots(filter : ((EncryptedBallot) -> Boolean)? ): Iterable { val devices = encryptingDevices() return Iterable { DeviceIterator(devices.iterator(), filter) } @@ -211,25 +219,6 @@ actual class ConsumerJson actual constructor(val topDir: String, val group: Grou return Files.exists(fileSystem.getPath(jsonPaths.encryptedBallotDir())) } - // all submitted ballots, with filter - actual override fun iterateEncryptedBallots(filter: ((EncryptedBallot) -> Boolean)?): Iterable { - val dirPath = fileSystem.getPath(jsonPaths.encryptedBallotDir()) - if (!Files.exists(dirPath)) { - return emptyList() - } - return Iterable { EncryptedBallotFileIterator(dirPath, group, filter) } - } - - // only EncryptedBallot that are CAST - actual override fun iterateCastBallots(): Iterable { - return iterateEncryptedBallots { it.state == EncryptedBallot.BallotState.CAST } - } - - // only EncryptedBallot that are SPOILED - actual override fun iterateSpoiledBallots(): Iterable { - return iterateEncryptedBallots { it.state == EncryptedBallot.BallotState.SPOILED } - } - // decrypted spoiled ballots actual override fun iterateDecryptedBallots(): Iterable { val dirPath = fileSystem.getPath(jsonPaths.decryptedBallotDir()) @@ -258,6 +247,33 @@ actual class ConsumerJson actual constructor(val topDir: String, val group: Grou return if (result is Ok) result.unwrap() else throw Exception(result.getError()) } + actual override fun readEncryptedBallot(ballotDir: String, ballotId: String) : Result { + val ballotFilePath = Path.of(jsonPaths.encryptedBallotPath(ballotDir, ballotId)) + return Ok(readEncryptedBallot(ballotFilePath)) + } + + actual override fun iteratePepBallots(pepDir : String): Iterable { + return Iterable { PepBallotIterator(group, Path.of(pepDir)) } + } + + private inner class PepBallotIterator(val group: GroupContext, ballotDir: Path) : AbstractIterator() { + val pathList = ballotDir.pathListNoDirs() + var idx = 0 + + override fun computeNext() { + while (idx < pathList.size) { + val file = pathList[idx++] + fileSystemProvider.newInputStream(file).use { inp -> + val json = jsonIgnoreNulls.decodeFromStream(inp) + val pepBallot = json.import(group) + return setNext(pepBallot) + } + } + return done() + } + } + + //////// The low level reading functions private fun readElectionConfig(constantsFile: Path, manifestFile: Path, configFile: Path,): Result { @@ -412,4 +428,12 @@ fun Path.pathList(): List { return Files.walk(this, 1).use { fileStream -> fileStream.filter { it != this }.toList() } +} + +fun Path.pathListNoDirs(): List { + // LOOK does this sort? + // LOOK "API Note: This method must be used within a try-with-resources statement" + return Files.walk(this, 1).use { fileStream -> + fileStream.filter { it != this && !it.isDirectory() }.toList() + } } \ No newline at end of file diff --git a/egklib/src/jvmMain/kotlin/electionguard/publish/ConsumerProto.kt b/egklib/src/jvmMain/kotlin/electionguard/publish/ConsumerProto.kt index 95ddfa57..ccbc1079 100644 --- a/egklib/src/jvmMain/kotlin/electionguard/publish/ConsumerProto.kt +++ b/egklib/src/jvmMain/kotlin/electionguard/publish/ConsumerProto.kt @@ -11,6 +11,7 @@ import electionguard.core.GroupContext import electionguard.core.fileReadBytes import electionguard.decrypt.DecryptingTrusteeDoerre import electionguard.decrypt.DecryptingTrusteeIF +import electionguard.pep.BallotPep import electionguard.protoconvert.import import mu.KotlinLogging import pbandk.decodeFromByteBuffer @@ -198,7 +199,11 @@ actual class ConsumerProto actual constructor(val topDir: String, val groupConte ////////////////////////////////////////////////////////////////////////////////////////////////// - // all submitted ballots, cast or spoiled + actual override fun hasEncryptedBallots(): Boolean { + return Files.exists(Path.of(protoPaths.encryptedBallotPath())) + } + + /* all submitted ballots, cast or spoiled actual override fun iterateEncryptedBallots(filter: ((EncryptedBallot) -> Boolean)?): Iterable { val filename = protoPaths.encryptedBallotPath() if (!Files.exists(Path.of(filename))) { @@ -207,10 +212,6 @@ actual class ConsumerProto actual constructor(val topDir: String, val groupConte return Iterable { EncryptedBallotIterator(filename, groupContext, null, filter) } } - actual override fun hasEncryptedBallots(): Boolean { - return Files.exists(Path.of(protoPaths.encryptedBallotPath())) - } - // only EncryptedBallot that are CAST actual override fun iterateCastBallots(): Iterable { val filename = protoPaths.encryptedBallotPath() @@ -233,6 +234,8 @@ actual class ConsumerProto actual constructor(val topDir: String, val groupConte return Iterable { EncryptedBallotIterator(filename, groupContext, protoFilter, null) } } + */ + // all tallies in the SPOILED_BALLOT_FILE file actual override fun iterateDecryptedBallots(): Iterable { val filename = protoPaths.spoiledBallotPath() @@ -259,6 +262,10 @@ actual class ConsumerProto actual constructor(val topDir: String, val groupConte return groupContext.readTrustee(filename) } + actual override fun readEncryptedBallot(ballotDir: String, ballotId: String) : Result = + Err("Not implemented yet") + + //////// The low level reading functions for protobuf private fun makeManifestResult(manifestBytes: ByteArray): Result { @@ -404,6 +411,10 @@ actual class ConsumerProto actual constructor(val topDir: String, val groupConte return proto.import(this).getOrElse { throw RuntimeException("DecryptingTrustee $filename failed to parse") } } + actual override fun iteratePepBallots(pepDir : String): Iterable { + return emptyList() + } + } // variable length (base 128) int32 diff --git a/egklib/src/jvmMain/kotlin/electionguard/publish/PublisherJson.kt b/egklib/src/jvmMain/kotlin/electionguard/publish/PublisherJson.kt index e78384db..58fb3d64 100644 --- a/egklib/src/jvmMain/kotlin/electionguard/publish/PublisherJson.kt +++ b/egklib/src/jvmMain/kotlin/electionguard/publish/PublisherJson.kt @@ -3,6 +3,8 @@ package electionguard.publish import electionguard.ballot.* import electionguard.json2.publishJson import electionguard.keyceremony.KeyCeremonyTrustee +import electionguard.pep.BallotPep +import electionguard.pep.publishJson import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import kotlinx.serialization.json.encodeToStream @@ -125,7 +127,7 @@ actual class PublisherJson actual constructor(topDir: String, createNew: Boolean inner class EncryptedBallotDeviceSink(val device: String) : EncryptedBallotSinkIF { override fun writeEncryptedBallot(ballot: EncryptedBallot) { - val ballotFile = jsonPaths.encryptedBallotPath(device, ballot.ballotId) + val ballotFile = jsonPaths.encryptedBallotDevicePath(device, ballot.ballotId) val json = ballot.publishJson() FileOutputStream(ballotFile).use { out -> jsonFormat.encodeToStream(json, out) @@ -154,4 +156,21 @@ actual class PublisherJson actual constructor(topDir: String, createNew: Boolean override fun close() { } } + + /////////////////////////////////////////////////////////////////////// + actual override fun pepBallotSink(outputDir: String): PepBallotSinkIF = PepBallotSink(outputDir) + + inner class PepBallotSink(val outputDir: String) : PepBallotSinkIF { + override fun writePepBallot(pepBallot: BallotPep) { + val pepJson = pepBallot.publishJson() + FileOutputStream(jsonPaths.pepBallotPath(outputDir, pepBallot.ballotId)).use { out -> + jsonFormat.encodeToStream(pepJson, out) + out.close() + } + } + override fun close() { + } + } + + } \ No newline at end of file diff --git a/egklib/src/jvmMain/kotlin/electionguard/publish/PublisherProto.kt b/egklib/src/jvmMain/kotlin/electionguard/publish/PublisherProto.kt index 2b6a7fa8..f61d9872 100644 --- a/egklib/src/jvmMain/kotlin/electionguard/publish/PublisherProto.kt +++ b/egklib/src/jvmMain/kotlin/electionguard/publish/PublisherProto.kt @@ -2,6 +2,7 @@ package electionguard.publish import electionguard.ballot.* import electionguard.keyceremony.KeyCeremonyTrustee +import electionguard.pep.BallotPep import electionguard.protoconvert.publishDecryptingTrusteeProto import electionguard.protoconvert.publishProto import electionguard.publish.ElectionRecordProtoPaths.Companion.DECRYPTION_RESULT_FILE @@ -39,7 +40,7 @@ actual class PublisherProto actual constructor(topDir: String, createNew: Boolea validateOutputDir(electionRecordDir, Formatter()) } - actual override fun isJson() : Boolean = false + actual override fun isJson(): Boolean = false //////////////////// // duplicated from ElectionRecordPath so that we can use java.nio.file.Path @@ -139,7 +140,7 @@ actual class PublisherProto actual constructor(topDir: String, createNew: Boolea val ballotDir = protoPaths.encryptedBallotDir(device) validateOutputDir(Path.of(ballotDir), Formatter()) return if (batched) EncryptedBallotBatchedSink(device, protoPaths.encryptedBallotBatched(device)) - else EncryptedBallotDeviceSink(device) + else EncryptedBallotDeviceSink(device) } inner class EncryptedBallotDeviceSink(val device: String) : EncryptedBallotSinkIF { @@ -175,7 +176,7 @@ actual class PublisherProto actual constructor(topDir: String, createNew: Boolea inner class DecryptedTallyOrBallotSink(path: String) : DecryptedTallyOrBallotSinkIF { val out: FileOutputStream = FileOutputStream(path, true) // append - override fun writeDecryptedTallyOrBallot(tally: DecryptedTallyOrBallot){ + override fun writeDecryptedTallyOrBallot(tally: DecryptedTallyOrBallot) { val ballotProto: pbandk.Message = tally.publishProto() writeDelimitedTo(ballotProto, out) } @@ -185,6 +186,32 @@ actual class PublisherProto actual constructor(topDir: String, createNew: Boolea } } + ///////////////////////////////////////////////////////////////////////////// + actual override fun pepBallotSink(outputDir: String): PepBallotSinkIF = PepBallotSink(outputDir) + + inner class PepBallotSink(path: String) : PepBallotSinkIF { + override fun writePepBallot(pepBallot : BallotPep) { + } + override fun close() { + } + } + +} + + +/** Delete everything in the given directory, but leave that directory. */ +fun removeAllFiles(path: Path) { + if (!path.toFile().exists()) { + return + } + Files.walk(path) + .filter { p: Path -> p != path } + .map { obj: Path -> obj.toFile() } + .sorted { o1: File, o2: File? -> -o1.compareTo(o2) } + .forEach { f: File -> f.delete() } +} + + fun writeDelimitedTo(proto: pbandk.Message, output: OutputStream) { val bb = ByteArrayOutputStream() proto.encodeToStream(bb) @@ -205,19 +232,6 @@ actual class PublisherProto actual constructor(topDir: String, createNew: Boolea } } } -} - -/** Delete everything in the given directory, but leave that directory. */ -fun removeAllFiles(path: Path) { - if (!path.toFile().exists()) { - return - } - Files.walk(path) - .filter { p: Path -> p != path } - .map { obj: Path -> obj.toFile() } - .sorted { o1: File, o2: File? -> -o1.compareTo(o2) } - .forEach { f: File -> f.delete() } -} /** Make sure output directories exists and are writeable. */ fun validateOutputDir(path: Path, error: Formatter): Boolean { diff --git a/egklib/src/jvmTest/kotlin/electionguard/publish/ConsumerJsonTest.kt b/egklib/src/jvmTest/kotlin/electionguard/publish/ConsumerJsonTest.kt index 30b38c0f..815c1e19 100644 --- a/egklib/src/jvmTest/kotlin/electionguard/publish/ConsumerJsonTest.kt +++ b/egklib/src/jvmTest/kotlin/electionguard/publish/ConsumerJsonTest.kt @@ -40,8 +40,8 @@ class ConsumerJsonTest { //@ParameterizedTest @MethodSource("params") fun readSpoiledBallotTallys(topdir: String) { - val context = productionGroup() - val consumerIn = makeConsumer(topdir, context) + val group = productionGroup() + val consumerIn = makeConsumer(group, topdir) var count = 0 for (tally in consumerIn.iterateDecryptedBallots()) { println("$count tally = ${tally.id}") @@ -53,10 +53,10 @@ class ConsumerJsonTest { //@ParameterizedTest @MethodSource("params") fun readEncryptedBallots(topdir: String) { - val context = productionGroup() - val consumerIn = makeConsumer(topdir, context) + val group = productionGroup() + val consumerIn = makeConsumer(group, topdir) var count = 0 - for (ballot in consumerIn.iterateEncryptedBallots { true }) { + for (ballot in consumerIn.iterateAllEncryptedBallots { true }) { println("$count ballot = ${ballot.ballotId}") assertTrue(ballot.ballotId.startsWith("ballot-id")) count++ @@ -66,8 +66,8 @@ class ConsumerJsonTest { //@ParameterizedTest @MethodSource("params") fun readEncryptedBallotsCast(topdir: String) { - val context = productionGroup() - val consumerIn = makeConsumer(topdir, context) + val group = productionGroup() + val consumerIn = makeConsumer(group, topdir) var count = 0 for (ballot in consumerIn.iterateAllCastBallots()) { println("$count ballot = ${ballot.ballotId}") @@ -79,8 +79,8 @@ class ConsumerJsonTest { //@ParameterizedTest @MethodSource("params") fun readSubmittedBallotsSpoiled(topdir: String) { - val context = productionGroup() - val consumerIn = makeConsumer(topdir, context) + val group = productionGroup() + val consumerIn = makeConsumer(group, topdir) var count = 0 for (ballot in consumerIn.iterateAllSpoiledBallots()) { println("$count ballot = ${ballot.ballotId}")