Skip to content

Commit

Permalink
Use Json for shuffled ballot serialization.
Browse files Browse the repository at this point in the history
Add coverage tests for VectorQ, RunPaperBallotDecrypt.
  • Loading branch information
JohnLCaron committed Apr 21, 2024
1 parent ce94b5b commit 52b6130
Show file tree
Hide file tree
Showing 341 changed files with 108,741 additions and 11,794 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[![License](https://img.shields.io/github/license/JohnLCaron/egk-ec)](https://github.com/JohnLCaron/egk-ec-mixnet/blob/main/LICENSE.txt)
![GitHub branch checks state](https://img.shields.io/github/actions/workflow/status/JohnLCaron/egk-ec-mixnet/unit-tests.yml)
![Coverage](https://img.shields.io/badge/coverage-90.1%25%20LOC%20(1353/1502)-blue)
![Coverage](https://img.shields.io/badge/coverage-90.6%25%20LOC%20(1356/1497)-blue)

# Egk Elliptic Curves Mixnet

_last update 04/20/2024_
_last update 04/21/2024_

Implementation of a mixnet using the [ElectionGuard Kotlin Elliptical Curve library](https://github.com/JohnLCaron/egk-ec),
and the [Verificatum library](https://www.verificatum.org/). The mixnet uses the Terelius / Wikström (TW) mixnet
Expand Down Expand Up @@ -210,11 +210,11 @@ working/public
mix1/
mix_config.json
proof_of_shuffle.json
ShuffledBallots.bin
ShuffledBallots.json
mix2/
mix_config.json
proof_of_shuffle.json
ShuffledBallots.bin
ShuffledBallots.json
mixN/
...
Expand Down
24 changes: 0 additions & 24 deletions src/main/kotlin/org/cryptobiotic/maths/VectorCiphertext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,6 @@ import org.cryptobiotic.eg.core.*
data class VectorCiphertext(val group: GroupContext, val elems: List<ElGamalCiphertext> ) {
val nelems = elems.size

infix fun powP(exp: VectorQ): VectorCiphertext {
require (nelems == exp.nelems)
val powers = elems.mapIndexed { idx, it -> ElGamalCiphertext(it.pad powP exp.elems[idx], it.data powP exp.elems[idx]) }
return VectorCiphertext(group, powers)
}

operator infix fun times(other: VectorCiphertext): VectorCiphertext {
require (nelems == other.nelems)
val products = elems.mapIndexed { idx, it -> ElGamalCiphertext(it.pad * other.elems[idx].pad, it.data * other.elems[idx].data) }
Expand All @@ -36,21 +30,3 @@ data class VectorCiphertext(val group: GroupContext, val elems: List<ElGamalCiph
}
}

fun Prod(vc: VectorCiphertext): ElGamalCiphertext {
return vc.elems.encryptedSum()!!
}

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

fun ElementModP.toBigInteger(): java.math.BigInteger {
return java.math.BigInteger(1, this.byteArray().normalize(512))
}

fun ElementModQ.toBigInteger(): java.math.BigInteger {
return java.math.BigInteger(1, this.byteArray().normalize(512))
}

fun ByteArray.toBigInteger(): java.math.BigInteger {
return java.math.BigInteger(1, this.normalize(512))
}

16 changes: 3 additions & 13 deletions src/main/kotlin/org/cryptobiotic/maths/VectorQ.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,13 @@ data class MatrixQ(val elems: List<VectorQ> ) {

constructor(group: GroupContext, llist: List<List<ElementModQ>>): this(llist.map{ VectorQ(group, it) })

// right multiply
fun rmultiply(colv: VectorQ) : List<ElementModQ> {
fun rightMultiply(colv: VectorQ) : List<ElementModQ> {
require(colv.nelems == width)
return elems.map{ row -> row.innerProduct(colv) }
}

fun invert(psi: Permutation) = MatrixQ(psi.invert(this.elems))
fun permute(psi: Permutation) = MatrixQ(psi.permute(this.elems))

}

data class VectorQ(val group: GroupContext, val elems: List<ElementModQ> ) {
Expand All @@ -30,7 +28,7 @@ data class VectorQ(val group: GroupContext, val elems: List<ElementModQ> ) {

fun product(): ElementModQ {
if (elems.isEmpty()) {
group.ONE_MOD_Q
return group.ONE_MOD_Q
}
if (elems.count() == 1) {
return elems[0]
Expand All @@ -40,7 +38,7 @@ data class VectorQ(val group: GroupContext, val elems: List<ElementModQ> ) {

fun sum(): ElementModQ {
if (elems.isEmpty()) {
group.ZERO_MOD_Q
return group.ZERO_MOD_Q
}
if (elems.count() == 1) {
return elems[0]
Expand Down Expand Up @@ -84,14 +82,6 @@ data class VectorQ(val group: GroupContext, val elems: List<ElementModQ> ) {
return VectorQ(group, emptyList())
}
}

fun gPowP(vp: VectorQ): VectorP {
return vp.gPowP()
}

fun shiftPush(elem0: ElementModQ): VectorQ {
return VectorQ(group, List (this.nelems) { if (it == 0) elem0 else this.elems[it - 1] })
}
}

fun Prod(vp: VectorQ): ElementModQ {
Expand Down
20 changes: 9 additions & 11 deletions src/main/kotlin/org/cryptobiotic/mixnet/cli/RunMixnet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ class RunMixnet {
val decryptedSnsFilename = "decrypted_sns.json"
val pballotTableFilename = "pballot_table.json"

val shuffledFilename = "ShuffledBallots.bin"
val shuffledBinFilename = "ShuffledBallots.bin"
val shuffledJsonFilename = "ShuffledBallots.json"

@JvmStatic
fun main(args: Array<String>) {
Expand Down Expand Up @@ -82,7 +83,12 @@ class RunMixnet {
}
val previousConfig = result.unwrap()
width = previousConfig.width
inputBallots = mixnet.readInputBallots("$inputMixDir/$shuffledFilename", previousConfig.width)
val ballotResult = readShuffledBallotsFromFile( mixnet.group, inputMixDir!!, previousConfig.width)
if (ballotResult is Err) {
logger.error {"Error reading input ballots in $inputMixDir = $ballotResult" }
return
}
inputBallots = ballotResult.unwrap()
ballotStyles = previousConfig.ballotStyles

} else {
Expand All @@ -100,15 +106,12 @@ class RunMixnet {

val topdir = outputDir ?: publicDir
val outputDirMix = "$topdir/$mixName"
writeBallotsToFile(shuffled, "$outputDirMix/$shuffledFilename")
writeShuffledBallotsToFile(true, outputDirMix, shuffled)
writeProofOfShuffleJsonToFile(proof, "$outputDirMix/$proofFilename")

val config = MixnetConfig(mixName, mixnet.electionId.publishJson(), ballotStyles, width, noncesSeed)
writeMixnetConfigToFile(config, "$outputDirMix/$configFilename")
logger.info { "success" }

// compare with json
// writeMatrixCiphertextJsonToFile("$outputDirMix/ShuffledBallots.json", shuffled)
}
}
}
Expand All @@ -129,11 +132,6 @@ class Mixnet(egDir:String) {
RunMixnet.logger.info { "using group ${group.constants.name}" }
}

fun readInputBallots(inputBallots: String, width: Int): List<VectorCiphertext> {
val reader = BallotReader(group, width)
return reader.readFromFile(inputBallots)
}

fun runShuffleProof(
ballots: List<VectorCiphertext>,
mixName: String,
Expand Down
10 changes: 8 additions & 2 deletions src/main/kotlin/org/cryptobiotic/mixnet/cli/RunMixnetTable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,14 @@ class RunMixnetTable {
val electionInit = initResult.unwrap()
val group = consumerIn.group

val reader = BallotReader(group, config.width)
val shuffled = reader.readFromFile("$mixDir/${RunMixnet.shuffledFilename}")
val shuffledResult = readShuffledBallotsFromFile( group, mixDir, config.width)
if (shuffledResult is Err) {
logger.error {"Error reading shuffled ballots in $mixDir = $shuffledResult" }
return
}
val shuffled = shuffledResult.unwrap()
RunProofOfShuffleVerifier.logger.info { " Read ${shuffled.size} shuffled ballots" }

val encryptedSns = shuffled.map { Ciphertext(it.elems[0]) }
val encryptedStyles = shuffled.map { Ciphertext(it.elems[1]) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ class RunPaperBallotDecrypt {
val configFilename = "$mixDir/${RunMixnet.configFilename}"
val resultConfig = readMixnetConfigFromFile(configFilename)
if (resultConfig is Err) {
RunMixnet.logger.error { "Error reading MixnetConfig from $configFilename err = $resultConfig" }
return
logger.error { "Error reading MixnetConfig from $configFilename err = $resultConfig" }
throw RuntimeException("Error reading MixnetConfig from $configFilename err = $resultConfig")
}
val config = resultConfig.unwrap()
val decryptor = DecryptFromSn(publicDir, config.width, mixDir)
Expand All @@ -83,18 +83,24 @@ class RunPaperBallotDecrypt {
throw RuntimeException("failed $pballotTableResult")
}
val pballotTable = pballotTableResult.unwrap()
val errs = ErrorMessages("findAndDecrypt")

if (ballotSn.lowercase() == "all") {
pballotTable.entries.forEach { paperBallotEntry ->
decryptor.findAndDecrypt(trusteeDir, outputDir, paperBallotEntry)
decryptor.findAndDecrypt(trusteeDir, outputDir, paperBallotEntry, errs)
}

} else {
val paperBallotEntry = findPaperBallot(pballotTable, ballotSn)
if (paperBallotEntry == null) {
logger.error { "Cant find paperBallot with serial number= '${ballotSn}'" }
throw RuntimeException("Cant find paperBallot with serial number= '${ballotSn}'")
}
decryptor.findAndDecrypt(trusteeDir, outputDir, paperBallotEntry)
decryptor.findAndDecrypt(trusteeDir, outputDir, paperBallotEntry, errs)
}
if (errs.hasErrors()) {
logger.error { "decryptShuffledBallot failed errors = $errs" }
throw RuntimeException("decryptShuffledBallot failed errors = $errs")
}
}

Expand Down Expand Up @@ -127,8 +133,8 @@ class RunPaperBallotDecrypt {
) {
val consumerIn = makeConsumer(publicDir)
val group = consumerIn.group
val electionInit: ElectionInitialized
val publicKey: ElGamalPublicKey
private val electionInit: ElectionInitialized
private val publicKey: ElGamalPublicKey

init {
val initResult = consumerIn.readElectionInitialized()
Expand All @@ -140,13 +146,14 @@ class RunPaperBallotDecrypt {
publicKey = electionInit.jointPublicKey
}

fun findAndDecrypt(trusteeDir: String, outputDir: String, pballot: PballotEntry) {
fun findAndDecrypt(trusteeDir: String, outputDir: String, pballot: PballotEntry, errs: ErrorMessages) {
val pair = findShuffledBallot(publicKey, width, mixDir, pballot)
if (pair == null) {
throw RuntimeException("Cant find shuffled ballot for ${pballot}")
errs.add( "Cant find shuffled ballot for ${pballot}" )
return
}
val (dsn, ballot) = pair
decryptShuffledBallot(trusteeDir, pballot, dsn, ballot, outputDir)
decryptShuffledBallot(trusteeDir, pballot, dsn, ballot, errs, outputDir)
}

// open the mixnet ballot table and find matching K^psn, then open the Shuffled ballots and fetch that row
Expand Down Expand Up @@ -178,12 +185,15 @@ class RunPaperBallotDecrypt {
}
val ballotRow = foundBallot.shuffledRow

// TODO only open Shuffled ballots once? then open the Shuffled ballots and fetch that row
val reader = BallotReader(group, width)
val mixFile = "$mixDir/${RunMixnet.shuffledFilename}"
val shuffled = reader.readFromFile(mixFile)
val ballotResult = readShuffledBallotsFromFile( group, mixDir, width)
if (ballotResult is Err) {
logger.error {"Error reading input ballots in $mixDir = $ballotResult" }
return null
}
val shuffled = ballotResult.unwrap()

if (ballotRow < 0 || ballotRow >= shuffled.size) {
logger.error { "ballotRow $ballotRow not in bounds 0 .. ${shuffled.size} in the shuffled file $mixFile" }
logger.error { "ballotRow $ballotRow not in bounds 0 .. ${shuffled.size} in the ballot file in $mixDir" }
return null
}
return Pair(foundBallot, shuffled[ballotRow])
Expand All @@ -194,6 +204,7 @@ class RunPaperBallotDecrypt {
pballot: PballotEntry,
dsn: DecryptedSn,
ballot: VectorCiphertext,
errs: ErrorMessages,
outputDir: String,
) {
val group = consumerIn.group
Expand All @@ -205,12 +216,9 @@ class RunPaperBallotDecrypt {
val manifest = consumerIn.makeManifest(electionInit.config.manifestBytes)
val eballot = rehydrate(manifest, pballot.sn.toString(), electionInit.extendedBaseHash, dsn.ballotStyleIdx, ballot)

val errs = ErrorMessages("runPaperBallotDecrypt")
try {
val decryptedBallot = decryptor.decrypt(eballot, errs)
val decryptedBallot = decryptor.decrypt(eballot, errs.nested("Ballot id='${eballot.ballotId}"))
if (errs.hasErrors()) {
logger.error { "decryptShuffledBallot failed errors = $errs" }
println("decryptShuffledBallot failed errors = $errs")
return
}
requireNotNull(decryptedBallot)
Expand All @@ -221,7 +229,6 @@ class RunPaperBallotDecrypt {
logger.info { "decrypt and write shuffled ballot sn=${pballot.sn} to output directory $outputDir " }
} catch (t: Throwable) {
errs.add("Exception= ${t.message} ${t.stackTraceToString()}")
logger.error { errs }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,12 @@ import org.cryptobiotic.eg.publish.makeConsumer
import org.cryptobiotic.maths.VectorCiphertext
import org.cryptobiotic.mixnet.ProofOfShuffle
import org.cryptobiotic.mixnet.cli.RunMixnet.Companion.proofFilename
import org.cryptobiotic.mixnet.cli.RunMixnet.Companion.shuffledFilename
import org.cryptobiotic.mixnet.runVerify
import org.cryptobiotic.util.ErrorMessages
import org.cryptobiotic.util.Stopwatch
import org.cryptobiotic.mixnet.writer.BallotReader
import org.cryptobiotic.mixnet.writer.readMixnetConfigFromFile
import org.cryptobiotic.mixnet.writer.readProofOfShuffleJsonFromFile
import org.cryptobiotic.mixnet.writer.readShuffledBallotsFromFile

class RunProofOfShuffleVerifier {

Expand Down Expand Up @@ -76,7 +75,12 @@ class RunProofOfShuffleVerifier {

val ballots: List<VectorCiphertext>
if (inputMixDir != null) {
ballots = verifier.readInputBallots("$inputMixDir/$shuffledFilename", config.width)
val ballotResult = readShuffledBallotsFromFile( verifier.group, inputMixDir!!, config.width)
if (ballotResult is Err) {
logger.error {"Error reading input ballots in $inputMixDir = $ballotResult" }
return
}
ballots = ballotResult.unwrap()
logger.info { " Read ${ballots.size} input ballots" }

} else {
Expand All @@ -89,7 +93,12 @@ class RunProofOfShuffleVerifier {
println("readEncryptedBallots ${ciphertexts.size} hash(ciphertexts) ${hashFunction(mixnet.electionId.bytes, ciphertexts)}")
}

val shuffled: List<VectorCiphertext> = verifier.readInputBallots("$outputMixDir/$shuffledFilename", config.width)
val shuffledResult = readShuffledBallotsFromFile( verifier.group, outputMixDir, config.width)
if (shuffledResult is Err) {
logger.error {"Error reading shuffled ballots in $outputMixDir = $shuffledResult" }
return
}
val shuffled = shuffledResult.unwrap()
logger.info { " Read ${shuffled.size} shuffled ballots" }

if (ballots.size != shuffled.size) {
Expand Down Expand Up @@ -124,11 +133,6 @@ class Verifier(egDir:String) {
publicKey = init.jointPublicKey
}

fun readInputBallots(inputBallots: String, width: Int): List<VectorCiphertext> {
val reader = BallotReader(group, width)
return reader.readFromFile(inputBallots)
}

fun runVerifier(
ballots: List<VectorCiphertext>,
shuffled: List<VectorCiphertext>,
Expand Down
Loading

0 comments on commit 52b6130

Please sign in to comment.