Skip to content

Commit

Permalink
PreEncryption, ContestData spec 2.0
Browse files Browse the repository at this point in the history
change labels to index
change spec equation references
  • Loading branch information
JohnLCaron committed Aug 23, 2023
1 parent d879b36 commit be53e27
Show file tree
Hide file tree
Showing 15 changed files with 143 additions and 118 deletions.
32 changes: 18 additions & 14 deletions egklib/src/commonMain/kotlin/electionguard/ballot/ContestData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ data class ContestData(
publicKey: ElGamalPublicKey, // aka K
extendedBaseHash: UInt256, // aka He
contestId: String, // aka Λ
contestIndex: Int, // ind_c(Λ)
ballotNonce: UInt256,
votesAllowed: Int): HashedElGamalCiphertext {

Expand Down Expand Up @@ -120,16 +121,17 @@ data class ContestData(
if (debug) println("encodedData = $trialContestData")
if (debug) println(" trialSizes = $trialSizes")

return trialContestDataBA.encryptContestData(publicKey, extendedBaseHash, contestId, ballotNonce)
return trialContestDataBA.encryptContestData(publicKey, extendedBaseHash, contestId, contestIndex, ballotNonce)
}

fun ByteArray.encryptContestData(
publicKey: ElGamalPublicKey, // aka K
extendedBaseHash: UInt256, // aka He
contestId: String, // aka Λ
contestIndex: Int, // ind_c(Λ)
ballotNonce: UInt256): HashedElGamalCiphertext {

// D = D_1 ∥ D_2 ∥ · · · ∥ D_bD ; spec 1.9 eq (46)
// D = D_1 ∥ D_2 ∥ · · · ∥ D_bD ; eq (49)
val messageBlocks: List<UInt256> =
this.toList()
.chunked(32) { block ->
Expand All @@ -141,23 +143,24 @@ data class ContestData(

val group = compatibleContextOrFail(publicKey.key)

// ξ = H(HE ; 20, ξB , Λ, ”contest_data”) ; spec 1.9 eq (47)
val contestDataNonce = hashFunction(extendedBaseHash.bytes, 0x20.toByte(), ballotNonce, contestId, contestDataLabel)
// ξ = H(HE ; 0x20, ξB , indc (Λ), “contest data”) (eq 50)
val contestDataNonce = hashFunction(extendedBaseHash.bytes, 0x20.toByte(), ballotNonce, contestIndex, contestDataLabel)

// ElectionGuard spec: (α, β) = (g^ξ mod p, K^ξ mod p); by encrypting a zero, we achieve exactly this
val (alpha, beta) = 0.encrypt(publicKey, contestDataNonce.toElementModQ(group))
// k = H(HE ; 22, K, α, β). ; spec 1.9 eq (48)
// k = H(HE ; 0x22, K, α, β) ; eq 51
val kdfKey = hashFunction(extendedBaseHash.bytes, 0x22.toByte(), publicKey.key, alpha, beta)

// TODO check
// context = b(”contest_data”) ∥ b(Λ).
val context = "$contestDataLabel$contestId"
val kdf = KDF(kdfKey, label, context, this.size * 8) // TODO is this spec 1.9 eq(49) ??
val kdf = KDF(kdfKey, label, context, this.size * 8) // TODO is this eq(52) ??

val k0 = kdf[0]
val c0 = alpha.byteArray() // (50)
val c0 = alpha.byteArray() // (53)
val encryptedBlocks = messageBlocks.mapIndexed { i, p -> (p xor kdf[i + 1]).bytes }.toTypedArray()
val c1 = concatByteArrays(*encryptedBlocks) // (51)
val c2 = (c0 + c1).hmacSha256(k0) // ; spec 1.9 eq (52) TODO can we use hmacFunction() ??
val c1 = concatByteArrays(*encryptedBlocks) // (54)
val c2 = (c0 + c1).hmacSha256(k0) // ; eq (55) TODO can we use hmacFunction() ??

return HashedElGamalCiphertext(alpha, c1, c2, this.size)
}
Expand Down Expand Up @@ -204,10 +207,11 @@ fun HashedElGamalCiphertext.decryptWithNonceToContestData(
publicKey: ElGamalPublicKey, // aka K
extendedBaseHash: UInt256, // aka He
contestId: String, // aka Λ
contestIndex: Int,
ballotNonce: UInt256) : Result<ContestData, String> {

val group = compatibleContextOrFail(publicKey.key)
val contestDataNonce = hashFunction(extendedBaseHash.bytes, 0x20.toByte(), ballotNonce, contestId, contestDataLabel)
val contestDataNonce = hashFunction(extendedBaseHash.bytes, 0x20.toByte(), ballotNonce, contestIndex, contestDataLabel)
val (alpha, beta) = 0.encrypt(publicKey, contestDataNonce.toElementModQ(group))
val ba: ByteArray = this.decryptContestData(publicKey, extendedBaseHash, contestId, alpha, beta) ?: return Err( "decryptWithNonceToContestData failed")
val proto = electionguard.protogen.ContestData.decodeFromByteArray(ba)
Expand All @@ -227,12 +231,12 @@ private fun HashedElGamalCiphertext.decryptContestData(
alpha: ElementModP,
beta: ElementModP): ByteArray? {

// k = H(HE ; 22, K, α, β). (48)
// k = H(HE ; 22, K, α, β). (51)
val kdfKey = hashFunction(extendedBaseHash.bytes, 0x22.toByte(), publicKey.key, alpha, beta)

// context = b(”contest_data”) ∥ b(Λ).
val context = "$contestDataLabel$contestId"
val kdf = KDF(kdfKey, label, context, numBytes * 8) // TODO is this (87, 88) ??
val kdf = KDF(kdfKey, label, context, numBytes * 8) // TODO check this (86, 87) ??
val k0 = kdf[0]

val expectedHmac = (c0.byteArray() + c1).hmacSha256(k0) // TODO use hmacFunction() ?
Expand All @@ -242,9 +246,9 @@ private fun HashedElGamalCiphertext.decryptContestData(
return null
}

val ciphertextBlocks = c1.toList().chunked(32) { it.toByteArray().toUInt256() } // eq 89
val ciphertextBlocks = c1.toList().chunked(32) { it.toByteArray().toUInt256() } // eq 88
val plaintextBlocks = ciphertextBlocks.mapIndexed { i, c -> (c xor kdf[i + 1]).bytes }.toTypedArray()
val plaintext = concatByteArrays(*plaintextBlocks) // eq 90
val plaintext = concatByteArrays(*plaintextBlocks) // eq 89

return if (plaintext.size == numBytes) {
plaintext
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ class DecryptPreencryptWithNonce(
publicKey,
extendedBaseHash,
contest.contestId,
contest.sequenceOrder,
ballotNonce)

if (contestDataResult is Err) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class DecryptWithNonce(val group : GroupContext, val publicKey: ElGamalPublicKey
publicKey,
extendedBaseHash,
contest.contestId,
contest.sequenceOrder,
ballotNonce)

if (contestDataResult is Err) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@ class Encryptor(
status
)

val contestDataEncrypted = contestData.encrypt(jointPublicKey, extendedBaseHash, mcontest.contestId, ballotNonce, mcontest.votesAllowed)
val contestDataEncrypted = contestData.encrypt(jointPublicKey, extendedBaseHash, mcontest.contestId,
mcontest.sequenceOrder, ballotNonce, mcontest.votesAllowed)

return this.encryptContest(
group,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ internal data class PreBallot(

internal data class PreContest(
val contestId: String,
val preencryptionHash: UInt256, // (95)
val preencryptionHash: UInt256, // (94)
val allSelectionHashes: List<UInt256>, // nselections + limit, numerically sorted
val selectedVectors: List<PreSelectionVector>, // limit number of them, sorted by selectionHash
val votedFor: List<Boolean> // nselections, in order by sequence_order
Expand All @@ -38,7 +38,7 @@ internal data class PreContest(

internal data class PreSelectionVector(
val selectionId: String, // do not serialize
val selectionHash: ElementModQ, // ψi (93)
val selectionHash: ElementModQ, // ψi (92)
val shortCode: String,
val encryptions: List<ElGamalCiphertext>, // Ej, size = nselections, in order by sequence_order
val nonces: List<ElementModQ>, // size = nselections, in order by sequence_order, do not serialize
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ data class PreEncryptedBallot(
val ballotStyleId: String,
val primaryNonce: UInt256,
val contests: List<PreEncryptedContest>,
val confirmationCode: UInt256, // eq 96
val confirmationCode: UInt256, // eq 95
) {
fun show() {
println("\nPreEncryptedBallot $ballotId code = $confirmationCode")
Expand All @@ -31,15 +31,15 @@ data class PreEncryptedContest(
val contestId: String, // could just pass the manifest contest, in case other info is needed
val sequenceOrder: Int,
val votesAllowed: Int,
val selections: List<PreEncryptedSelection>, // nselections + limit, in sequenceOrder, eq 93,94
val selections: List<PreEncryptedSelection>, // nselections + limit, in sequenceOrder, eq 92,93
val preencryptionHash: UInt256, // eq 95
)

data class PreEncryptedSelection(
val selectionId: String, // could just pass the manifest selection, in case other info is needed
val sequenceOrder: Int, // matches the Manifest
val selectionHash: ElementModQ, // allow numerical sorting with ElementModQ, eq 93
val selectionHash: ElementModQ, // allow numerical sorting with ElementModQ, eq 92
val shortCode: String,
val selectionVector: List<ElGamalCiphertext>, // nselections, in sequenceOrder, eq 92
val selectionVector: List<ElGamalCiphertext>, // nselections, in sequenceOrder, eq 91
val selectionNonces: List<ElementModQ>, // nselections, in sequenceOrder (optional)
)
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class PreEncryptor(
• a ballot id,
• a ballot style,
• a primary nonce
The tool produces the following outputs – which can be used to construct a single pre-encrypted ballot.
The tool produces the following outputs – which can be used to construct a single pre-encrypted ballot:
• for each selection, a selection vector, a selection hash, and user-visible short code
• for each contest, votesAllowed additional null selections vectors, and a contest hash
• a confirmation code for the ballot
Expand All @@ -38,7 +38,7 @@ class PreEncryptor(
}
val contestHashes = preeContests.map { it.preencryptionHash }

// H(B) = H(HE ; 42, χ1 , χ2 , . . . , χmB , Baux ). (96)
// H(B) = H(HE ; 0x42, χ1 , χ2 , . . . , χmB , Baux ). (95)
val confirmationCode = hashFunction(extendedBaseHash.bytes, 0x42.toByte(), contestHashes, codeBaux)

return PreEncryptedBallot(
Expand All @@ -53,24 +53,26 @@ class PreEncryptor(
private fun ManifestIF.Contest.preencryptContest(primaryNonce: UInt256): PreEncryptedContest {
val preeSelections = mutableListOf<PreEncryptedSelection>()

val selections = this.selections.sortedBy { it.sequenceOrder }
val selectionLabels = selections.map { it.selectionId }
selections.map {
preeSelections.add( preencryptSelection(primaryNonce, this.contestId, it.selectionId, it.sequenceOrder, selectionLabels))
// make sure selections are sorted by sequence number
val sortedSelections = this.selections.sortedBy { it.sequenceOrder }
val sortedSelectionIndices = sortedSelections.map { it.sequenceOrder }
sortedSelections.map {
preeSelections.add( preencryptSelection(primaryNonce, this.sequenceOrder, it.selectionId, it.sequenceOrder, sortedSelectionIndices))
}

// In a contest with a selection limit of L, an additional L null vectors are hashed to obtain
// In a contest with a selection limit of L, an additional L null vectors are added
var sequence = this.selections.size
for (nullVectorIdx in (1..this.votesAllowed)) {
preeSelections.add( preencryptSelection(primaryNonce, this.contestId, "null${nullVectorIdx}", sequence, selectionLabels))
// TODO null labels may be in manifest, see 4.2.1
preeSelections.add( preencryptSelection(primaryNonce, this.sequenceOrder, "null${nullVectorIdx}", sequence, sortedSelectionIndices))
sequence++
}

// numerically sorted selectionHashes
val selectionHashes = preeSelections.sortedBy { it.selectionHash }.map { it.selectionHash.toUInt256() }

// χl = H(HE ; 41, Λl , K, ψσ(1) , ψσ(2) , . . . , ψσ(m+L) ), (95)
val preencryptionHash = hashFunction(extendedBaseHash.bytes, 0x41.toByte(), this.contestId, publicKey, selectionHashes)
// χl = H(HE ; 0x41, indc (Λl ), K, ψσ(1) , ψσ(2) , . . . , ψσ(m+L) ) ; 94
val preencryptionHash = hashFunction(extendedBaseHash.bytes, 0x41.toByte(), this.sequenceOrder, publicKey, selectionHashes)

return PreEncryptedContest(
this.contestId,
Expand All @@ -81,30 +83,30 @@ class PreEncryptor(
)
}

// depends only on the labels.
private fun preencryptSelection(primaryNonce: UInt256, contestLabel : String, selectionId : String,
sequenceOrder: Int, selectionLabels: List<String>): PreEncryptedSelection {
// A PreEncryptedSelection for thisSelectionId being voted for
private fun preencryptSelection(primaryNonce: UInt256, contestIndex : Int, thisSelectionId : String,
thisSelectionIndex: Int, allSelectionIndices: List<Int>): PreEncryptedSelection {

val encryptionVector = mutableListOf<ElGamalCiphertext>()
val encryptionNonces = mutableListOf<ElementModQ>()
val hashElements = mutableListOf<ElementModP>()
selectionLabels.forEach{
// ξi,j,k = H(HE ; 43, ξ, Λi , λj , λk ) eq 97
val nonce = hashFunction(extendedBaseHash.bytes, 0x43.toByte(), primaryNonce, contestLabel, selectionId, it).toElementModQ(group)
val encoding = if (selectionId == it) 1.encrypt(publicKeyEG, nonce) else 0.encrypt(publicKeyEG, nonce)
allSelectionIndices.forEach{
// ξi,j,k = H(HE ; 0x43, ξ, indc (Λi ), indo (λj ), indo (λk )) eq 96
val nonce = hashFunction(extendedBaseHash.bytes, 0x43.toByte(), primaryNonce, contestIndex, thisSelectionIndex, it).toElementModQ(group)
val encoding = if (thisSelectionIndex == it) 1.encrypt(publicKeyEG, nonce) else 0.encrypt(publicKeyEG, nonce)
encryptionVector.add(encoding)
encryptionNonces.add(nonce)
hashElements.add(encoding.pad)
hashElements.add(encoding.data)
}

// here is the selection order dependency
// ψi = H(HE ; 40, K, α1 , β1 , α2 , β2 . . . , αm , βm ), (eq 93)
// ψi = H(HE ; 0x40, K, α1 , β1 , α2 , β2 . . . , αm , βm ), (eq 92)
val selectionHash = hashFunction(extendedBaseHash.bytes, 0x40.toByte(), publicKey, hashElements)

return PreEncryptedSelection(
selectionId,
sequenceOrder,
thisSelectionId,
thisSelectionIndex,
selectionHash.toElementModQ(group),
sigma(selectionHash),
encryptionVector,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ data class RecordedPreBallot(

data class RecordedPreEncryption(
val contestId: String,
val preencryptionHash: UInt256, // (95)
val preencryptionHash: UInt256, // (94)
val allSelectionHashes: List<UInt256>, // nselections + limit, numerically sorted
val selectedVectors: List<RecordedSelectionVector>, // limit number of them, sorted by selectionHash
) {
Expand All @@ -33,7 +33,7 @@ data class RecordedPreEncryption(

data class RecordedSelectionVector(
val selectionId: String, // do not serialize
val selectionHash: ElementModQ, // ψi (93)
val selectionHash: ElementModQ, // ψi (92)
val shortCode: String,
val encryptions: List<ElGamalCiphertext>, // Ej, size = nselections, in order by sequence_order
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,11 @@ class Recorder(
ContestDataStatus.normal
)

val contestDataEncrypted = contestData.encrypt(publicKeyEG, extendedBaseHash, preeContest.contestId, ballotNonce, preeContest.votesAllowed)
val contestDataEncrypted = contestData.encrypt(publicKeyEG, extendedBaseHash, preeContest.contestId,
preeContest.sequenceOrder, ballotNonce, preeContest.votesAllowed)

// we are going to substitute preencryptionHash (eq 95) instead of eq 58 when we validate
// χl = H(HE ; 23, Λl , K, α1 , β1 , α2 , β2 . . . , αm , βm ). (58)
// we are going to substitute preencryptionHash (eq 94) instead of eq 57 when we validate TODO WTF?
// χl = H(HE ; 0x23, Λl , K, α1 , β1 , α2 , β2 . . . , αm , βm ). (57)
val ciphers = mutableListOf<ElementModP>()
texts.forEach {
ciphers.add(it.pad)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,18 +115,27 @@ class VerifyDecryption(
return this.proof.validate2(publicKey.key, extendedBaseHash, this.bOverM, this.encryptedVote)
}

// Verification 10 (Correctness of decryptions of contest data)
// 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(where: String, decryptedContestData: DecryptedTallyOrBallot.DecryptedContestData): Result<Boolean, String> {
val results = mutableListOf<Result<Boolean, String>>()

// (10.A,14.A) The given value v is in the set Zq.
// (11.A,14.A) The given value v is in the set Zq.
if (!decryptedContestData.proof.r.inBounds()) {
results.add(Err(" (10.A,14.A) The value v is not in the set Zq.: '$where'"))
results.add(Err(" (11.A,14.A) The value v is not in the set Zq.: '$where'"))
}

val challengeOk = decryptedContestData.proof.validate2(publicKey.key, extendedBaseHash, decryptedContestData.beta, decryptedContestData.encryptedContestData)
if (challengeOk) {
results.add(Err(" (10.B,14.B) The challenge value is wrong: '$where'"))
results.add(Err(" (11.B,14.B) The challenge value is wrong: '$where'"))
}
return results.merge()
}
Expand Down
Loading

0 comments on commit be53e27

Please sign in to comment.