Skip to content

Commit

Permalink
Merge pull request #81 from JohnLCaron/intGroupContext
Browse files Browse the repository at this point in the history
Break Elements out of GroupContext.
  • Loading branch information
JohnLCaron authored May 14, 2024
2 parents fecda4d + aa96052 commit 30380c0
Show file tree
Hide file tree
Showing 19 changed files with 446 additions and 421 deletions.
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.6%25%20LOC%20(7055/7784)-blue)
![Coverage](https://img.shields.io/badge/coverage-90.6%25%20LOC%20(7055/7787)-blue)

# ElectionGuard-Kotlin Elliptic Curve

_last update 05/08/2024_
_last update 05/09/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
68 changes: 0 additions & 68 deletions src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.cryptobiotic.eg.core

import org.cryptobiotic.eg.core.Base16.toHex
import org.cryptobiotic.eg.election.ElectionConstants

fun productionGroup(groupName: String = "P-256", useNative: Boolean = true): GroupContext {
Expand Down Expand Up @@ -126,73 +125,6 @@ interface GroupContext {
fun getAndClearOpCounts(): Map<String, Int>
}

interface Element {
/** The [GroupContext] it belongs to */
val group: GroupContext

/** Validates that this element is a member of the Group */
fun isValidElement(): Boolean

/** Converts to a [ByteArray] representation. Inverse to group.binaryToElementModX(). */
fun byteArray(): ByteArray

fun toHex() : String = byteArray().toHex()
}

interface ElementModQ : Element, Comparable<ElementModQ> {
/** Modular addition */
operator fun plus(other: ElementModQ): ElementModQ

/** Modular subtraction */
operator fun minus(other: ElementModQ): ElementModQ

/** Modular multiplication */
operator fun times(other: ElementModQ): ElementModQ

/** Computes the additive inverse */
operator fun unaryMinus(): ElementModQ

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

/** Multiplies by the modular inverse of [denominator] */
infix operator fun div(denominator: ElementModQ): 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> {

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

/** Modular multiplication */
operator fun times(other: ElementModP): ElementModP

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

/** Multiplies by the modular inverse of [denominator] */
infix operator fun div(denominator: ElementModP): ElementModP

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

/** Create a new instance of this element where the `powP` function will possibly run faster. */
fun acceleratePow(): ElementModP

/** Short version of the String for readability. */
fun toStringShort(): String {
val s = toHex()
val len = s.length
return "${s.substring(0, 7)}...${s.substring(len-8, len)}"
}
}

// Converts an integer to an ElementModQ, with optimizations when possible for small integers
fun Int.toElementModQ(ctx: GroupContext) =
when {
Expand Down
70 changes: 70 additions & 0 deletions src/main/kotlin/org/cryptobiotic/eg/core/GroupElement.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package org.cryptobiotic.eg.core

import org.cryptobiotic.eg.core.Base16.toHex

interface Element {
/** The [GroupContext] it belongs to */
val group: GroupContext

/** Validates that this element is a member of the Group */
fun isValidElement(): Boolean

/** Converts to a [ByteArray] representation. Inverse to group.binaryToElementModX(). */
fun byteArray(): ByteArray

fun toHex() : String = byteArray().toHex()
}

interface ElementModQ : Element, Comparable<ElementModQ> {
/** Modular addition */
operator fun plus(other: ElementModQ): ElementModQ

/** Modular subtraction */
operator fun minus(other: ElementModQ): ElementModQ

/** Modular multiplication */
operator fun times(other: ElementModQ): ElementModQ

/** Computes the additive inverse */
operator fun unaryMinus(): ElementModQ

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

/** Multiplies by the modular inverse of [denominator] */
infix operator fun div(denominator: ElementModQ): 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> {

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

/** Modular multiplication */
operator fun times(other: ElementModP): ElementModP

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

/** Multiplies by the modular inverse of [denominator] */
infix operator fun div(denominator: ElementModP): ElementModP

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

/** Create a new instance of this element where the `powP` function will possibly run faster. */
fun acceleratePow(): ElementModP

/** Short version of the String for readability. */
fun toStringShort(): String {
val s = toHex()
val len = s.length
return "${s.substring(0, 7)}...${s.substring(len-8, len)}"
}
}
8 changes: 8 additions & 0 deletions src/main/kotlin/org/cryptobiotic/eg/core/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,14 @@ fun BigInteger.normalize() : String {
}
fun BigInteger.toHex() = this.toByteArray().toHex().lowercase()


internal fun UInt.toBigInteger() = BigInteger.valueOf(this.toLong())
internal fun ULong.toBigInteger() = BigInteger.valueOf(this.toLong())

/** Convert an array of bytes, in big-endian format, to a BigInteger */
internal fun ByteArray.toBigInteger() = BigInteger(1, this)


/**
* Convert an integer to a big-endian array of four bytes. Negative numbers will be in
* twos-complement.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.cryptobiotic.eg.core.ecgroup

import org.cryptobiotic.eg.core.*
import org.cryptobiotic.eg.core.intgroup.toBigInteger
import java.math.BigInteger
import java.util.concurrent.atomic.AtomicInteger

Expand Down
124 changes: 124 additions & 0 deletions src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntElementModP.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package org.cryptobiotic.eg.core.intgroup

import org.cryptobiotic.eg.core.*
import org.cryptobiotic.eg.core.Base64.toBase64
import java.math.BigInteger
import java.util.concurrent.atomic.AtomicInteger

open class IntElementModP(internal val element: BigInteger, val groupContext: IntGroupContext): ElementModP,
Element, Comparable<ElementModP> {

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

private fun BigInteger.modWrap(): ElementModP = this.mod(groupContext.p).wrap()
private fun BigInteger.wrap(): ElementModP = IntElementModP(this, groupContext)

override val group: GroupContext
get() = groupContext

override operator fun compareTo(other: ElementModP): Int = element.compareTo(other.getCompat(groupContext))

/**
* Validates that this element is in Z_p^r, "set of r-th-residues in Z_p".
* "A value x is in Z_p^r if and only if x is an integer such that 0 ≤ x < p
* and x^q mod p == 1", see spec 2.0 p.9.
*/
override fun isValidElement(): Boolean {
groupContext.opCounts.getOrPut("exp") { AtomicInteger(0) }.incrementAndGet()
val inBounds = this.element >= BigInteger.ZERO && this.element < groupContext.p
val residue = this.element.modPow(groupContext.q, groupContext.p) == groupContext.oneModP.element
return inBounds && residue
}

override infix fun powP(exp: ElementModQ) : ElementModP {
groupContext.opCounts.getOrPut("exp") { AtomicInteger(0) }.incrementAndGet()
return this.element.modPow(exp.getCompat(groupContext), groupContext.p).wrap()
}

override operator fun times(other: ElementModP) =
(this.element * other.getCompat(groupContext)).modWrap()

override fun multInv()
= element.modInverse(groupContext.p).wrap()
// = this powP groupContext.qMinus1Q

// Performance note: multInv() can be expressed with the modInverse() method or we can do
// this exponentiation thing with Q - 1, which works for the subgroup. On the JVM, we get
// basically the same performance either way.

override infix operator fun div(denominator: ElementModP) =
(element * denominator.getCompat(groupContext).modInverse(groupContext.p)).modWrap()

override fun acceleratePow() : ElementModP =
AcceleratedElementModP(this)

fun toMontgomeryElementModP(): MontgomeryElementModP =
ProductionMontgomeryElementModP(
element.shiftLeft(groupContext.productionMode.numBitsInP).mod(groupContext.p),
groupContext
)

override fun equals(other: Any?) = when (other) {
is ElementModP -> byteArray().contentEquals(other.byteArray())
else -> false
}

override fun hashCode() = byteArray().contentHashCode()

override fun toString() = byteArray().toBase64()

private fun ElementModP.getCompat(other: IntGroupContext): BigInteger {
group.assertCompatible(other)
return when (this) {
is IntElementModP -> this.element
else -> throw NotImplementedError("should only be two kinds of elements")
}
}
}

class AcceleratedElementModP(p: IntElementModP) : IntElementModP(p.element, p.groupContext) {
// Laziness to delay computation of the table until its first use; saves space
val powRadix by lazy { PowRadix(p, p.groupContext.powRadixOption) }

override fun acceleratePow(): ElementModP = this

override infix fun powP(exp: ElementModQ) : ElementModP {
groupContext.opCounts.getOrPut("acc") { AtomicInteger(0) }.incrementAndGet()
return powRadix.pow(exp)
}
}

internal data class ProductionMontgomeryElementModP(val element: BigInteger, val groupContext: IntGroupContext):
MontgomeryElementModP {
internal fun MontgomeryElementModP.getCompat(other: GroupContext): BigInteger {
context.assertCompatible(other)
if (this is ProductionMontgomeryElementModP) {
return this.element
} else {
throw NotImplementedError("unexpected MontgomeryElementModP type")
}
}

internal fun BigInteger.modI(): BigInteger = this and groupContext.montgomeryIMinusOne

internal fun BigInteger.divI(): BigInteger = this shr groupContext.productionMode.numBitsInP

override fun times(other: MontgomeryElementModP): MontgomeryElementModP {
// w = aI * bI = (ab)(I^2)
val w: BigInteger = this.element * other.getCompat(this.context)

// Z = ((((W mod I)⋅p^' ) mod I)⋅p+W)/I
val z: BigInteger = (((w.modI() * groupContext.montgomeryPPrime).modI() * groupContext.p) + w).divI()

return ProductionMontgomeryElementModP(
if (z >= groupContext.p) z - groupContext.p else z,
groupContext)
}

override fun toElementModP(): ElementModP =
IntElementModP((element * groupContext.montgomeryIPrime).mod(groupContext.p), groupContext)

override val context: GroupContext
get() = groupContext

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package org.cryptobiotic.eg.core.intgroup

import org.cryptobiotic.eg.core.Base64.toBase64
import org.cryptobiotic.eg.core.Element
import org.cryptobiotic.eg.core.ElementModQ
import org.cryptobiotic.eg.core.assertCompatible
import org.cryptobiotic.eg.core.normalize
import java.math.BigInteger

class IntElementModQ(internal val element: BigInteger, override val group: IntGroupContext): ElementModQ,
Element, Comparable<ElementModQ> {

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

private fun BigInteger.modWrap(): ElementModQ = this.mod(this@IntElementModQ.group.q).wrap()
private fun BigInteger.wrap(): ElementModQ = IntElementModQ(this, this@IntElementModQ.group)

override fun isZero() = element == BigInteger.ZERO
override fun isValidElement() = element >= BigInteger.ZERO && element < this.group.q

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

override operator fun plus(other: ElementModQ) =
(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(this.group)).modWrap()

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

override operator fun unaryMinus(): ElementModQ =
if (this == this.group.zeroModQ)
this
else
(this.group.q - element).wrap()

override infix operator fun div(denominator: ElementModQ): ElementModQ =
this * denominator.multInv()


override fun equals(other: Any?) = when (other) {
is ElementModQ -> byteArray().contentEquals(other.byteArray())
else -> false
}

override fun hashCode() = byteArray().contentHashCode()

override fun toString() = byteArray().toBase64()
}

internal fun ElementModQ.getCompat(other: IntGroupContext): BigInteger {
group.assertCompatible(other)
return when (this) {
is IntElementModQ -> this.element
else -> throw NotImplementedError("should only be two kinds of elements")
}
}
Loading

0 comments on commit 30380c0

Please sign in to comment.