diff --git a/README.md b/README.md index a03f18e..2d8eedc 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ [![License](https://img.shields.io/github/license/JohnLCaron/egk-ec)](https://github.com/JohnLCaron/egk-ec/blob/main/LICENSE.txt) ![GitHub branch checks state](https://img.shields.io/github/actions/workflow/status/JohnLCaron/egk-ec/unit-tests.yml) -![Coverage](https://img.shields.io/badge/coverage-90.5%25%20LOC%20(7001/7733)-blue) +![Coverage](https://img.shields.io/badge/coverage-90.6%25%20LOC%20(6958/7679)-blue) # ElectionGuard-Kotlin Elliptic Curve -_last update 04/13/2024_ +_last update 04/17/2024_ EGK Elliptic Curve (egk-ec) is an experimental implementation of [ElectionGuard](https://github.com/microsoft/electionguard), [version 2.0](https://github.com/microsoft/electionguard/releases/download/v2.0/EG_Spec_2_0.pdf), diff --git a/docs/CommandLineInterface.md b/docs/CommandLineInterface.md index ce3d2c2..27595c2 100644 --- a/docs/CommandLineInterface.md +++ b/docs/CommandLineInterface.md @@ -54,17 +54,17 @@ last update 04/16/2024 2. Use existing fake ballots for testing in _src/test/data/fakeBallots_. 5. **Encryption**. - 1. The [_RunAddEncryptedBallots_ CLI](#run-addencryptedballots) reads plaintext ballots from a directory and - writes their encryptions into the specified election record. - 1. The [_RunEncryptBallot_ CLI](#run-encrypt-ballot) reads a plaintext ballot from disk and writes its encryption to disk. - 1. The [_RunExampleEncryption_ CLI](#run-example-encryption) Is an example of running RunEncryptBallot to encrypt ballots. - This can simulate more complex election records with multiple voting devices. - 1. The [_RunBatchEncryption_ CLI](#run-batch-encryption) reads plaintext ballots from a directory and writes their encryptions to the - specified election record. It is multithreaded. - 1. _org.cryptobiotic.eg.encrypt.AddEncryptedBallot_ is a class that your program calls to encrypt plaintext ballots - and add them to the election record. (See _org.cryptobiotic.eg.cli.ExampleEncryption_ as an example of using AddEncryptedBallot). - 1. To run encryption with the Encryption server, see the webapps CLI. This allows you to run the encryption on a - different machine than where ballots are generated, and/or to call from a non-JVM program. + 1. The [_RunAddEncryptedBallots_ CLI](#run-addencryptedballots) reads plaintext ballots from a directory and + writes their encryptions into the specified election record. + 1. The [_RunEncryptBallot_ CLI](#run-encrypt-ballot) reads a plaintext ballot from disk and writes its encryption to disk. + 1. The [_RunExampleEncryption_ CLI](#run-example-encryption) Is an example of running RunEncryptBallot to encrypt ballots. + This can simulate more complex election records with multiple voting devices. + 1. The [_RunBatchEncryption_ CLI](#run-batch-encryption) reads plaintext ballots from a directory and writes their encryptions to the + specified election record. It is multithreaded. + 1. _org.cryptobiotic.eg.encrypt.AddEncryptedBallot_ is a class that your program calls to encrypt plaintext ballots + and add them to the election record. (See _org.cryptobiotic.eg.cli.ExampleEncryption_ as an example of using AddEncryptedBallot). + 1. To run encryption with the Encryption server, see the webapps CLI. This allows you to run the encryption on a + different machine than where ballots are generated, and/or to call from a non-JVM program. 6. **Accumulate Tally**. 1. [_RunAccumulateTally_ CLI](#run-accumulate_tally) reads an ElectionInitialized record and EncryptedBallot @@ -85,7 +85,9 @@ last update 04/16/2024 ## Make ekglib uberJar -For classpath simplicity, the examples below use the [ekglib uberJar](https://github.com/JohnLCaron/egk-ec/blob/main/docs/GettingStarted.md#building-a-library-with-all-dependencies-uber-jar). +For classpath simplicity, the examples below use the +[ekglib uberJar](https://github.com/JohnLCaron/egk-ec/blob/main/docs/GettingStarted.md#building-a-library-with-all-dependencies-uber-jar). + https://github.com/JohnLCaron/egk-ec/blob/main/docs/GettingStarted.md#building-a-library-with-all-dependencies-uber-jar ## Election setup diff --git a/docs/JsonSerializationSpec1.9.md b/docs/JsonSerializationSpec1.9.md index 121c5b5..d9d5c81 100644 --- a/docs/JsonSerializationSpec1.9.md +++ b/docs/JsonSerializationSpec1.9.md @@ -295,7 +295,7 @@ data class HashedElGamalCiphertextJson( val c0: ElementModPJson, // ElementModP, val c1: String, // ByteArray, val c2: UInt256Json, // UInt256, - val numBytes: Int // TODO needed? + val numBytes: Int ) @Serializable diff --git a/docs/JsonSerializationSpec2.1.md b/docs/JsonSerializationSpec2.1.md index 73bc70f..b22a95c 100644 --- a/docs/JsonSerializationSpec2.1.md +++ b/docs/JsonSerializationSpec2.1.md @@ -408,7 +408,7 @@ data class HashedElGamalCiphertextJson( val c0: ElementModPJson, // ElementModP, val c1: String, // ByteArray, val c2: UInt256Json, // UInt256, - val numBytes: Int // TODO needed? + val numBytes: Int ) @Serializable @@ -701,6 +701,8 @@ data class ContestDataJson( val status: String, ) +TODO ContestDataJson is not in any spec. + ```` Example: diff --git a/src/main/kotlin/org/cryptobiotic/eg/cli/RunTrustedBallotDecryption.kt b/src/main/kotlin/org/cryptobiotic/eg/cli/RunTrustedBallotDecryption.kt index e1f873e..fec9e64 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/cli/RunTrustedBallotDecryption.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/cli/RunTrustedBallotDecryption.kt @@ -109,7 +109,6 @@ class RunTrustedBallotDecryption { decryptingTrustees, ) - // TODO you may want to put the decryption results in the same directory, but sinks now are append-only. val publisher = makePublisher(outputDir, false) val sink: DecryptedBallotSinkIF = publisher.decryptedBallotSink() diff --git a/src/main/kotlin/org/cryptobiotic/eg/decrypt/BallotDecryptor.kt b/src/main/kotlin/org/cryptobiotic/eg/decrypt/BallotDecryptor.kt index 32b1f38..a6f89a8 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/decrypt/BallotDecryptor.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/decrypt/BallotDecryptor.kt @@ -19,40 +19,8 @@ class BallotDecryptor( val guardians: Guardians, // all guardians decryptingTrustees: List, // the trustees available to decrypt ) { - val lagrangeCoordinates: Map - val stats = Stats() - val nguardians = guardians.guardians.size // number of guardinas - val quorum = guardians.guardians[0].coefficientCommitments().size val decryptor = CipherDecryptor(group, extendedBaseHash, publicKey, guardians, decryptingTrustees) - - init { - // check that the DecryptingTrustee's match their public key - val badTrustees = mutableListOf() - for (trustee in decryptingTrustees) { - val guardian = guardians.guardianMap[trustee.id()] - if (guardian == null) { - badTrustees.add(trustee.id()) - } else { - if (trustee.guardianPublicKey().key != guardian.publicKey()) { - badTrustees.add(trustee.id()) - logger.error { "trustee public key = ${trustee.guardianPublicKey()} not equal guardian = ${guardian.publicKey()}" } - } - } - } - if (badTrustees.isNotEmpty()) { - throw RuntimeException("DecryptingTrustee(s) ${badTrustees.joinToString(",")} do not match the public record") - } - - // build the lagrangeCoordinates once and for all - val dguardians = mutableListOf() - for (trustee in decryptingTrustees) { - val present: List = // available trustees minus me - decryptingTrustees.filter { it.id() != trustee.id() }.map { it.xCoordinate() } - val coeff: ElementModQ = group.computeLagrangeCoefficient(trustee.xCoordinate(), present) - dguardians.add(LagrangeCoordinate(trustee.id(), trustee.xCoordinate(), coeff)) - } - this.lagrangeCoordinates = dguardians.associateBy { it.guardianId } - } + val stats = Stats() fun decrypt(eballot: EncryptedBallotIF, errs : ErrorMessages): DecryptedTallyOrBallot? { if (eballot.electionId != extendedBaseHash) { @@ -87,7 +55,7 @@ class BallotDecryptor( val result = makeBallot(eballot, decryptionAndProofs, contestDecryptionAndProofs, errs.nested("BallotDecryptor.decrypt")) if (!errs.hasErrors()) { val ndecrypt = decryptionAndProofs.size + contestDecryptionAndProofs.size - stats.of("decryptTally").accum(stopwatch.stop(), ndecrypt) + stats.of("decryptBallot").accum(stopwatch.stop(), ndecrypt) } return if (errs.hasErrors()) null else result!! } @@ -103,10 +71,13 @@ class BallotDecryptor( val selections = econtest.selections.map { eselection -> val (decryption, proof) = decryptions[selectionCount++] val (T, tally) = decryption.decryptCiphertext(publicKey) + if (tally == null) { + errs.add("Cant decrypt tally for ${econtest.contestId}.${eselection.selectionId}") + } DecryptedTallyOrBallot.Selection( eselection.selectionId, - tally?: 0, // TODO error handling + tally?: 0, T, (decryption.cipher as Ciphertext).delegate, proof diff --git a/src/main/kotlin/org/cryptobiotic/eg/decrypt/CipherDecryptor.kt b/src/main/kotlin/org/cryptobiotic/eg/decrypt/CipherDecryptor.kt index f09a484..ce8af08 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/decrypt/CipherDecryptor.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/decrypt/CipherDecryptor.kt @@ -6,80 +6,39 @@ import org.cryptobiotic.eg.core.* import org.cryptobiotic.eg.core.Base16.toHex import org.cryptobiotic.eg.election.* import org.cryptobiotic.util.ErrorMessages -import org.cryptobiotic.util.Stats /** Orchestrates the decryption of List or List using DecryptingTrustees. */ class CipherDecryptor( val group: GroupContext, val extendedBaseHash: UInt256, val publicKey: ElGamalPublicKey, - val guardians: Guardians, // all guardians + guardians: Guardians, // all guardians private val decryptingTrustees: List, // the trustees available to decrypt ) { - val lagrangeCoordinates: Map - val stats = Stats() - val nguardians = guardians.guardians.size // number of guardinas - val quorum = guardians.guardians[0].coefficientCommitments().size - - init { - // TODO put these in the guardians - // check that the DecryptingTrustee's match their public key - val badTrustees = mutableListOf() - for (trustee in decryptingTrustees) { - val guardian = guardians.guardianMap[trustee.id()] - if (guardian == null) { - badTrustees.add(trustee.id()) - } else { - if (trustee.guardianPublicKey().key != guardian.publicKey()) { - badTrustees.add(trustee.id()) - logger.error { "trustee public key = ${trustee.guardianPublicKey()} not equal guardian = ${guardian.publicKey()}" } - } - } - } - if (badTrustees.isNotEmpty()) { - throw RuntimeException("DecryptingTrustee(s) ${badTrustees.joinToString(",")} do not match the public record") - } - - // build the lagrangeCoordinates once and for all - val dguardians = mutableListOf() - for (trustee in decryptingTrustees) { - val present: List = // available trustees minus me - decryptingTrustees.filter { it.id() != trustee.id() }.map { it.xCoordinate() } - val coeff: ElementModQ = group.computeLagrangeCoefficient(trustee.xCoordinate(), present) - dguardians.add(LagrangeCoordinate(trustee.id(), trustee.xCoordinate(), coeff)) - } - this.lagrangeCoordinates = dguardians.associateBy { it.guardianId } - } + val lagrangeCoordinates: Map = guardians.buildLagrangeCoordinates(decryptingTrustees) fun decrypt(texts: List, errs : ErrorMessages): List? { if (texts.isEmpty()) return emptyList() // get the PartialDecryptions from each of the trustees - val partialDecryptions = decryptingTrustees.map { // partialDecryptions are in the order of the decryptingTrustees - it.getPartialDecryptionsFromTrustee(texts, errs) + val partialDecryptions = decryptingTrustees.map { trustee -> // partialDecryptions are in order of the decryptingTrustees + trustee.getPartialDecryptionsFromTrustee(texts, errs) } if (errs.hasErrors()) { - logger.error { "partial decryptions failed = ${errs}" } + logger.error { "partial decryptions failed = $errs" } return null } // Do the decryption for each text val decryptions = texts.mapIndexed { idx, text -> - // TODO could use the shares // lagrange weighted product of the shares, M = Prod(M_i^w_i) mod p; spec 2.0.0, eq 68 - val weightedProduct = with(group) { + val weightedProduct = with (group) { // for this idx, run over all the trustees partialDecryptions.mapIndexed { tidx, pds -> val trustee = decryptingTrustees[tidx] - val lagrange = lagrangeCoordinates[trustee.id()] - val coeff = if (lagrange == null) { // TODO check to make sure this cant happen - errs.add("missing lagrangeCoordinate for ${trustee.id()}") - group.ONE_MOD_Q - } else { - lagrange.lagrangeCoefficient - } - pds.partial[idx].Mi powP coeff + val lagrange = lagrangeCoordinates[trustee.id()]!! // buildLagrangeCoordinates() guarentees exists + pds.partial[idx].Mi powP lagrange.lagrangeCoefficient }.multP() } @@ -93,7 +52,7 @@ class CipherDecryptor( CipherDecryption(text, weightedProduct, collectiveChallenge) } if (errs.hasErrors()) { - logger.error { "decrypt failed = ${errs}" } + logger.error { "decrypt failed = $errs" } return null } @@ -103,12 +62,12 @@ class CipherDecryptor( trustee.getResponsesFromTrustee(batchId, decryptions, errs.nested("trusteeChallengeResponses")) } if (errs.hasErrors()) { - logger.error { "decrypt failed = ${errs}" } + logger.error { "decrypt failed = $errs" } return null } // After gathering the challenge responses from the available trustees, we can create the proof - return makeHDecryptionAndProofs( decryptions, challengeResponses, errs) + return makeHDecryptionAndProofs( decryptions, challengeResponses) } private fun DecryptingTrusteeIF.getPartialDecryptionsFromTrustee(texts: List, errs : ErrorMessages) : PartialDecryptions { @@ -122,9 +81,9 @@ class CipherDecryptor( return pds } - // send all challenges for a ballot / tally to one trustee, get all its responses + // send all challenges for a ballot / tally to one trustee, get all its responses, in one call fun DecryptingTrusteeIF.getResponsesFromTrustee(batchId: Int, decryptions: List, errs : ErrorMessages) : ChallengeResponses { - val wi = lagrangeCoordinates[this.id()]!!.lagrangeCoefficient // TODO ensure cant fail + val wi = lagrangeCoordinates[this.id()]!!.lagrangeCoefficient // buildLagrangeCoordinates() guarentees exists // Create all the challenges from each Decryption for this trustee val requests: MutableList = mutableListOf() decryptions.forEach { decryption -> @@ -142,14 +101,13 @@ class CipherDecryptor( fun makeHDecryptionAndProofs( decryptions: List, // for each text challengeResponses: List, // for each trustee, list of responses for each text - errs : ErrorMessages, // TODO errors ): List { val ds = mutableListOf() decryptions.forEachIndexed { idx, decryption -> val responsesForIdx = challengeResponses.map { it.responses[idx] } ds.add( makeHDecryptionAndProof( decryption, responsesForIdx) ) } - return ds // for each text + return ds // one for each text } private fun makeHDecryptionAndProof( @@ -205,6 +163,7 @@ class CipherDecryption( // the return value of the decryption data class CipherDecryptionAndProof(val decryption: CipherDecryption, val proof: ChaumPedersenProof) +// abstraction so we can work with either ElGamalCiphertext or HashedElGamalCiphertext interface Cipher { fun pad() : ElementModP fun collectiveChallenge(extendedBaseHash:UInt256, publicKey: ElGamalPublicKey, a: ElementModP, b: ElementModP, beta: ElementModP): UInt256 @@ -234,35 +193,4 @@ data class HashedCiphertext(val delegate: HashedElGamalCiphertext): Cipher { delegate.c1.toHex(), delegate.c2, a, b, beta) -} - -////////////////////////////////////////////////////////////////////////////// - -data class LagrangeCoordinate( - var guardianId: String, - var xCoordinate: Int, - var lagrangeCoefficient: ElementModQ, // wℓ, spec 2.0.0 eq 67 -) { - init { - require(guardianId.isNotEmpty()) - require(xCoordinate > 0) - } -} - -/** Compute the lagrange coefficient, now that we know which guardians are present; 2.0, section 3.6.2, eq 67. */ -fun GroupContext.computeLagrangeCoefficient(coordinate: Int, present: List): ElementModQ { - val others: List = present.filter { it != coordinate } - if (others.isEmpty()) { - return this.ONE_MOD_Q - } - val numerator: Int = others.reduce { a, b -> a * b } - - val diff: List = others.map { degree -> degree - coordinate } - val denominator = diff.reduce { a, b -> a * b } - - val denomQ = - if (denominator > 0) denominator.toElementModQ(this) else (-denominator).toElementModQ(this) - .unaryMinus() - - return numerator.toElementModQ(this) / denomQ } \ No newline at end of file diff --git a/src/main/kotlin/org/cryptobiotic/eg/decrypt/Guardians.kt b/src/main/kotlin/org/cryptobiotic/eg/decrypt/Guardians.kt index f6b4413..312ad12 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/decrypt/Guardians.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/decrypt/Guardians.kt @@ -1,6 +1,8 @@ package org.cryptobiotic.eg.decrypt +import io.github.oshai.kotlinlogging.KotlinLogging import org.cryptobiotic.eg.core.* +import org.cryptobiotic.eg.decrypt.CipherDecryptor.Companion import org.cryptobiotic.eg.election.Guardian import org.cryptobiotic.eg.keyceremony.calculateGexpPiAtL @@ -38,4 +40,68 @@ data class Guardians(val group : GroupContext, val guardians: List) { } } + fun buildLagrangeCoordinates(decryptingTrustees: List) : Map { + // check that the DecryptingTrustee's match their public key + val badTrustees = mutableListOf() + for (trustee in decryptingTrustees) { + val guardian = guardianMap[trustee.id()] + if (guardian == null) { + badTrustees.add(trustee.id()) + } else { + if (trustee.guardianPublicKey().key != guardian.publicKey()) { + badTrustees.add(trustee.id()) + logger.error { "trustee public key = ${trustee.guardianPublicKey()} not equal guardian = ${guardian.publicKey()}" } + } + } + } + if (badTrustees.isNotEmpty()) { + throw RuntimeException("DecryptingTrustee(s) ${badTrustees.joinToString(",")} do not match the public record") + } + + // build lagrange coeff for each trustee + val lagrange = decryptingTrustees.map { trustee -> + val present: List = // available trustees minus me + decryptingTrustees.filter { it.id() != trustee.id() }.map { it.xCoordinate() } + val coeff: ElementModQ = computeLagrangeCoefficient(group, trustee.xCoordinate(), present) + LagrangeCoordinate(trustee.id(), trustee.xCoordinate(), coeff) + } + + return lagrange.associateBy { it.guardianId } + } + + companion object { + private val logger = KotlinLogging.logger("Guardians") + } + +} + +////////////////////////////////////////////////////////////////////////////// + +data class LagrangeCoordinate( + var guardianId: String, + var xCoordinate: Int, + var lagrangeCoefficient: ElementModQ, // wℓ, spec 2.0.0 eq 67 +) { + init { + require(guardianId.isNotEmpty()) + require(xCoordinate > 0) + } +} + +/** Compute the lagrange coefficient, now that we know which guardians are present; 2.0, section 3.6.2, eq 67. */ +fun computeLagrangeCoefficient(group: GroupContext, coordinate: Int, present: List): ElementModQ { + val others: List = present.filter { it != coordinate } + if (others.isEmpty()) { + return group.ONE_MOD_Q + } + val numerator: Int = others.reduce { a, b -> a * b } + + val diff: List = others.map { degree -> degree - coordinate } + val denominator = diff.reduce { a, b -> a * b } + + val denomQ = + if (denominator > 0) denominator.toElementModQ(group) else (-denominator).toElementModQ(group) + .unaryMinus() + + return numerator.toElementModQ(group) / denomQ } \ No newline at end of file diff --git a/src/main/kotlin/org/cryptobiotic/eg/decrypt/TallyDecryptor.kt b/src/main/kotlin/org/cryptobiotic/eg/decrypt/TallyDecryptor.kt index 95238bf..fb3025e 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/decrypt/TallyDecryptor.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/decrypt/TallyDecryptor.kt @@ -16,40 +16,8 @@ class TallyDecryptor( val guardians: Guardians, // all guardians decryptingTrustees: List, // the trustees available to decrypt ) { - val lagrangeCoordinates: Map - val nguardians = guardians.guardians.size // number of guardinas - val quorum = guardians.guardians[0].coefficientCommitments().size val decryptor = CipherDecryptor(group, extendedBaseHash, publicKey, guardians, decryptingTrustees) - init { - // check that the DecryptingTrustee's match their public key - val badTrustees = mutableListOf() - for (trustee in decryptingTrustees) { - val guardian = guardians.guardianMap[trustee.id()] - if (guardian == null) { - badTrustees.add(trustee.id()) - } else { - if (trustee.guardianPublicKey().key != guardian.publicKey()) { - badTrustees.add(trustee.id()) - logger.error { "trustee public key = ${trustee.guardianPublicKey()} not equal guardian = ${guardian.publicKey()}" } - } - } - } - if (badTrustees.isNotEmpty()) { - throw RuntimeException("DecryptingTrustee(s) ${badTrustees.joinToString(",")} do not match the public record") - } - - // build the lagrangeCoordinates once and for all - val dguardians = mutableListOf() - for (trustee in decryptingTrustees) { - val present: List = // available trustees minus me - decryptingTrustees.filter { it.id() != trustee.id() }.map { it.xCoordinate() } - val coeff: ElementModQ = group.computeLagrangeCoefficient(trustee.xCoordinate(), present) - dguardians.add(LagrangeCoordinate(trustee.id(), trustee.xCoordinate(), coeff)) - } - this.lagrangeCoordinates = dguardians.associateBy { it.guardianId } - } - fun decrypt(etally: EncryptedTally, errs : ErrorMessages): DecryptedTallyOrBallot? { if (etally.electionId != extendedBaseHash) { errs.add("Encrypted Tally/Ballot has wrong electionId = ${etally.electionId}") @@ -78,15 +46,16 @@ class TallyDecryptor( ): DecryptedTallyOrBallot? { var count = 0 val contests = etally.contests.map { econtest -> - val cerrs = errs.nested("Contest ${econtest.contestId}") val selections = econtest.selections.map { eselection -> - val serrs = cerrs.nested("Selection ${eselection.selectionId}") val (decryption, proof) = decryptions[count++] val (T, tally) = decryption.decryptCiphertext(publicKey) + if (tally == null) { + errs.add("Cant decrypt tally for ${econtest.contestId}.${eselection.selectionId}") + } DecryptedTallyOrBallot.Selection( eselection.selectionId, - tally!!, // TODO errors + tally!!, T, (decryption.cipher as Ciphertext).delegate, proof diff --git a/src/main/kotlin/org/cryptobiotic/eg/election/EncryptedBallot.kt b/src/main/kotlin/org/cryptobiotic/eg/election/EncryptedBallot.kt index af2ed1b..6a9e1c8 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/election/EncryptedBallot.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/election/EncryptedBallot.kt @@ -79,7 +79,7 @@ data class EncryptedBallot( val contestHash: UInt256, // eq 58 override val selections: List, val proof: ChaumPedersenRangeProofKnownNonce, - override val contestData: HashedElGamalCiphertext, // TODO make optional? + override val contestData: HashedElGamalCiphertext, val preEncryption: PreEncryption? = null, // pre-encrypted ballots only ) : EncryptedBallotIF.Contest { diff --git a/src/main/kotlin/org/cryptobiotic/eg/keyceremony/KeyCeremony.kt b/src/main/kotlin/org/cryptobiotic/eg/keyceremony/KeyCeremony.kt index e8e0abb..65868be 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/keyceremony/KeyCeremony.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/keyceremony/KeyCeremony.kt @@ -32,10 +32,7 @@ fun keyCeremonyExchange(trustees: List, allowEncryptedFail return Err("keyCeremonyExchange trustees have different quorums = ${trustees.map{it.coefficientCommitments().size}}") } - // TODO if the trustees are not trusted, we could do other verification tests here. - // are the public keys valid? - // are the encrypted shares valid? - // are the unencrypted shares valid? + // if the trustees are not trusted, we could do other verification tests here. // exchange PublicKeys val publicKeys: MutableList = mutableListOf() @@ -96,7 +93,6 @@ fun keyCeremonyExchange(trustees: List, allowEncryptedFail // footnote 28 It is also permissible to dismiss any guardian that makes a false claim of malfeasance. However, this is not // required as the sensitive information that is released as a result of the claim could have been released by the claimant // in any case. - // TODO KeyShare should include ξi,ℓ // Phase Two: if any secretKeyShares fail to validate, send and validate KeyShares val keyResults: MutableList> = mutableListOf() diff --git a/src/main/kotlin/org/cryptobiotic/eg/preencrypt/PreBallot.kt b/src/main/kotlin/org/cryptobiotic/eg/preencrypt/PreBallot.kt index 80c3fe5..05a8604 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/preencrypt/PreBallot.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/preencrypt/PreBallot.kt @@ -32,7 +32,7 @@ internal data class PreContest( val votedFor: List // nselections, in order by sequence_order ) { init { - require(votedFor.size == allSelectionHashes.size - selectedVectors.size) // TODO + require(votedFor.size == allSelectionHashes.size - selectedVectors.size) } fun selectedCodes() : List = selectedVectors.map { it.shortCode } fun nselections() = votedFor.size diff --git a/src/main/kotlin/org/cryptobiotic/eg/preencrypt/PreEncryptor.kt b/src/main/kotlin/org/cryptobiotic/eg/preencrypt/PreEncryptor.kt index fcb665f..0535bdb 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/preencrypt/PreEncryptor.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/preencrypt/PreEncryptor.kt @@ -3,7 +3,6 @@ package org.cryptobiotic.eg.preencrypt import org.cryptobiotic.eg.core.* import org.cryptobiotic.eg.election.ManifestIF - /** * The crypto part of the "The Ballot Encrypting Tool" * The encrypting/decrypting of the primaryNonce is done external to this. @@ -64,7 +63,7 @@ class PreEncryptor( // In a contest with a selection limit of L, an additional L null vectors are added var nextSeqNo = sortedSelections.last().sequenceOrder + 1 for (nullVectorIdx in (1..contestLimit)) { - // TODO "null labels may be in manifest", see 4.2.1. wtf? + // TODO "null labels may be in manifest", see Issue #56 preeSelections.add( preencryptSelection(primaryNonce, this.sequenceOrder, "null${nullVectorIdx}", nextSeqNo, sortedSelectionIndices)) nextSeqNo++ } @@ -114,4 +113,9 @@ class PreEncryptor( encryptionNonces, ) } + + companion object { + // TODO "hash trimming function Ω must be completely specified in the election manifest" see Issue #56 + fun sigma(hash: UInt256): String = hash.toHex().substring(0, 5) + } } \ No newline at end of file diff --git a/src/main/kotlin/org/cryptobiotic/eg/preencrypt/Recorder.kt b/src/main/kotlin/org/cryptobiotic/eg/preencrypt/Recorder.kt index 3c7079f..23e4291 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/preencrypt/Recorder.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/preencrypt/Recorder.kt @@ -8,15 +8,6 @@ import org.cryptobiotic.util.ErrorMessages /** * The crypto part of the "The Recording Tool". * The encrypting/decrypting primaryNonce is done external. - * - * TODO : uncast (implicitly or explicitly challenged) aka CHALLENGED - * "For each uncast (implicitly or explicitly challenged) ballot, the recording tool returns the primary - * nonce that enables the encryptions to be opened and checked." - * "For an uncast ballot, the wrapper computes the short codes for all possible selections and posts - * in the election record the full set of pre-encryption vectors, selection hashes, and short codes for - * each possible selection." - * "The decryptions of all pre-encryptions correspond to the plaintext values indicated in the - * election manifest." */ class Recorder( val group: GroupContext, diff --git a/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyDecryption.kt b/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyDecryption.kt index ab6d572..8cfd048 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyDecryption.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyDecryption.kt @@ -126,16 +126,6 @@ class VerifyDecryption( } - // TODO check - // Verification 11 (Correctness of decryptions of contest data) - // An election verifier must confirm the correct decryption of the contest data field for each contest by - // verifying the conditions analogous to Verification 9 for the corresponding NIZK proof with (A, B) - // replaced by (C0 , C1 , C2 ) and Mi by mi as follows. An election verifier must compute the following values. - // (11.1) a = g v · K c mod p, - // (11.2) b = C0v · β c mod p. - // An election verifier must then confirm the following. - // (11.A) The given value v is in the set Zq . - // (11.B) The challenge value c satisfies c = H(HE ; 0x31, K, C0 , C1 , C2 , a, b, β). private fun verifyContestData(decryptedContestData: DecryptedTallyOrBallot.DecryptedContestData, errs: ErrorMessages){ // (11.A,14.A) The given value v is in the set Zq. if (!decryptedContestData.proof.r.inBounds()) { diff --git a/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyEncryptedBallots.kt b/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyEncryptedBallots.kt index 7ae2e0d..5f8c632 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyEncryptedBallots.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyEncryptedBallots.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.yield import org.cryptobiotic.eg.encrypt.EncryptedBallotChain import org.cryptobiotic.eg.encrypt.EncryptedBallotChain.Companion.assembleChain +import org.cryptobiotic.eg.preencrypt.PreEncryptor import org.cryptobiotic.eg.publish.Consumer import org.cryptobiotic.util.Stopwatch @@ -61,8 +62,7 @@ class VerifyEncryptedBallots( joinAll(*verifierJobs.toTypedArray()) } - // check duplicate confirmation codes (7.C): TODO what if there are multiple records for the election? - // TODO what about checking for duplicate ballot ids? + // check duplicate confirmation codes (7.C): val checkDuplicates = mutableMapOf() confirmationCodes.forEach { if (checkDuplicates[it.code] != null) { @@ -147,7 +147,7 @@ class VerifyEncryptedBallots( } if (isPreencrypt) { - verifyPreencryptionShortCodes(contest, errs) + verifyPreencryptionShortCodes(contest, errs, PreEncryptor::sigma) } } diff --git a/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyPreEncryptedBallots.kt b/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyPreEncryptedBallots.kt index 4d914a4..271b4b0 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyPreEncryptedBallots.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyPreEncryptedBallots.kt @@ -8,9 +8,6 @@ import org.cryptobiotic.util.ErrorMessages ////////////////////////////////////////////////////////////////////////////// // pre-encryption -// TODO specify sigma in manifest -private fun sigma(hash: UInt256): String = hash.toHex().substring(0, 5) - /* Every step of verification that applies to traditional ElectionGuard ballots also applies to pre- encrypted ballots – with the exception of the process for computing confirmation codes. However, @@ -53,6 +50,7 @@ record as uncast. fun VerifyEncryptedBallots.verifyPreencryptionShortCodes( contest: EncryptedBallot.Contest, errs: ErrorMessages, + sigma : (UInt256) -> String, // "hash trimming function Ω must be completely specified in the election manifest" ) { if (contest.preEncryption == null) { errs.add(" 18. Contest has no preEncryption") diff --git a/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyTally.kt b/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyTally.kt index 023cb3e..7715745 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyTally.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyTally.kt @@ -40,12 +40,7 @@ class VerifyTally( if (selection.encryptedVote.data != accum.data) { errs.add(" 8.B Ballot Aggregation does not match: $key") } - } /* else { - // TODO what is it? is it needed? left over from placeholders ?? - if (selection.encryptedVote.pad != group.ZERO_MOD_P || selection.encryptedVote.data != group.ZERO_MOD_P) { - errs.add(" Ballot Aggregation empty does not match $key") - } - } */ + } } } diff --git a/src/test/kotlin/org/cryptobiotic/eg/decrypt/EncryptDecryptTest.kt b/src/test/kotlin/org/cryptobiotic/eg/decrypt/EncryptDecryptTest.kt index 076310c..ce8e5ab 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/decrypt/EncryptDecryptTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/decrypt/EncryptDecryptTest.kt @@ -69,7 +69,7 @@ fun encryptDecrypt( val evote = vote.encrypt(publicKey, group.randomElementModQ(minimum = 1)) val available = trustees.filter {present.contains(it.xCoordinate())} - val lagrangeCoefficients = available.associate { it.id() to group.computeLagrangeCoefficient(it.xCoordinate(), present) } + val lagrangeCoefficients = available.associate { it.id() to computeLagrangeCoefficient(group, it.xCoordinate(), present) } val shares: List = available.map { val pd = it.decrypt(listOf(evote.pad)) diff --git a/src/test/kotlin/org/cryptobiotic/eg/decrypt/LagrangeCoefficientsTest.kt b/src/test/kotlin/org/cryptobiotic/eg/decrypt/LagrangeCoefficientsTest.kt index 4db58f4..504d1e5 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/decrypt/LagrangeCoefficientsTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/decrypt/LagrangeCoefficientsTest.kt @@ -30,7 +30,7 @@ class LagrangeCoefficientsTest { val coeff: Int = computeLagrangeCoefficientInt(coord, others) val numer: Int = computeLagrangeNumerator(others) val denom: Int = computeLagrangeDenominator(coord, others) - val coeffQ = group.computeLagrangeCoefficient(coord, others.map { it }) + val coeffQ = computeLagrangeCoefficient(group, coord, others.map { it }) println("($coord) $coeff == ${numer} / ${denom} rem ${numer % denom} == $coeffQ") if (exact) { assertEquals(0, numer % denom) diff --git a/src/test/kotlin/org/cryptobiotic/eg/decrypt/LagrangeTest.kt b/src/test/kotlin/org/cryptobiotic/eg/decrypt/LagrangeTest.kt index b26cd03..2f2aad1 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/decrypt/LagrangeTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/decrypt/LagrangeTest.kt @@ -12,15 +12,15 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue -private val group = productionGroup() /** Test KeyCeremony Trustee generation and recovered decryption. */ class LagrangeTest { + private val group = productionGroup() @Test fun testLagrangeInterpolation() { - val w1 = group.computeLagrangeCoefficient(1, listOf(1, 2)) - val w2 = group.computeLagrangeCoefficient(2, listOf(1, 2)) + val w1 = computeLagrangeCoefficient(group, 1, listOf(1, 2)) + val w2 = computeLagrangeCoefficient(group, 2, listOf(1, 2)) val polly = group.generatePolynomial("guardian1", 1, 2) val y1 = polly.valueAt(group, 1) @@ -33,8 +33,8 @@ class LagrangeTest { @Test fun testLagrangePolySum() { - val w1 = group.computeLagrangeCoefficient(1, listOf(1, 2)) - val w2 = group.computeLagrangeCoefficient(2, listOf(1, 2)) + val w1 = computeLagrangeCoefficient(group, 1, listOf(1, 2)) + val w2 = computeLagrangeCoefficient(group, 2, listOf(1, 2)) val polly1 = group.generatePolynomial("guardian1", 1, 2) val y11 = polly1.valueAt(group, 1) @@ -51,8 +51,8 @@ class LagrangeTest { @Test fun testTrusteePolySum() { - val w1 = group.computeLagrangeCoefficient(1, listOf(1, 2)) - val w2 = group.computeLagrangeCoefficient(2, listOf(1, 2)) + val w1 = computeLagrangeCoefficient(group, 1, listOf(1, 2)) + val w2 = computeLagrangeCoefficient(group, 2, listOf(1, 2)) val polly1 = KeyCeremonyTrustee(group, "guardian1", 1, 2, 2) val y11 = polly1.valueAt(group, 1) @@ -69,8 +69,8 @@ class LagrangeTest { @Test fun testTrusteePolySum2() { - val w1 = group.computeLagrangeCoefficient(1, listOf(1, 2)) - val w2 = group.computeLagrangeCoefficient(2, listOf(1, 2)) + val w1 = computeLagrangeCoefficient(group, 1, listOf(1, 2)) + val w2 = computeLagrangeCoefficient(group, 2, listOf(1, 2)) val polly1 = KeyCeremonyTrustee(group, "guardian1", 1, 2, 2) val y11 = polly1.valueAt(group, 1) @@ -109,7 +109,7 @@ class LagrangeTest { trustees: List, present: List) { val available = trustees.filter {present.contains(it.xCoordinate())} - val lagrangeCoefficients = available.associate { it.id to group.computeLagrangeCoefficient(it.xCoordinate, present) } + val lagrangeCoefficients = available.associate { it.id to computeLagrangeCoefficient(group, it.xCoordinate, present) } lagrangeCoefficients.values.forEach { assertTrue( it.inBounds()) } val weightedSum = with(group) { diff --git a/src/test/kotlin/org/cryptobiotic/eg/preencrypt/PreEncryptorTest.kt b/src/test/kotlin/org/cryptobiotic/eg/preencrypt/PreEncryptorTest.kt index d4e61aa..cb8bd03 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/preencrypt/PreEncryptorTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/preencrypt/PreEncryptorTest.kt @@ -295,7 +295,7 @@ internal fun runComplete( assertFalse(errs.hasErrors()) } -fun sigma(hash: UInt256): String = hash.toHex().substring(0, 5) +fun sigma(code: UInt256) : String = PreEncryptor.sigma(code) internal class ChosenBallot(val selectedIdx: Int) { diff --git a/src/test/kotlin/org/cryptobiotic/eg/workflow/FakeKeyCeremony.kt b/src/test/kotlin/org/cryptobiotic/eg/workflow/FakeKeyCeremony.kt index cd0aa43..40bc6ed 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/workflow/FakeKeyCeremony.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/workflow/FakeKeyCeremony.kt @@ -138,7 +138,7 @@ fun testDoerreDecrypt(group: GroupContext, val evote = vote.encrypt(publicKey, group.randomElementModQ(minimum = 1)) val available = trustees.filter {present.contains(it.xCoordinate())} - val lagrangeCoefficients = available.associate { it.id to group.computeLagrangeCoefficient(it.xCoordinate, present) } + val lagrangeCoefficients = available.associate { it.id to computeLagrangeCoefficient(group, it.xCoordinate, present) } val shares: List = available.map { val pd = it.decrypt(listOf(evote.pad)) diff --git a/src/test/kotlin/org/cryptobiotic/util/DfracTest.kt b/src/test/kotlin/org/cryptobiotic/util/DfracTest.kt index 34acce9..7d326a5 100644 --- a/src/test/kotlin/org/cryptobiotic/util/DfracTest.kt +++ b/src/test/kotlin/org/cryptobiotic/util/DfracTest.kt @@ -79,7 +79,7 @@ class DfracTest { fraction.append("0") } } else if (fixedDecimals < fracFigs) { - val chop = fracFigs - fixedDecimals // TODO should round !! + val chop = fracFigs - fixedDecimals // LOOK should round !! fraction.setLength(fraction.length - chop) }