Skip to content

Commit

Permalink
Add ElGamalCiphertext, PublicKey to hashFunction().
Browse files Browse the repository at this point in the history
Add Github publishing to build.
  • Loading branch information
JohnLCaron committed Dec 24, 2023
1 parent ed61620 commit 191257b
Show file tree
Hide file tree
Showing 18 changed files with 137 additions and 39 deletions.
20 changes: 20 additions & 0 deletions egklib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
kotlin("multiplatform")
alias(libs.plugins.serialization)
application
id("maven-publish")
}

repositories {
Expand Down Expand Up @@ -99,4 +100,23 @@ tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>()
.configureEach { kotlinOptions.freeCompilerArgs += "-opt-in=kotlin.RequiresOptIn" }
dependencies {
implementation(kotlin("stdlib-jdk8"))
}

// publish github package
publishing {
repositories {
maven {
name = "GitHubPackages"
url = uri("https://maven.pkg.github.com/votingworks/electionguard-kotlin-multiplatform")
credentials {
username = project.findProperty("github.user") as String? ?: System.getenv("GITHUB_USER")
password = project.findProperty("github.key") as String? ?: System.getenv("GITHUB_TOKEN")
}
}
}
publications {
register<MavenPublication>("gpr") {
from(components["java"])
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ fun ByteArray.encryptContestData(
// 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 ; 0x22, K, α, β) ; (spec 2.0, eq 51)
val kdfKey = hashFunction(extendedBaseHash.bytes, 0x22.toByte(), publicKey.key, alpha, beta)
val kdfKey = hashFunction(extendedBaseHash.bytes, 0x22.toByte(), publicKey, alpha, beta)

// TODO check
// context = b(”contest_data”) ∥ b(Λ).
Expand Down Expand Up @@ -210,7 +210,7 @@ fun HashedElGamalCiphertext.decryptContestData(
beta: ElementModP): ByteArray? {

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

// context = b(”contest_data”) ∥ b(Λ).
val context = "${ContestData.contestDataLabel}$contestId"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ fun electionBaseHash(Hp: UInt256, HM: UInt256, n : Int, k : Int) : UInt256 {
return hashFunction(
Hp.bytes,
0x02.toByte(),
HM.bytes,
HM,
n,
k,
)
Expand Down
1 change: 0 additions & 1 deletion egklib/src/commonMain/kotlin/electionguard/core/Base64.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package electionguard.core

import electionguard.core.Base64.fromBase64
import kotlin.math.min
import io.github.oshai.kotlinlogging.KotlinLogging

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ internal fun ElGamalCiphertext.makeChaumPedersenWithNonces(
val abList: List<ElementModP> = aList.zip(bList).flatMap { listOf(it.first, it.second) }

// c = H(HE ; 0x21, K, α, β, a0 , b0 , a1 , b1 , . . . , aR , bR ) ; eq 46
val c = hashFunction(extendedBaseHash.bytes, 0x21.toByte(), publicKey.key, alpha, beta, abList).toElementModQ(group)
val c = hashFunction(extendedBaseHash.bytes, 0x21.toByte(), publicKey, alpha, beta, abList).toElementModQ(group)

// cl = (c − (c0 + · · · + cℓ−1 + cℓ+1 + · · · + cR )) mod q ; eq 47
val cl = c -
Expand Down Expand Up @@ -166,7 +166,7 @@ fun ChaumPedersenRangeProofKnownNonce.verify(
val abList = expandedProofs.flatMap { listOf(it.a, it.b) }

// c = H(HE ; 0x21, K, α, β, a0 , b0 , a1 , b1 , . . . , aR , bR ) ; eq 5.3
val c = hashFunction(extendedBaseHash.bytes, 0x21.toByte(), publicKey.key, alpha, beta, abList).toElementModQ(group)
val c = hashFunction(extendedBaseHash.bytes, 0x21.toByte(), publicKey, alpha, beta, abList).toElementModQ(group)

// The equation c = (c0 + c1 + · · · + cR ) mod q is satisfied ; eq 5.D
val cSum = this.proofs.fold(group.ZERO_MOD_Q) { a, b -> a + b.c }
Expand Down
41 changes: 20 additions & 21 deletions egklib/src/commonMain/kotlin/electionguard/core/GroupCommon.kt
Original file line number Diff line number Diff line change
Expand Up @@ -305,16 +305,19 @@ interface ElementModP : Element, Comparable<ElementModP> {
}

/**
* Computes the sum of the given elements, mod q; this can be faster than using the addition
* operation for large numbers of inputs by potentially reusing scratch-space memory.
* Montgomery form of an [ElementModP]. Note the very limited set of methods. Convert back
* a regular [ElementModP] for anything other than multiplication.
*/
fun GroupContext.addQ(vararg elements: ElementModQ) = elements.asIterable().addQ()
interface MontgomeryElementModP {
/** Modular multiplication */
operator fun times(other: MontgomeryElementModP): MontgomeryElementModP

/**
* Computes the product of the given elements, mod p; this can be faster than using the
* multiplication operation for large numbers of inputs by potentially reusing scratch-space memory.
*/
fun GroupContext.multP(vararg elements: ElementModP) = elements.asIterable().multP()
/** Convert back to the normal [ElementModP] representation. */
fun toElementModP(): ElementModP

/** Every [MontgomeryElementModP] knows the [GroupContext] that was used to create it. */
val context: GroupContext
}

/**
* Converts a base-16 (hexadecimal) string to an [ElementModP]. Returns null if the number is out of
Expand Down Expand Up @@ -398,8 +401,7 @@ fun Long.toElementModQ(ctx: GroupContext) =

/**
* Returns a random number in [minimum, Q), where minimum defaults to zero. Promises to use a
* "secure" random number generator, such that the results are suitable for use as cryptographic
* keys.
* "secure" random number generator, such that the results are suitable for use as cryptographic keys.
*
* @throws IllegalArgumentException if the minimum is negative
*/
Expand Down Expand Up @@ -480,16 +482,13 @@ fun ElectionConstants.toGroupContext(
}

/**
* Montgomery form of an [ElementModP]. Note the very limited set of methods. Convert back
* a regular [ElementModP] for anything other than multiplication.
* Computes the sum of the given elements, mod q; this can be faster than using the addition
* operation for large numbers of inputs by potentially reusing scratch-space memory.
*/
interface MontgomeryElementModP {
/** Modular multiplication */
operator fun times(other: MontgomeryElementModP): MontgomeryElementModP

/** Convert back to the normal [ElementModP] representation. */
fun toElementModP(): ElementModP
fun GroupContext.addQ(vararg elements: ElementModQ) = elements.asIterable().addQ()

/** Every [MontgomeryElementModP] knows the [GroupContext] that was used to create it. */
val context: GroupContext
}
/**
* Computes the product of the given elements, mod p; this can be faster than using the
* multiplication operation for large numbers of inputs by potentially reusing scratch-space memory.
*/
fun GroupContext.multP(vararg elements: ElementModP) = elements.asIterable().multP()
4 changes: 2 additions & 2 deletions egklib/src/commonMain/kotlin/electionguard/core/Hash.kt
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ fun HmacSha256.addToHash(element : Any, show : Boolean = false) {
is UInt256 -> element.bytes
is Element -> element.byteArray()
is String -> element.encodeToByteArray() // LOOK not adding size
// is Short -> ByteArray(2) { if (it == 0) (element / 256).toByte() else (element % 256).toByte() }
// is UShort -> ByteArray(2) { if (it == 0) (element / U256).toByte() else (element % U256).toByte() }
is ElGamalCiphertext -> element.pad.byteArray() + element.data.byteArray()
is ElGamalPublicKey -> element.key.byteArray()
is Int -> intToByteArray(element)
else -> throw IllegalArgumentException("unknown type in hashElements: ${element::class}")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ fun ByteArray.encryptToHashedElGamal(
// ElectionGuard spec: (α, β) = (g^ξ mod p, K^ξ mod p); by encrypting a zero, we achieve exactly this
val (alpha, beta) = 0.encrypt(publicKey, nonce)
// k = H(HE ; 0x22, K, C0 , β) eq 51: secret key since beta is secret since nonce is secret.
val kdfKey = hashFunction(extendedBaseHash.bytes, separator, publicKey.key, alpha, beta)
val kdfKey = hashFunction(extendedBaseHash.bytes, separator, publicKey, alpha, beta)

// ki = HMAC(k, b(i, 4) ∥ Label ∥ 0x00 ∥ Context ∥ b((bD + 1) · 256, 4)) // TODO implementation correct?
val kdf = KDF(kdfKey, label, context, this.size * 8)
Expand All @@ -137,7 +137,7 @@ fun HashedElGamalCiphertext.decryptToByteArray(
): ByteArray? {

// has to match encryptToHashedElGamal()
val kdfKey = hashFunction(extendedBaseHash.bytes, separator, publicKey.key, alpha, beta)
val kdfKey = hashFunction(extendedBaseHash.bytes, separator, publicKey, alpha, beta)
val kdf = KDF(kdfKey, label, context, numBytes * 8) // (86, 87)
val k0 = kdf[0]
val expectedHmac = (c0.byteArray() + c1).hmacSha256(k0)
Expand Down
2 changes: 1 addition & 1 deletion egklib/src/commonMain/kotlin/electionguard/core/Schnorr.kt
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ fun ElGamalKeypair.schnorrProof(
): SchnorrProof {
val context = compatibleContextOrFail(publicKey.key, secretKey.key, nonce)
val h = context.gPowP(nonce) // h = g ^ u; spec 2.0.0, eq 11
val c = hashFunction(context.constants.hp.bytes, 0x10.toByte(), guardianXCoord, coeff, publicKey.key, h).toElementModQ(context) // eq 12
val c = hashFunction(context.constants.hp.bytes, 0x10.toByte(), guardianXCoord, coeff, publicKey, h).toElementModQ(context) // eq 12
val v = nonce - secretKey.key * c

return SchnorrProof(publicKey.key, c, v)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ class DecryptorDoerre(
val b: ElementModP = with(group) { cresults.shares.values.map { it.b }.multP() }

// "joint challenge" c = H(HE ; 0x31, K, C0 , C1 , C2 , a, b, β) ; 2.0, eq 81
cresults.collectiveChallenge = hashFunction(extendedBaseHash.bytes, 0x31.toByte(), jointPublicKey.key,
cresults.collectiveChallenge = hashFunction(extendedBaseHash.bytes, 0x31.toByte(), jointPublicKey,
cresults.hashedCiphertext.c0,
cresults.hashedCiphertext.c1.toHex(),
cresults.hashedCiphertext.c2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ fun PlaintextBallot.Contest.encryptContest(
ciphers.add(it.pad)
ciphers.add(it.data)
}
val contestHash = hashFunction(extendedBaseHash.bytes, 0x23.toByte(), this.sequenceOrder, jointPublicKey.key, ciphers)
val contestHash = hashFunction(extendedBaseHash.bytes, 0x23.toByte(), this.sequenceOrder, jointPublicKey, ciphers)

return CiphertextBallot.Contest(
this.contestId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ open class KeyCeremonyTrustee(

override fun xCoordinate(): Int = xCoordinate

override fun guardianPublicKey(): ElementModP = polynomial.coefficientCommitments[0]
override fun guardianPublicKey(): ElementModP = polynomial.coefficientCommitments[0] // TODO make sure this is accelerated

override fun coefficientCommitments(): List<ElementModP> = polynomial.coefficientCommitments

Expand Down Expand Up @@ -214,7 +214,7 @@ open class KeyCeremonyTrustee(
// by encrypting a zero, we achieve exactly this
val (alpha, beta) = 0.encrypt(K_l, nonce)
// ki,ℓ = H(HP ; 0x11, i, ℓ, Kℓ , αi,ℓ , βi,ℓ ) ; eq 15 "secret key"
val kil = hashFunction(hp, 0x11.toByte(), i, l, K_l.key, alpha, beta).bytes
val kil = hashFunction(hp, 0x11.toByte(), i, l, K_l, alpha, beta).bytes

// footnote 27
// This key derivation uses the KDF in counter mode from SP 800-108r1. The second input to HMAC contains
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import electionguard.core.*
class PreEncryptor(
val group: GroupContext,
val manifest: ManifestIF,
val publicKey: ElementModP,
val publicKey: ElementModP, // TODO make sure this is accellerated
val extendedBaseHash: UInt256,
val sigma : (UInt256) -> String, // hash trimming function Ω
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ class VerifyEncryptedBallots(
ciphers.add(it.data)
}
val contestHash =
hashFunction(extendedBaseHash.bytes, 0x23.toByte(), contest.sequenceOrder, jointPublicKey.key, ciphers)
hashFunction(extendedBaseHash.bytes, 0x23.toByte(), contest.sequenceOrder, jointPublicKey, ciphers)
if (contestHash != contest.contestHash) {
errs.add(" 7.A. Incorrect contest hash")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ fun VerifyEncryptedBallots.verifyPreencryptedCode(ballot: EncryptedBallot,
val cv = contest.preEncryption
for (sv in cv.selectedVectors) {
val hashVector: List<ElementModP> = sv.encryptions.map { listOf(it.pad, it.data) }.flatten()
val selectionHash = hashFunction(extendedBaseHash.bytes, 0x40.toByte(), jointPublicKey.key, hashVector)
val selectionHash = hashFunction(extendedBaseHash.bytes, 0x40.toByte(), jointPublicKey, hashVector)
if (selectionHash != sv.selectionHash) {
errs.add(" 17.A. Incorrect selectionHash for selection shortCode=${sv.shortCode} contest=${contest.contestId} ballot='${ballot.ballotId}' ")
}
Expand Down
20 changes: 20 additions & 0 deletions egklib/src/commonTest/kotlin/electionguard/core/HashTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,24 @@ class HashTest {
assertEquals(h1, h2)
}
}

@Test
fun testCiphertext() {
val group = productionGroup()
val keypair = elGamalKeyPairFromRandom(group)
val ciphertext = 42.encrypt(keypair)
val h1 = hashFunction("hay1".encodeToByteArray(), 0x42, ciphertext)
val h2 = hashFunction("hay1".encodeToByteArray(), 0x42, ciphertext.pad, ciphertext.data)
assertEquals(h1, h2)
}

@Test
fun testPublicKey() {
val group = productionGroup()
val keypair = elGamalKeyPairFromRandom(group)
val h1 = hashFunction("hay2".encodeToByteArray(), 0x422, keypair.publicKey)
val h2 = hashFunction("hay2".encodeToByteArray(), 0x422, keypair.publicKey.key)
assertEquals(h1, h2)
}

}
32 changes: 32 additions & 0 deletions egklib/src/jvmTest/kotlin/electionguard/core/TestRandom.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package electionguard.core

import org.junit.jupiter.api.Test
import java.security.SecureRandom
import java.security.Security
import java.util.*

class TestRandom {

@Test
fun testRandom() {
val rng = Random()
println("Random $rng")
}

@Test
fun testSecureRandom() {
val rng = SecureRandom.getInstanceStrong()
println("SecureRandom.getInstanceStrong")
println(" algo=${rng.algorithm}")
println(" params=${rng.parameters}")
println(" provider=${rng.provider}")
}

@Test
fun showAlgorithms() {
val algorithms = Security.getAlgorithms ("SecureRandom");
println("Available algorithms")
algorithms.forEach { println(" $it") }
}

}
28 changes: 28 additions & 0 deletions egklib/src/jvmTest/kotlin/electionguard/core/TestSetMembership.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package electionguard.core

import org.junit.jupiter.api.Test
import kotlin.test.assertTrue

class TestSetMembership {
val group = productionGroup()

@Test
fun testSetMembership0() {
assertTrue(checkMembership(group.ZERO_MOD_P))
}

@Test
fun testSetMembership1() {
assertTrue(checkMembership(group.ONE_MOD_P))
}

@Test
fun testSetMembership2() {
assertTrue(checkMembership(group.TWO_MOD_P))
}

fun checkMembership(x: ElementModP): Boolean {
return x.isValidResidue()
}

}

0 comments on commit 191257b

Please sign in to comment.