Skip to content

Commit

Permalink
Break Elements out of GroupContext.
Browse files Browse the repository at this point in the history
Separate int group into IntGroupContext, IntElementModP, IntElementModQ.
Move some random methods to Utils.
  • Loading branch information
JohnLCaron committed May 10, 2024
1 parent 45d7494 commit aa96052
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 aa96052

Please sign in to comment.