Skip to content

Commit

Permalink
Merge pull request #70 from JohnLCaron/elementP
Browse files Browse the repository at this point in the history
ElementModP/Q cleanup
  • Loading branch information
JohnLCaron authored Apr 25, 2024
2 parents 11b30c5 + c615a40 commit 4a9a7c5
Show file tree
Hide file tree
Showing 19 changed files with 170 additions and 213 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.7%25%20LOC%20(6931/7639)-blue)
![Coverage](https://img.shields.io/badge/coverage-90.8%25%20LOC%20(6930/7630)-blue)

# ElectionGuard-Kotlin Elliptic Curve

_last update 04/17/2024_
_last update 04/24/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/ChaumPedersen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ fun ChaumPedersenRangeProofKnownNonce.verify(

val (alpha, beta) = ciphertext
results.add(
if (alpha.isValidResidue() && beta.isValidResidue()) Ok(true) else
Err(" 5.A,6.A values not in Zp^r: alpha = ${alpha.inBounds()} beta = ${beta.inBounds()}")
if (alpha.isValidElement() && beta.isValidElement()) Ok(true) else
Err(" 5.A,6.A values not in Zp^r: alpha = ${alpha.isValidElement()} beta = ${beta.isValidElement()}")
)

val expandedProofs = proofs.mapIndexed { j, proof ->
Expand Down
65 changes: 21 additions & 44 deletions src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package org.cryptobiotic.eg.core

import org.cryptobiotic.eg.core.Base16.toHex
import org.cryptobiotic.eg.core.intgroup.PowRadixOption
import org.cryptobiotic.eg.core.intgroup.ProductionMode
import org.cryptobiotic.eg.election.ElectionConstants

fun productionGroup(groupName: String = "P-256", useNative: Boolean = true): GroupContext {
Expand All @@ -16,11 +14,6 @@ fun productionGroup(groupName: String = "P-256", useNative: Boolean = true): Gro
* encapsulate acceleration data structures that we'll use to support various operations.
*/
interface GroupContext {
/**
* Returns whether we're using "production primes" (bigger, slower, secure) versus "test primes"
* (smaller, faster, but insecure).
*/
fun isProductionStrength(): Boolean

/** Useful constant: one mod p */
val ONE_MOD_P: ElementModP
Expand Down Expand Up @@ -63,12 +56,6 @@ interface GroupContext {
*/
fun isCompatible(ctx: GroupContext): Boolean

/** Converts a [ByteArray] to an [ElementModP]. Guarantees the result is in [minimum, P), by computing the result mod P. */
fun binaryToElementModPsafe(b: ByteArray, minimum: Int = 0): ElementModP

/** Converts a [ByteArray] to an [ElementModQ]. Guarantees the result is in [minimum, Q), by computing the result mod Q. */
fun binaryToElementModQsafe(b: ByteArray, minimum: Int = 0): ElementModQ

/**
* Converts a [ByteArray] to an [ElementModP], inverse of ElementModP.byteArray().
* Returns null if the number is out of bounds or malformed.
Expand All @@ -77,9 +64,22 @@ interface GroupContext {

/**
* Converts a [ByteArray] to an [ElementModQ], inverse of ElementModQ.byteArray().
* Guarantees the result is in [0, Q), by computing the result mod Q.
* Returns null if the number is out of bounds or malformed.
*/
fun binaryToElementModQ(b: ByteArray): ElementModQ?
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.
*/
fun randomElementModQ(minimum: Int = 0) : ElementModQ // = binaryToElementModQ(randomBytes(MAX_BYTES_Q), minimum)

/**
* Returns a random ElementModP. Promises to use a "secure" random number generator, such that
* the results are suitable for use as cryptographic keys.
*/
fun randomElementModP() : ElementModP

/** Converts an integer to an ElementModQ, with optimizations when possible for small integers */
fun uIntToElementModQ(i: UInt): ElementModQ
Expand Down Expand Up @@ -115,29 +115,13 @@ interface GroupContext {
/**
* 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)
* value designed not to consume too much memory. 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. */
fun getAndClearOpCounts(): Map<String, Int>
}
Expand All @@ -150,14 +134,6 @@ interface Element {
*/
val context: GroupContext

/**
* 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 [0, N)
* where N is the group modulus.
*/
fun inBounds(): Boolean

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

Expand Down Expand Up @@ -188,14 +164,15 @@ interface ElementModQ : Element, Comparable<ElementModQ> {

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

/** Validate the element is in [0,Q) */
fun inBounds(): Boolean
}

interface ElementModP : Element, Comparable<ElementModP> {
/**
* Validates that this element is a quadratic residue, ie in Z_p^r.
* "Z_p^r is the set of r-th-residues in Z∗p", see spec 2.0 p.9
*/
fun isValidResidue(): Boolean

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

/** Computes b^e mod p */
infix fun powP(exp: ElementModQ): ElementModP
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/org/cryptobiotic/eg/core/Schnorr.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ data class SchnorrProof(

init {
compatibleContextOrFail(publicCommitment, challenge, response)
require(publicCommitment.isValidResidue()) // 2.A
require(publicCommitment.isValidElement()) // 2.A
}

// verification Box 2, p 23
Expand All @@ -30,7 +30,7 @@ data class SchnorrProof(
// c wouldnt agree unless h = g^u
// therefore, whoever generated v knows s

val inBoundsK = publicCommitment.isValidResidue() // 2.A
val inBoundsK = publicCommitment.isValidElement() // 2.A
val inBoundsU = response.inBounds() // 2.B
val validChallenge = c == challenge // 2.C
val success = inBoundsK && inBoundsU && validChallenge
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/org/cryptobiotic/eg/core/UInt256.kt
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ fun ByteArray.normalize(nbytes: Int): ByteArray {
* beginning by computing "mod q".
*/
fun UInt256.toElementModQ(context: GroupContext): ElementModQ =
context.binaryToElementModQsafe(bytes)
context.binaryToElementModQ(bytes)

fun ElementModQ.toUInt256safe(): UInt256 = this.byteArray().toUInt256safe()
fun ULong.toUInt256(): UInt256 = this.toByteArray().toUInt256safe()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,8 @@ class EcElementModP(val group: EcGroupContext, val ec: VecElementP): ElementModP
return EcElementModP(group, ec.mul(inv))
}

// TODO what does it mean to be in bounds ??
override fun inBounds(): Boolean = true

// TODO check this
override fun isValidResidue(): Boolean {
/** Validate that this element is a member of the elliptic curve Group.*/
override fun isValidElement(): Boolean {
return group.vecGroup.isPointOnCurve(this.ec.x, this.ec.y)
}

Expand Down
25 changes: 10 additions & 15 deletions src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcGroupContext.kt
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.toBigInteger
import java.math.BigInteger
import java.util.concurrent.atomic.AtomicInteger

Expand All @@ -19,29 +20,29 @@ class EcGroupContext(val name: String, useNative: Boolean = true): GroupContext
override val ZERO_MOD_Q: ElementModQ = EcElementModQ(this, BigInteger.ZERO)
override val ONE_MOD_Q: ElementModQ = EcElementModQ(this, BigInteger.ONE)
override val TWO_MOD_Q: ElementModQ = EcElementModQ(this, BigInteger.TWO)
val NUM_Q_BITS: Int = vecGroup.qbitLength

override val constants = vecGroup.constants
val dlogg = DLogarithm(G_MOD_P)

// TODO whats difference with safe version?
override fun binaryToElementModP(b: ByteArray): ElementModP? {
val elem = vecGroup.elementFromByteArray(b)
return if (elem != null) EcElementModP(this, elem) else null
}

override fun binaryToElementModPsafe(b: ByteArray, minimum: Int): ElementModP {
return binaryToElementModP(b) ?: throw RuntimeException("invalid input")
}

override fun binaryToElementModQ(b: ByteArray): ElementModQ? {
override fun binaryToElementModQ(b: ByteArray): ElementModQ {
return EcElementModQ(this, BigInteger(1, b))
}

override fun binaryToElementModQsafe(b: ByteArray, minimum: Int): ElementModQ {
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()
val tmp = b.toBigInteger().mod(vecGroup.order)
val big = if (tmp < bigMinimum) tmp + bigMinimum else tmp
return EcElementModQ(this, big)
}

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

override fun dLogG(p: ElementModP, maxResult: Int): Int? {
require(p is EcElementModP)
return dlogg.dLog(p, maxResult)
Expand All @@ -64,10 +65,6 @@ class EcGroupContext(val name: String, useNative: Boolean = true): GroupContext
return (ctx is EcGroupContext) && name == ctx.name
}

override fun isProductionStrength(): Boolean {
return true
}

override fun uIntToElementModQ(i: UInt): ElementModQ {
return EcElementModQ(this, BigInteger.valueOf(i.toLong()))
}
Expand All @@ -85,8 +82,6 @@ class EcGroupContext(val name: String, useNative: Boolean = true): GroupContext
return pees.fold(ONE_MOD_P) { a, b -> a * b }
}

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

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
Expand Down
4 changes: 4 additions & 0 deletions src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroup.kt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ open class VecGroup(
fun elementFromByteArray(ba: ByteArray): VecElementP? = elementFromByteArray1(ba)

val ffbyte: Byte = (-1).toByte()

// this is for serialization of both the x and y value.
fun elementFromByteArray2(ba: ByteArray): VecElementP? {
if (ba.size != 2*pbyteLength) return null
val allff = ba.fold( true) { a, b -> a && (b == ffbyte) }
Expand Down Expand Up @@ -123,6 +125,7 @@ open class VecGroup(
return makeVecModP(x, y)
}

// this is for testing that 1 and 2 are equivilent
fun elementFromByteArray1from2(ba: ByteArray): VecElementP? {
if (ba.size != 2*pbyteLength) return null
val allff = ba.fold( true) { a, b -> a && (b == ffbyte) }
Expand All @@ -132,6 +135,7 @@ open class VecGroup(
return makeVecModP(x, y)
}

// this value will always > 1, since 0, 1 are not on the curve.
fun randomElement(): VecElementP {
for (j in 0 until 1000) { // limited in case theres a bug
try {
Expand Down
Loading

0 comments on commit 4a9a7c5

Please sign in to comment.