Skip to content

Commit

Permalink
Rename ElementModP.isValidResidue() to isValidElement().
Browse files Browse the repository at this point in the history
Element.inBounds() only applies to ElementModQ.
Remove group.isProductionStrength()
Remove binaryToElementModP(Q)safe, used only by randomElementModP(Q)
  • Loading branch information
JohnLCaron committed Apr 25, 2024
1 parent 8c8968f commit c615a40
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 c615a40

Please sign in to comment.