Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanup Group and GroupCommon #455

Merged
merged 2 commits into from
Feb 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading