Skip to content

Commit

Permalink
Merge pull request #79 from JohnLCaron/statBits
Browse files Browse the repository at this point in the history
Add statBytes parameter to randomElementModQ, randomElementModP.
  • Loading branch information
JohnLCaron authored May 8, 2024
2 parents c86d0ab + f05bbf7 commit 50d1753
Show file tree
Hide file tree
Showing 37 changed files with 392 additions and 249 deletions.
2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 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/blob/main/LICENSE.txt)
![GitHub branch checks state](https://img.shields.io/github/actions/workflow/status/JohnLCaron/egk-ec/unit-tests.yml)
![Coverage](https://img.shields.io/badge/coverage-90.4%25%20LOC%20(6991/7730)-blue)
![Coverage](https://img.shields.io/badge/coverage-90.5%25%20LOC%20(6991/7729)-blue)

# ElectionGuard-Kotlin Elliptic Curve

_last update 04/30/2024_
_last update 05/05/2024_

EGK Elliptic Curve (egk-ec) is an experimental implementation of [ElectionGuard](https://github.com/microsoft/electionguard),
[version 2.0](https://github.com/microsoft/electionguard/releases/download/v2.0/EG_Spec_2_0.pdf),
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/org/cryptobiotic/eg/core/DLogarithm.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ class DLogarithm(val base: ElementModP) {
private val dLogMapping: MutableMap<ElementModP, Int> =
ConcurrentHashMap<ElementModP, Int>()
.apply {
this[base.context.ONE_MOD_P] = 0
this[base.group.ONE_MOD_P] = 0
}

private var dLogMaxElement = base.context.ONE_MOD_P
private var dLogMaxElement = base.group.ONE_MOD_P
private var dLogMaxExponent = 0

private val mutex = Mutex()
Expand Down
6 changes: 3 additions & 3 deletions src/main/kotlin/org/cryptobiotic/eg/core/ElGamalCiphertext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,13 @@ fun List<ElGamalCiphertext>.add(other: List<ElGamalCiphertext>): List<ElGamalCip

fun Int.encrypt(
keypair: ElGamalKeypair,
nonce: ElementModQ = keypair.context.randomElementModQ(minimum = 1)
nonce: ElementModQ = keypair.context.randomElementModQ()
) = this.encrypt(keypair.publicKey, nonce)

/** Encrypt an Int. Value must be positive. */
fun Int.encrypt(
publicKey: ElGamalPublicKey,
nonce: ElementModQ = publicKey.context.randomElementModQ(minimum = 1)
nonce: ElementModQ = publicKey.context.randomElementModQ()
): ElGamalCiphertext {
val context = compatibleContextOrFail(publicKey.key, nonce)

Expand All @@ -119,7 +119,7 @@ fun Int.encrypt(
/** Encrypt a Long. Used to encrypt serial number. Value must be positive. */
fun Long.encrypt(
publicKey: ElGamalPublicKey,
nonce: ElementModQ = publicKey.context.randomElementModQ(minimum = 1)
nonce: ElementModQ = publicKey.context.randomElementModQ()
): ElGamalCiphertext {
val context = compatibleContextOrFail(publicKey.key, nonce)

Expand Down
12 changes: 6 additions & 6 deletions src/main/kotlin/org/cryptobiotic/eg/core/ElGamalKeys.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class ElGamalPublicKey(inputKey: ElementModP) {
private val dlogger = DLogarithm(inputKey)

val context: GroupContext
get() = this.key.context
get() = this.key.group

/** Helper function. `key powP e` is shorthand for `key.key powP e`. */
infix fun powP(exponent: ElementModQ): ElementModP = key powP exponent
Expand Down Expand Up @@ -59,12 +59,12 @@ class ElGamalPublicKey(inputKey: ElementModP) {
*/
class ElGamalSecretKey(val key: ElementModQ) {
init {
if (key < key.context.TWO_MOD_Q)
throw ArithmeticException("secret key must be in [2, Q) group=${key.context.javaClass.name}")
if (key < key.group.TWO_MOD_Q)
throw ArithmeticException("secret key must be in [2, Q) group=${key.group.javaClass.name}")
}
val negativeKey: ElementModQ = -key
val context: GroupContext
get() = this.key.context
get() = this.key.group

override fun equals(other: Any?) =
when (other) {
Expand All @@ -83,8 +83,8 @@ class ElGamalSecretKey(val key: ElementModQ) {
* @throws ArithmeticException if the secret key is less than two
*/
fun elGamalKeyPairFromSecret(secret: ElementModQ) =
ElGamalKeypair(ElGamalSecretKey(secret), ElGamalPublicKey(secret.context.gPowP(secret))) // eq 10
ElGamalKeypair(ElGamalSecretKey(secret), ElGamalPublicKey(secret.group.gPowP(secret))) // eq 10

/** Generates a random ElGamal keypair. */
fun elGamalKeyPairFromRandom(context: GroupContext) =
elGamalKeyPairFromSecret(context.randomElementModQ(minimum = 2))
elGamalKeyPairFromSecret(context.randomElementModQ())
38 changes: 24 additions & 14 deletions src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,16 +64,19 @@ interface GroupContext {
fun binaryToElementModQ(b: ByteArray): ElementModQ

/**
* 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.
* Returns a random number in [2, Q).
* Add "statistical distance" when generating.
* Uses a "secure" random number generator, such that the results are suitable for use as cryptographic keys.
*/
fun randomElementModQ(minimum: Int = 0) : ElementModQ // = binaryToElementModQ(randomBytes(MAX_BYTES_Q), minimum)
fun randomElementModQ(statBytes:Int = 0) : ElementModQ

/**
* Returns a random ElementModP. Promises to use a "secure" random number generator, such that
* the results are suitable for use as cryptographic keys.
* Returns a random ElementModP.
* Add "statistical distance" when generating.
* Uses a "secure" random number generator, such that the results are suitable for use as cryptographic keys.
* TODO no one actually needs this
*/
fun randomElementModP() : ElementModP
fun randomElementModP(statBytes:Int = 0) : ElementModP

/** Converts an integer to an ElementModQ, with optimizations when possible for small integers */
fun uIntToElementModQ(i: UInt): ElementModQ
Expand Down Expand Up @@ -121,12 +124,8 @@ interface GroupContext {
}

interface Element {
/**
* Every Element knows the [GroupContext] that was used to create it. This simplifies code that
* computes with elements, allowing arithmetic expressions to be written in many cases without
* needing to pass in the context.
*/
val context: GroupContext
/** The [GroupContext] it belongs to */
val group: GroupContext

/** Validates that this element is a member of the Group */
fun isValidElement(): Boolean
Expand Down Expand Up @@ -205,6 +204,17 @@ fun Long.toElementModQ(ctx: GroupContext) =
else -> ctx.uLongToElementModQ(this.toULong())
}

/**
* Throughout our bignum arithmetic, every operation needs to check that its operands are compatible
* (i.e., that we're not trying to use the test group and the production group interchangeably).
* This will verify that compatibility and throw an `ArithmeticException` if they're not.
*/
fun GroupContext.assertCompatible(other: GroupContext) {
if (!this.isCompatible(other)) {
throw ArithmeticException("incompatible group contexts")
}
}

/**
* Verifies that every element has a compatible [GroupContext] and returns the first context.
*
Expand All @@ -217,12 +227,12 @@ fun compatibleContextOrFail(vararg elements: Element): GroupContext {

if (elements.isEmpty()) throw IllegalArgumentException("no arguments")

val headContext = elements[0].context
val headContext = elements[0].group

// Note: this is comparing the head of the list to itself, which seems inefficient,
// but adding something like drop(1) in here would allocate an ArrayList and
// entail a bunch of copying overhead. What's here is almost certainly cheaper.
val allCompat = elements.all { it.context.isCompatible(headContext) }
val allCompat = elements.all { it.group.isCompatible(headContext) }

if (!allCompat) {
throw IllegalArgumentException("incompatible contexts")
Expand Down
5 changes: 1 addition & 4 deletions src/main/kotlin/org/cryptobiotic/eg/core/Nonces.kt
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,7 @@ operator fun Nonces.get(index: Int): ElementModQ = getWithHeaders(index)
fun Nonces.getWithHeaders(index: Int, vararg headers: String) =
hashFunction(internalSeed, index, *headers).toElementModQ(internalGroup)

/**
* Get an infinite (lazy) sequences of nonces. Equivalent to indexing with [Nonces.get] starting at
* 0.
*/
/** Get an infinite (lazy) sequences of nonces. Equivalent to indexing with [Nonces.get] starting at 1 */
fun Nonces.asSequence(): Sequence<ElementModQ> = generateSequence(0) { it + 1 }.map { this[it] }

/** Gets a list of the desired number (`n`) of nonces. */
Expand Down
11 changes: 0 additions & 11 deletions src/main/kotlin/org/cryptobiotic/eg/core/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,6 @@ fun fileReadBytes(filename: String): ByteArray = File(filename).readBytes()

fun fileReadText(filename: String): String = File(filename).readText()

/**
* Throughout our bignum arithmetic, every operation needs to check that its operands are compatible
* (i.e., that we're not trying to use the test group and the production group interchangeably).
* This will verify that compatibility and throw an `ArithmeticException` if they're not.
*/
fun GroupContext.assertCompatible(other: GroupContext) {
if (!this.isCompatible(other)) {
throw ArithmeticException("incompatible group contexts")
}
}

/**
* Convert an unsigned 64-bit long into a big-endian byte array of size 1, 2, 4, or 8 bytes, as
* necessary to fit the value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ package org.cryptobiotic.eg.core.ecgroup
import org.cryptobiotic.eg.core.*
import java.util.concurrent.atomic.AtomicInteger

class EcElementModP(val group: EcGroupContext, val ec: VecElementP): ElementModP {
override val context: GroupContext = group
class EcElementModP(override val group: EcGroupContext, val ec: VecElementP): ElementModP {

override fun acceleratePow(): ElementModP {
return EcElementModP(this.group, this.ec.acceleratePow())
Expand All @@ -20,7 +19,7 @@ class EcElementModP(val group: EcGroupContext, val ec: VecElementP): ElementModP
override fun div(denominator: ElementModP): ElementModP {
require (denominator is EcElementModP)
val inv = denominator.ec.inv()
return EcElementModP(group, ec.mul(inv))
return EcElementModP(this.group, ec.mul(inv))
}

/** Validate that this element is a member of the elliptic curve Group.*/
Expand All @@ -29,18 +28,18 @@ class EcElementModP(val group: EcGroupContext, val ec: VecElementP): ElementModP
}

override fun multInv(): ElementModP {
return EcElementModP(group, ec.inv())
return EcElementModP(this.group, ec.inv())
}

override fun powP(exp: ElementModQ): ElementModP {
require (exp is EcElementModQ)
group.opCounts.getOrPut("exp") { AtomicInteger(0) }.incrementAndGet()
return EcElementModP(group, ec.exp(exp.element))
this.group.opCounts.getOrPut("exp") { AtomicInteger(0) }.incrementAndGet()
return EcElementModP(this.group, ec.exp(exp.element))
}

override fun times(other: ElementModP): ElementModP {
require (other is EcElementModP)
return EcElementModP(group, ec.mul(other.ec))
return EcElementModP(this.group, ec.mul(other.ec))
}

override fun toString(): String {
Expand All @@ -57,14 +56,14 @@ class EcElementModP(val group: EcGroupContext, val ec: VecElementP): ElementModP

other as EcElementModP

if (group != other.group) return false
if (this.group != other.group) return false
if (ec != other.ec) return false

return true
}

override fun hashCode(): Int {
var result = group.hashCode()
var result = this.group.hashCode()
result = 31 * result + ec.hashCode()
return result
}
Expand Down
26 changes: 12 additions & 14 deletions src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModQ.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,35 @@ import org.cryptobiotic.eg.core.*
import org.cryptobiotic.eg.core.Base64.toBase64
import java.math.BigInteger

class EcElementModQ(val group: EcGroupContext, val element: BigInteger): ElementModQ {
// Theres not really any difference with the Integer Group ElementModQ.
class EcElementModQ(override val group: EcGroupContext, val element: BigInteger): ElementModQ {

override fun byteArray(): ByteArray = element.toByteArray().normalize(32)

private fun BigInteger.modWrap(): ElementModQ = this.mod(group.vecGroup.order).wrap()
private fun BigInteger.wrap(): ElementModQ = EcElementModQ(group, this)
private fun BigInteger.modWrap(): ElementModQ = this.mod(this@EcElementModQ.group.vecGroup.order).wrap()
private fun BigInteger.wrap(): ElementModQ = EcElementModQ(this@EcElementModQ.group, this)

override fun isZero() = element == BigInteger.ZERO
override val context: GroupContext
get() = group
override fun isValidElement() = element >= BigInteger.ZERO && element < this.group.vecGroup.order

override fun isValidElement() = element >= BigInteger.ZERO && element < group.vecGroup.order

override operator fun compareTo(other: ElementModQ): Int = element.compareTo(other.getCompat(group))
override operator fun compareTo(other: ElementModQ): Int = element.compareTo(other.getCompat(this.group))

override operator fun plus(other: ElementModQ) =
(this.element + other.getCompat(group)).modWrap()
(this.element + other.getCompat(this.group)).modWrap()

override operator fun minus(other: ElementModQ) =
this + (-other)

override operator fun times(other: ElementModQ) =
(this.element * other.getCompat(group)).modWrap()
(this.element * other.getCompat(this.group)).modWrap()

override fun multInv(): ElementModQ = element.modInverse(group.vecGroup.order).wrap()
override fun multInv(): ElementModQ = element.modInverse(this.group.vecGroup.order).wrap()

override operator fun unaryMinus(): ElementModQ =
if (this == group.ZERO_MOD_Q)
if (this == this.group.ZERO_MOD_Q)
this
else
(group.vecGroup.order - element).wrap()
(this.group.vecGroup.order - element).wrap()

override infix operator fun div(denominator: ElementModQ): ElementModQ =
this * denominator.multInv()
Expand All @@ -50,7 +48,7 @@ class EcElementModQ(val group: EcGroupContext, val element: BigInteger): Element
override fun toString() = byteArray().toBase64()

fun Element.getCompat(other: GroupContext): BigInteger {
context.assertCompatible(other)
group.assertCompatible(other)
return when (this) {
is EcElementModQ -> this.element
else -> throw NotImplementedError("should only be two kinds of elements")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.cryptobiotic.eg.core.ecgroup

import org.cryptobiotic.eg.core.*
import org.cryptobiotic.eg.core.intgroup.ProductionElementModQ
import org.cryptobiotic.eg.core.intgroup.toBigInteger
import java.math.BigInteger
import java.util.concurrent.atomic.AtomicInteger
Expand Down Expand Up @@ -31,15 +32,15 @@ class EcGroupContext(val name: String, useNative: Boolean = true): GroupContext
return EcElementModQ(this, BigInteger(1, b))
}

override fun randomElementModQ(minimum: Int) : ElementModQ {
val b = randomBytes(MAX_BYTES_Q)
val bigMinimum = if (minimum <= 0) BigInteger.ZERO else minimum.toBigInteger()
/** Returns a random number in [2, Q). */
override fun randomElementModQ(statBytes:Int) : ElementModQ {
val b = randomBytes(MAX_BYTES_Q + statBytes)
val tmp = b.toBigInteger().mod(vecGroup.order)
val big = if (tmp < bigMinimum) tmp + bigMinimum else tmp
return EcElementModQ(this, big)
val tmp2 = if (tmp < BigInteger.TWO) tmp + BigInteger.TWO else tmp
return EcElementModQ(this, tmp2)
}

override fun randomElementModP() = EcElementModP(this, vecGroup.randomElement())
override fun randomElementModP(statBytes:Int) = EcElementModP(this, vecGroup.randomElement(statBytes))

override fun dLogG(p: ElementModP, maxResult: Int): Int? {
require(p is EcElementModP)
Expand Down
Loading

0 comments on commit 50d1753

Please sign in to comment.