diff --git a/README.md b/README.md index fd1c21c..31998f0 100644 --- a/README.md +++ b/README.md @@ -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), diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/ChaumPedersen.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ChaumPedersen.kt index eb596b7..9ff5b78 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ChaumPedersen.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ChaumPedersen.kt @@ -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 -> diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt b/src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt index 03ad9ec..bff6315 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt @@ -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 { @@ -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 @@ -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. @@ -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 @@ -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 } @@ -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 @@ -188,14 +164,15 @@ interface ElementModQ : Element, Comparable { /** Checks whether this element is zero. */ fun isZero(): Boolean + + /** Validate the element is in [0,Q) */ + fun inBounds(): Boolean } interface ElementModP : Element, Comparable { - /** - * 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 diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/Schnorr.kt b/src/main/kotlin/org/cryptobiotic/eg/core/Schnorr.kt index a1546d3..9c71d63 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/Schnorr.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/Schnorr.kt @@ -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 @@ -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 diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/UInt256.kt b/src/main/kotlin/org/cryptobiotic/eg/core/UInt256.kt index ba768c4..63db8c4 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/UInt256.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/UInt256.kt @@ -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() diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModP.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModP.kt index bb5a091..af2e12f 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModP.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModP.kt @@ -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) } diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcGroupContext.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcGroupContext.kt index be72f83..59917ff 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcGroupContext.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcGroupContext.kt @@ -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 @@ -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) @@ -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())) } @@ -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 diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroup.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroup.kt index 6279217..bf97715 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroup.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroup.kt @@ -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) } @@ -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) } @@ -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 { diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroup.kt b/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroup.kt index 5c7cea3..242f12f 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroup.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroup.kt @@ -62,7 +62,6 @@ class ProductionGroupContext( val groupConstants = IntGroupConstants(name, p, q, r, g) override val constants = groupConstants.constants - override fun isProductionStrength() = true override fun toString() : String = name override val ONE_MOD_P : ElementModP @@ -96,27 +95,7 @@ class ProductionGroupContext( get() = numPBits override fun isCompatible(ctx: GroupContext): Boolean { - return ctx.isProductionStrength() && productionMode == (ctx as ProductionGroupContext).productionMode - } - - override fun binaryToElementModPsafe(b: ByteArray, minimum: Int): ElementModP { - if (minimum < 0) { - throw IllegalArgumentException("minimum $minimum may not be negative") - } - val tmp = b.toBigInteger().mod(p) - val mv = minimum.toBigInteger() - val tmp2 = if (tmp < mv) tmp + mv else tmp - return ProductionElementModP(tmp2, this) - } - - override fun binaryToElementModQsafe(b: ByteArray, minimum: Int): ElementModQ { - if (minimum < 0) { - throw IllegalArgumentException("minimum $minimum may not be negative") - } - val tmp = b.toBigInteger().mod(q) - val mv = minimum.toBigInteger() - val tmp2 = if (tmp < mv) tmp + mv else tmp - return ProductionElementModQ(tmp2, this) + return (ctx is ProductionGroupContext) && productionMode == ctx.productionMode } override fun binaryToElementModP(b: ByteArray): ElementModP? = @@ -127,13 +106,18 @@ class ProductionGroupContext( null } - override fun binaryToElementModQ(b: ByteArray): ElementModQ? = - try { - val tmp = b.toBigInteger() - if (tmp >= q || tmp < BigInteger.ZERO) null else ProductionElementModQ(tmp, this) - } catch (t : Throwable) { - null - } + 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(q) + val tmp2 = if (tmp < bigMinimum) tmp + bigMinimum else tmp + return ProductionElementModQ(tmp2, this) + } + + override fun binaryToElementModQ(b: ByteArray): ElementModQ { + val big = b.toBigInteger().mod(q) + return ProductionElementModQ(big, this) + } override fun uIntToElementModQ(i: UInt) : ElementModQ = when (i) { 0U -> ZERO_MOD_Q @@ -154,59 +138,31 @@ class ProductionGroupContext( return ProductionElementModQ(sum.mod(q), this) } - /* - override fun Iterable.addQ(): ElementModQ { - val input = iterator().asSequence().toList() - - if (input.isEmpty()) { - return ZERO_MOD_Q - } - - if (input.count() == 1) { - return input[0] - } - - val result = input.map { - it.getCompat(this@ProductionGroupContext) - }.reduce { a, b -> - (a + b).mod(this@ProductionGroupContext.q) - } - - return ProductionElementModQ(result, this@ProductionGroupContext) - } - - */ - override fun multP(pees: Iterable): ElementModP { return pees.fold(ONE_MOD_P) { a, b -> a * b } } - /* - override fun Iterable.multP(): ElementModP { - val input = iterator().asSequence().toList() - - if (input.isEmpty()) { - return ONE_MOD_P - } - - if (input.count() == 1) { - return input[0] - } - - val result = input.map { - it.getCompat(this@ProductionGroupContext) - }.reduce { a, b -> - (a * b).mod(this@ProductionGroupContext.p) - } + override fun gPowP(exp: ElementModQ) = gModP powP exp - return ProductionElementModP(result, this@ProductionGroupContext) - } + override fun dLogG(p: ElementModP, maxResult: Int): Int? = dlogger.dLog(p, maxResult) + /** + * Returns a random number in [2, P). 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 */ + override fun randomElementModP() = binaryToElementModPsafe(randomBytes(MAX_BYTES_P), 2) - override fun gPowP(exp: ElementModQ) = gModP powP exp - override fun dLogG(p: ElementModP, maxResult: Int): Int? = dlogger.dLog(p, maxResult) + fun binaryToElementModPsafe(b: ByteArray, minimum: Int): ElementModP { + if (minimum < 0) { + throw IllegalArgumentException("minimum $minimum may not be negative") + } + val tmp = b.toBigInteger().mod(p) + val mv = minimum.toBigInteger() + val tmp2 = if (tmp < mv) tmp + mv else tmp + return ProductionElementModP(tmp2, this) + } var opCounts: HashMap = HashMap() override fun getAndClearOpCounts(): Map { @@ -285,14 +241,17 @@ open class ProductionElementModP(internal val element: BigInteger, val groupCont override val context: GroupContext get() = groupContext - override fun inBounds() = element >= BigInteger.ZERO && element < groupContext.p - override operator fun compareTo(other: ElementModP): Int = element.compareTo(other.getCompat(groupContext)) - override fun isValidResidue(): Boolean { + /** + * Validates that this element is a member of the Integer Group, ie in Z_p^r. + * "Z_p^r is the set of r-th-residues in Z∗p", 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 + return inBounds && residue } override infix fun powP(exp: ElementModQ) : ElementModP { diff --git a/src/main/kotlin/org/cryptobiotic/eg/decrypt/DecryptingTrustee.kt b/src/main/kotlin/org/cryptobiotic/eg/decrypt/DecryptingTrustee.kt index ed4df31..27eaf09 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/decrypt/DecryptingTrustee.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/decrypt/DecryptingTrustee.kt @@ -45,7 +45,7 @@ data class DecryptingTrustee( val results: MutableList = mutableListOf() val badTexts = mutableListOf() texts.forEachIndexed { idx, text -> - if (!text.isValidResidue()) { + if (!text.isValidElement()) { badTexts.add(idx) } else { // do not process if not valid val u = nonces.get(idx) // random value u in Zq diff --git a/src/main/kotlin/org/cryptobiotic/eg/verifier/Verifier.kt b/src/main/kotlin/org/cryptobiotic/eg/verifier/Verifier.kt index 2988eed..c7105f5 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/verifier/Verifier.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/verifier/Verifier.kt @@ -180,15 +180,15 @@ class Verifier(val record: ElectionRecord, val nthreads: Int = 11) { } // Verification 3 (Election public-key validation) - //An election verifier must verify the correct computation of the joint election public key. - //(3.A) The value Ki is in Zpr and Ki ̸= 1 mod p + // An election verifier must verify the correct computation of the joint election public key. + // (3.A) The value Ki is in Z_p^r and K_i != 1 mod p private fun verifyElectionPublicKey(): Result { val errors = mutableListOf>() val guardiansSorted = this.record.guardians().sortedBy { it.xCoordinate } guardiansSorted.forEach { val Ki = it.publicKey() - if (!Ki.isValidResidue()) { + if (!Ki.isValidElement()) { errors.add(Err(" 3.A publicKey Ki (${it.guardianId} is not in Zp^r")) } if (Ki == group.ONE_MOD_P) { diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/GroupTest.kt b/src/test/kotlin/org/cryptobiotic/eg/core/GroupTest.kt index 5f0789b..9d778b1 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/GroupTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/GroupTest.kt @@ -6,7 +6,6 @@ import io.kotest.property.checkAll import io.kotest.property.forAll import org.cryptobiotic.eg.core.ecgroup.EcGroupContext import org.cryptobiotic.eg.core.intgroup.tinyGroup -import org.cryptobiotic.util.Stopwatch import kotlin.test.* class GroupTest { @@ -53,17 +52,24 @@ class GroupTest { } @Test - fun generatorsWork() { - groups.forEach { generatorsWork(it) } + fun qInBounds() { + groups.forEach { qInBounds(it) } } - fun generatorsWork(context: GroupContext) { + fun qInBounds(context: GroupContext) { runTest { - forAll(propTestFastConfig, elementsModP(context)) { it.inBounds() } forAll(propTestFastConfig, elementsModQ(context)) { it.inBounds() } } } + @Test + fun pIsResidue() { + val group = EcGroupContext("P-256") + runTest { + forAll(propTestFastConfig, elementsModP(group)) { it.isValidElement() } + } + } + @Test fun validResiduesForGPowP() { groups.forEach { validResiduesForGPowP(it) } @@ -71,7 +77,7 @@ class GroupTest { fun validResiduesForGPowP(context: GroupContext) { runTest { - forAll(propTestFastConfig, validResiduesOfP(context)) { it.isValidResidue() } + forAll(propTestFastConfig, validResiduesOfP(context)) { it.isValidElement() } } } @@ -137,9 +143,9 @@ class GroupTest { runTest { checkAll( propTestFastConfig, - elementsModPNoZero(context), - elementsModPNoZero(context), - elementsModPNoZero(context) + elementsModP(context), + elementsModP(context), + elementsModP(context) ) { a, b, c -> assertEquals(a, a * context.ONE_MOD_P) // identity assertEquals(a * b, b * a) // commutative diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/KotestGenerators.kt b/src/test/kotlin/org/cryptobiotic/eg/core/KotestGenerators.kt index 161267a..83256a9 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/KotestGenerators.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/KotestGenerators.kt @@ -6,24 +6,28 @@ import io.kotest.common.ExperimentalKotest import io.kotest.property.Arb import io.kotest.property.PropTestConfig import io.kotest.property.ShrinkingMode -import io.kotest.property.arbitrary.byte -import io.kotest.property.arbitrary.byteArray -import io.kotest.property.arbitrary.constant -import io.kotest.property.arbitrary.map +import io.kotest.property.arbitrary.* -/** Generate an arbitrary ElementModP in [minimum, P) for the given group context. */ -fun elementsModP(ctx: GroupContext, minimum: Int = 0): Arb = - Arb.byteArray(Arb.constant(ctx.MAX_BYTES_P), Arb.byte()) - .map { ctx.randomElementModP() } +/** Generate an arbitrary ElementModP for the given group context. */ +fun elementsModP(ctx: GroupContext): Arb { + return arbitrary { ctx.randomElementModP() } +} + +// Arb.byteArray(Arb.constant(ctx.MAX_BYTES_P), Arb.byte()) +// .map { _ -> ctx.randomElementModP() } /** Generate an arbitrary ElementModP in [1, P) for the given group context. */ -fun elementsModPNoZero(ctx: GroupContext) = elementsModP(ctx, 1) +// fun elementsModPNoZero(ctx: GroupContext) = elementsModP(ctx) /** Generate an arbitrary ElementModQ in [minimum, Q) for the given group context. */ -fun elementsModQ(ctx: GroupContext, minimum: Int = 0): Arb = +fun elementsModQ(ctx: GroupContext, minimum: Int = 0): Arb = arbitrary{ ctx.randomElementModQ() } + +/* fun elementsModQ(ctx: GroupContext, minimum: Int = 0): Arb = Arb.byteArray(Arb.constant(ctx.MAX_BYTES_Q), Arb.byte()) .map { ctx.randomElementModQ() } + */ + /** Generate an arbitrary ElementModQ in [1, Q) for the given group context. */ fun elementsModQNoZero(ctx: GroupContext) = elementsModQ(ctx, 1) diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/SchnorrTest.kt b/src/test/kotlin/org/cryptobiotic/eg/core/SchnorrTest.kt index 770d703..2111f01 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/SchnorrTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/SchnorrTest.kt @@ -28,9 +28,8 @@ private fun testSchnorrProof(name: String, group: GroupContext) = wordSpec { Arb.int(1, 11), Arb.int(0, 10), elementsModQ(group), - validResiduesOfP(group), elementsModQ(group) - ) { kp, i, j, nonce, fakeElementModP, fakeElementModQ -> + ) { kp, i, j, nonce, fakeElementModQ -> val goodProof = kp.schnorrProof(i, j, nonce) (goodProof.validate(i, j) is Ok) shouldBe true } @@ -47,9 +46,8 @@ private fun testSchnorrProof(name: String, group: GroupContext) = wordSpec { Arb.int(1, 11), Arb.int(0, 10), elementsModQ(group), - validResiduesOfP(group), elementsModQ(group) - ) { kp, i, j, nonce, fakeElementModP, fakeElementModQ -> + ) { kp, i, j, nonce, fakeElementModQ -> val goodProof = kp.schnorrProof(i, j, nonce) val badProof1 = goodProof.copy(challenge = fakeElementModQ) val badProof2 = goodProof.copy(response = fakeElementModQ) diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/TestSetMembership.kt b/src/test/kotlin/org/cryptobiotic/eg/core/TestSetMembership.kt index 6529540..648c681 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/TestSetMembership.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/TestSetMembership.kt @@ -39,7 +39,7 @@ class TestSetMembership { } fun checkMembership(x: ElementModP?): Boolean { - return (x != null) && x.isValidResidue() + return (x != null) && x.isValidElement() } } \ No newline at end of file diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/ecgroup/TestAgainstNative.kt b/src/test/kotlin/org/cryptobiotic/eg/core/ecgroup/TestAgainstNative.kt index f041cda..2bb08bb 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/ecgroup/TestAgainstNative.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/ecgroup/TestAgainstNative.kt @@ -18,7 +18,7 @@ class TestAgainstNative { // randomElementModP seems to always be the case when p = 3 mod 4 repeat(100) { - val elemP = group.randomElementModP(2).ec + val elemP = group.randomElementModP().ec val elemPy2 = vecGroupN.equationf(elemP.x) val elemPy = vecGroup.sqrt(elemPy2) @@ -42,7 +42,7 @@ class TestAgainstNative { // seems to be the case when p = 3 mod 4 repeat(100) { - val elemP = group.randomElementModP(2).ec + val elemP = group.randomElementModP().ec val elemPy = elemP.y val sqrtPy = vecGroup.sqrt(elemPy) diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/ecgroup/TestElem.kt b/src/test/kotlin/org/cryptobiotic/eg/core/ecgroup/TestElem.kt index f77b1fa..8b96a1d 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/ecgroup/TestElem.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/ecgroup/TestElem.kt @@ -7,6 +7,7 @@ import org.cryptobiotic.util.Stopwatch import java.math.BigInteger import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith class TestElem { @@ -112,6 +113,28 @@ class TestElem { assertEquals(one, roundtrip) } + @Test + fun testZeroOnCurve() { + val ecGroup = VecGroups.getEcGroup("P-256") + val fx = BigInteger.ZERO + val fy = ecGroup.equationf(fx) + // is this legal ? + assertFailsWith { + VecElementP(ecGroup, fx, fy) + } + } + + @Test + fun testOneOnCurve() { + val ecGroup = VecGroups.getEcGroup("P-256") + val fx = BigInteger.ONE + val fy = ecGroup.equationf(fx) + // is this legal ? + assertFailsWith { + VecElementP(ecGroup, fx, fy) + } + } + @Test fun testSqrt() { if (VecGroups.hasNativeLibrary()) { @@ -119,7 +142,7 @@ class TestElem { val vecGroupN = group.vecGroup as VecGroupNative repeat(100) { - val elemP = group.randomElementModP(2).ec + val elemP = group.randomElementModP().ec val elemPx = elemP.x val elemPy2 = vecGroupN.equationf(elemPx) diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/TinyGroup.kt b/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/TinyGroup.kt index ba1e778..bf46416 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/TinyGroup.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/TinyGroup.kt @@ -60,8 +60,6 @@ internal class TinyGroupContext( dlogger = DLogarithm(gModP) } - override fun isProductionStrength() = false - val groupConstants = IntGroupConstants(name, BigInteger(1, p.toByteArray()), BigInteger(1, q.toByteArray()), @@ -93,7 +91,7 @@ internal class TinyGroupContext( override val NUM_P_BITS: Int get() = 31 - override fun isCompatible(ctx: GroupContext): Boolean = !ctx.isProductionStrength() + override fun isCompatible(ctx: GroupContext): Boolean = ctx is TinyGroupContext /** * Convert a ByteArray, of arbitrary size, to a UInt, mod the given modulus. If the ByteArray @@ -113,22 +111,11 @@ internal class TinyGroupContext( } } - override fun binaryToElementModPsafe(b: ByteArray, minimum: Int): ElementModP { - if (minimum < 0) { - throw IllegalArgumentException("minimum $minimum may not be negative") - } - - val u32 = b.toUIntMod(p) - val result = if (u32 < minimum.toUInt()) u32 + minimum.toUInt() else u32 - return uIntToElementModP(result) - } - - override fun binaryToElementModQsafe(b: ByteArray, minimum: Int): ElementModQ { - if (minimum < 0) { - throw IllegalArgumentException("minimum $minimum may not be negative") - } + override fun randomElementModQ(minimum: Int) : ElementModQ { + val b = randomBytes(MAX_BYTES_Q) + val useMinimum = if (minimum <= 0) 0U else minimum.toUInt() val u32 = b.toUIntMod(q) - val result = if (u32 < minimum.toUInt()) u32 + minimum.toUInt() else u32 + val result = if (u32 < useMinimum) u32 + useMinimum else u32 return uIntToElementModQ(result) } @@ -139,11 +126,9 @@ internal class TinyGroupContext( return if (u32 >= p) null else uIntToElementModP(u32) } - override fun binaryToElementModQ(b: ByteArray): ElementModQ? { - if (b.size > 4) return null // guaranteed to be out of bounds - - val u32: UInt = b.toUIntMod() - return if (u32 >= q) null else uIntToElementModQ(u32) + override fun binaryToElementModQ(b: ByteArray): ElementModQ { + val u32: UInt = b.toUIntMod(q) + return uIntToElementModQ(u32) } override fun uIntToElementModQ(i: UInt): ElementModQ = @@ -176,6 +161,15 @@ internal class TinyGroupContext( override fun dLogG(p: ElementModP, maxResult: Int): Int? = dlogger.dLog(p, maxResult) + override fun randomElementModP() = binaryToElementModPmin(randomBytes(MAX_BYTES_P), 2) + + private fun binaryToElementModPmin(b: ByteArray, minimum: Int): ElementModP { + val useMinimum = if (minimum <= 0) 0U else minimum.toUInt() + val u32 = b.toUIntMod(p) + val result = if (u32 < useMinimum) u32 + useMinimum else u32 + return uIntToElementModP(result) + } + override fun getAndClearOpCounts() = emptyMap() } @@ -185,10 +179,10 @@ internal class TinyElementModP(val element: UInt, val groupContext: TinyGroupCon fun UInt.wrap(): ElementModP = TinyElementModP(this, groupContext) fun ULong.wrap(): ElementModP = toUInt().wrap() - override fun isValidResidue(): Boolean { - val residue = - this powP TinyElementModQ(groupContext.q, groupContext) == groupContext.ONE_MOD_P - return inBounds() && residue + override fun isValidElement(): Boolean { + val inBounds = element < groupContext.p + val residue = this powP TinyElementModQ(groupContext.q, groupContext) == groupContext.ONE_MOD_P + return inBounds && residue } override fun powP(exp: ElementModQ): ElementModP { @@ -225,7 +219,7 @@ internal class TinyElementModP(val element: UInt, val groupContext: TinyGroupCon override val context: GroupContext get() = groupContext - override fun inBounds(): Boolean = element < groupContext.p + // fun inBounds(): Boolean = element < groupContext.p override fun byteArray(): ByteArray = element.toByteArray() diff --git a/src/test/kotlin/org/cryptobiotic/eg/publish/json/WebappDecryptionTest.kt b/src/test/kotlin/org/cryptobiotic/eg/publish/json/WebappDecryptionTest.kt index c569a70..2bc2b4b 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/publish/json/WebappDecryptionTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/publish/json/WebappDecryptionTest.kt @@ -60,9 +60,9 @@ class WebappDecryptionTest { ) { name, nrequests -> val drequest = DecryptRequest( - listOf( elementsModP(group, minimum = 2).single(), - elementsModP(group, minimum = 2).single(), - elementsModP(group, minimum = 2).single()) + listOf( elementsModP(group).single(), + elementsModP(group).single(), + elementsModP(group).single()) ) val drequestj = drequest.publishJson() val roundtrip = drequestj.import(group) @@ -86,9 +86,9 @@ class WebappDecryptionTest { ) { name, nrequests -> val crs = List(nrequests) { PartialDecryption( - elementsModP(group, minimum = 2).single(), - elementsModP(group, minimum = 2).single(), - elementsModP(group, minimum = 2).single(), + elementsModP(group).single(), + elementsModP(group).single(), + elementsModP(group).single(), ) } val org = PartialDecryptions(null, 42, crs)