Skip to content

Commit

Permalink
Merge pull request #455 from JohnLCaron/cleanupGroup
Browse files Browse the repository at this point in the history
Cleanup Group and GroupCommon
  • Loading branch information
JohnLCaron committed Feb 18, 2024
2 parents 4e759af + f957190 commit 194b9c7
Show file tree
Hide file tree
Showing 23 changed files with 102 additions and 272 deletions.
2 changes: 1 addition & 1 deletion egklib/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ repositories {
}

group = "electionguard-kotlin-multiplatform"
version = "2.0.3-SNAPSHOT"
version = "2.0.4-SNAPSHOT"

kotlin {
jvm {
Expand Down
9 changes: 8 additions & 1 deletion egklib/src/commonMain/kotlin/electionguard/core/Base16.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package electionguard.core

import electionguard.core.Base16.fromHex
import io.github.oshai.kotlinlogging.KotlinLogging

/**
Expand Down Expand Up @@ -88,5 +89,11 @@ object Base16 {
*/
fun String.fromHexSafe(): ByteArray =
fromHex() ?: throw IllegalArgumentException("fromHexSafe invalid input= '$this'")
}

}
/**
* Converts a base-16 (hexadecimal) string to an [ElementModP]. Returns null if the number is out of
* bounds or the string is malformed.
*/
fun GroupContext.base16ToElementModP(s: String): ElementModP? =
s.fromHex()?.let { binaryToElementModP(it) }
3 changes: 1 addition & 2 deletions egklib/src/commonMain/kotlin/electionguard/core/Base64.kt
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,7 @@ object Base64 {

/**
* Encodes all bytes from the specified byte array into a newly-allocated byte array using
* the [Base64] encoding scheme. The returned byte array is of the length of the resulting
* bytes.
* the [Base64] encoding scheme. The returned byte array is of the length of the resulting bytes.
*
* @param src the byte array to encode
* @param useBase64URL use Base64URL else use Base64
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ fun List<ElGamalCiphertext>.add(other: List<ElGamalCiphertext>): List<ElGamalCip
return result
}

// TODO what happens if nonce is small enough to take the log of?
fun Int.encrypt(
keypair: ElGamalKeypair,
nonce: ElementModQ = keypair.context.randomElementModQ(minimum = 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class ElGamalPublicKey(inputKey: ElementModP) {

/**
* A wrapper around an ElementModQ that allows us to hang onto a pre-computed [negativeKey]
* (i.e., the additive inverse mod `q`). The secret key must be in [2, Q).
* (i.e., the additive inverse mod q). The secret key must be in [2, Q).
*/
class ElGamalSecretKey(val key: ElementModQ) {
init {
Expand Down
159 changes: 29 additions & 130 deletions egklib/src/commonMain/kotlin/electionguard/core/GroupCommon.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package electionguard.core

import electionguard.ballot.ElectionConstants
import electionguard.core.Base16.fromHex
import electionguard.core.Base16.toHex
import electionguard.core.Base64.fromBase64
import electionguard.core.Base64.toBase64
import io.github.oshai.kotlinlogging.KotlinLogging

private val logger = KotlinLogging.logger("GroupCommon")
Expand Down Expand Up @@ -77,15 +74,9 @@ interface GroupContext {
*/
val constants: ElectionConstants

/** Useful constant: zero mod p */
val ZERO_MOD_P: ElementModP

/** Useful constant: one mod p */
val ONE_MOD_P: ElementModP

/** Useful constant: two mod p */
val TWO_MOD_P: ElementModP

/** Useful constant: the group generator */
val G_MOD_P: ElementModP

Expand All @@ -95,9 +86,6 @@ interface GroupContext {
/** Useful constant: the group generator, squared */
val G_SQUARED_MOD_P: ElementModP

/** Useful constant: the modulus of the ElementModQ group */
val Q_MOD_P: ElementModP

/** Useful constant: zero mod q */
val ZERO_MOD_Q: ElementModQ

Expand All @@ -107,21 +95,13 @@ interface GroupContext {
/** Useful constant: two mod q */
val TWO_MOD_Q: ElementModQ

/**
* Useful constant: the maximum number of bytes to represent any element mod p when serialized
* as a `ByteArray`.
*/
/** The maximum number of bytes to represent any element mod p when serialized as a ByteArray. */
val MAX_BYTES_P: Int

/**
* Useful constant: the maximum number of bytes to represent any element mod q when serialized
* as a `ByteArray`.
*/
/** The maximum number of bytes to represent any element mod q when serialized as a ByteArray. */
val MAX_BYTES_Q: Int

/**
* Useful constant: the number of bits it takes to represent any element mod p.
*/
/** Useful constant: the number of bits it takes to represent any element mod p. */
val NUM_P_BITS: Int

/**
Expand Down Expand Up @@ -174,9 +154,6 @@ interface GroupContext {
/** Converts a long to an ElementModQ, with optimizations when possible for small integers */
fun uLongToElementModQ(i: ULong): ElementModQ

/** Converts an integer to an ElementModP, with optimizations when possible for small integers */
fun uIntToElementModP(i: UInt): 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.
Expand All @@ -185,23 +162,39 @@ interface 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.
* multiplication operation for large numbers of inputs by potentially reusing scratch-space memory.
*/
fun Iterable<ElementModP>.multP(): ElementModP

/** Computes G^e mod p, where G is our generator */
fun gPowP(e: ElementModQ): ElementModP
fun gPowP(exp: ElementModQ): ElementModP

/**
* Given an element x for which there exists an e, such that g^e = x, this will find e,
* so long as e is less than [maxResult], which if unspecified defaults to a platform-specific
* value designed not to consume too much memory (perhaps 10 million). This will consume O(e)
* time, the first time, after which the results are memoized for all values between 0 and e,
* for better future performance.
* If the result is not found, null is returned.
*/
fun dLogG(p: ElementModP, maxResult: Int = - 1): Int?

/**
* 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.
* @throws IllegalArgumentException if the minimum is negative
*/
fun randomElementModQ(minimum: Int = 0) =
binaryToElementModQsafe(randomBytes(MAX_BYTES_Q), minimum)

/**
* Returns a random number in [minimum, P), where minimum defaults to zero. Promises to use a
* "secure" random number generator, such that the results are suitable for use as cryptographic keys.
* @throws IllegalArgumentException if the minimum is negative
*/
fun randomElementModP(minimum: Int = 0) =
binaryToElementModPsafe(randomBytes(MAX_BYTES_P), minimum)

/** debugging operation counts. TODO sidechannel attack? */
fun getAndClearOpCounts(): Map<String, Int>
}
Expand All @@ -222,17 +215,6 @@ interface Element {
*/
fun inBounds(): Boolean

/**
* Normal computations should ensure that every [Element] is in the modular bounds defined by
* the group, but deserialization of hostile inputs or buggy code might not preserve this
* property, so it's valuable to have a way to check. This method allows anything in [1, N)
* where N is the group modulus.
*/
fun inBoundsNoZero(): Boolean

/** Checks whether this element is zero. */
fun isZero(): Boolean

/** Converts from any [Element] to a big-endian [ByteArray] representation. */
fun byteArray(): ByteArray

Expand All @@ -252,9 +234,6 @@ interface ElementModQ : Element, Comparable<ElementModQ> {
/** Computes the additive inverse */
operator fun unaryMinus(): ElementModQ

/** Computes b^e mod q. LOOK seems to be not ever used */
infix fun powQ(e: ElementModQ): ElementModQ

/** Finds the multiplicative inverse */
fun multInv(): ElementModQ

Expand All @@ -263,6 +242,9 @@ interface ElementModQ : Element, Comparable<ElementModQ> {

/** Allows elements to be compared (<, >, <=, etc.) using the usual arithmetic operators. */
override operator fun compareTo(other: ElementModQ): Int

/** Checks whether this element is zero. */
fun isZero(): Boolean
}

interface ElementModP : Element, Comparable<ElementModP> {
Expand All @@ -273,7 +255,7 @@ interface ElementModP : Element, Comparable<ElementModP> {
fun isValidResidue(): Boolean

/** Computes b^e mod p */
infix fun powP(e: ElementModQ): ElementModP
infix fun powP(exp: ElementModQ): ElementModP

/** Modular multiplication */
operator fun times(other: ElementModP): ElementModP
Expand All @@ -299,7 +281,7 @@ interface ElementModP : Element, Comparable<ElementModP> {

/** Short version of the String for readability */
fun toStringShort(): String {
val s = base16()
val s = toHex()
val len = s.length
return "${s.substring(0, 7)}...${s.substring(len-8, len)}"
}
Expand All @@ -320,68 +302,6 @@ interface MontgomeryElementModP {
val context: GroupContext
}

/**
* Converts a base-16 (hexadecimal) string to an [ElementModP]. Returns null if the number is out of
* bounds or the string is malformed.
*/
fun GroupContext.base16ToElementModP(s: String): ElementModP? =
s.fromHex()?.let { binaryToElementModP(it) }

/**
* Converts a base-16 (hexadecimal) string to an [ElementModQ]. Returns null if the number is out of
* bounds or the string is malformed.
*/
fun GroupContext.base16ToElementModQ(s: String): ElementModQ? =
s.fromHex()?.let { binaryToElementModQ(it) }

/**
* Converts a base-16 (hexadecimal) string to an [ElementModP]. Guarantees the result is in [0, P),
* by computing the result mod P.
*/
fun GroupContext.base16ToElementModPsafe(s: String): ElementModP =
s.fromHex()?.let { binaryToElementModPsafe(it) } ?: ZERO_MOD_P

/**
* Converts a base-16 (hexadecimal) string to an [ElementModQ]. Guarantees the result is in [0, Q),
* by computing the result mod Q.
*/
fun GroupContext.base16ToElementModQsafe(s: String): ElementModQ =
s.fromHex()?.let { binaryToElementModQsafe(it) } ?: ZERO_MOD_Q

/**
* Converts a base-64 string to an [ElementModP]. Returns null if the number is out of bounds or the
* string is malformed.
*/
fun GroupContext.base64ToElementModP(s: String): ElementModP? =
s.fromBase64()?.let { binaryToElementModP(it) }

/**
* Converts a base-64 string to an [ElementModQ]. Returns null if the number is out of bounds or the
* string is malformed.
*/
fun GroupContext.base64ToElementModQ(s: String): ElementModQ? =
s.fromBase64()?.let { binaryToElementModQ(it) }

/**
* Converts a base-64 string to an [ElementModP]. Guarantees the result is in [0, P), by computing
* the result mod P.
*/
fun GroupContext.base64ToElementModPsafe(s: String): ElementModP =
s.fromBase64()?.let { binaryToElementModPsafe(it) } ?: ZERO_MOD_P

/**
* Converts a base-64 string to an [ElementModQ]. Guarantees the result is in [0, Q), by computing
* the result mod Q.
*/
fun GroupContext.base64ToElementModQsafe(s: String): ElementModQ =
s.fromBase64()?.let { binaryToElementModQsafe(it) } ?: ZERO_MOD_Q

/** Converts from any [Element] to a base64 string representation. */
fun Element.base64(): String = byteArray().toBase64()

/** Converts from any [Element] to a base16 (hexadecimal) string representation. */
fun Element.base16(): String = byteArray().toHex()

/** Converts an integer to an ElementModQ, with optimizations when possible for small integers */
fun Int.toElementModQ(ctx: GroupContext) =
when {
Expand All @@ -400,28 +320,6 @@ fun Long.toElementModQ(ctx: GroupContext) =
else -> ctx.uLongToElementModQ(this.toULong())
}

/**
* 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.
*
* @throws IllegalArgumentException if the minimum is negative
*/
fun GroupContext.randomElementModQ(minimum: Int = 0) =
binaryToElementModQsafe(randomBytes(MAX_BYTES_Q), minimum)

/**
* We often want to raise g to small powers, for which we've conveniently pre-computed the answers.
* This function will fall back to use [GroupContext.gPowP] if the input isn't precomputed.
*/
fun GroupContext.gPowPSmall(e: Int) =
when {
e == 0 -> ONE_MOD_P
e == 1 -> G_MOD_P
e == 2 -> G_SQUARED_MOD_P
e < 0 -> throw ArithmeticException("not defined for negative values")
else -> gPowP(e.toElementModQ(this))
}

/**
* Verifies that every element has a compatible [GroupContext] and returns the first context.
*
Expand Down Expand Up @@ -462,7 +360,7 @@ fun ElementModP.dLogG(maxResult: Int = -1): Int? = context.dLogG(this, maxResult
* `acceleration` parameter, to specify the speed versus memory tradeoff for subsequent computation.
* See [PowRadixOption] for details. Note that this function can return `null`, which indicates that
* the [ElectionConstants] were incompatible with this particular library.
*/
*
fun ElectionConstants.toGroupContext(
acceleration: PowRadixOption = PowRadixOption.LOW_MEMORY_USE
) : GroupContext? {
Expand All @@ -481,6 +379,7 @@ fun ElectionConstants.toGroupContext(
}
}
}
*/

/**
* Computes the sum of the given elements, mod q; this can be faster than using the addition
Expand Down
3 changes: 1 addition & 2 deletions egklib/src/commonMain/kotlin/electionguard/core/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,7 @@ fun <T : Any> List<T?>.noNullValuesOrNull(): List<T>? {

/**
* Normally, Kotlin's `Enum.valueOf` or [enumValueOf] method will throw an exception for an invalid
* input. This method will instead return `null` if the string doesn't map to a valid value of the
* enum.
* input. This method will instead return `null` if the string doesn't map to a valid value of the enum.
*/
inline fun <reified T : Enum<T>> safeEnumValueOf(name: String?): T? {
if (name == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ fun GuardianJsonR.import(group: GroupContext, errs : ErrorMessages) : GuardianR?
fun GuardianR.convert(group: GroupContext) = Guardian(
this.name,
this.i,
// Schnorr proof is missing - fake it
// Schnorr proof is missing - fake it TODO WTF?
this.coefficient_commitments.map { SchnorrProof(it, group.ONE_MOD_Q, group.TWO_MOD_Q) }
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ class VerifyTally(
if (selection.encryptedVote.data != accum.data) {
errs.add(" 8.B Ballot Aggregation does not match: $key")
}
} else {
} /* 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")
}
}
} */
}
}

Expand Down
Loading

0 comments on commit 194b9c7

Please sign in to comment.