diff --git a/README.md b/README.md index c669743..be9d64f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ [![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.4%25%20LOC%20(6997/7741)-blue) +![Coverage](https://img.shields.io/badge/coverage-90.4%25%20LOC%20(6991/7730)-blue) # ElectionGuard-Kotlin Elliptic Curve diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/ChaumPedersen.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ChaumPedersen.kt index 9ff5b78..8f81dfe 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ChaumPedersen.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ChaumPedersen.kt @@ -149,9 +149,9 @@ fun ChaumPedersenRangeProofKnownNonce.verify( val expandedProofs = proofs.mapIndexed { j, proof -> // recomputes all the a and b values val (cj, vj) = proof - if (cj.inBounds() && vj.inBounds()) results.add(Ok(true)) - if (!cj.inBounds()) results.add(Err(" 5.B,6.B cj (idx $j) not in bounds")) - if (!vj.inBounds()) results.add(Err(" 5.C,6.C vj (idx $j) not in bounds")) + if (cj.isValidElement() && vj.isValidElement()) results.add(Ok(true)) + if (!cj.isValidElement()) results.add(Err(" 5.B,6.B cj (idx $j) not in bounds")) + if (!vj.isValidElement()) results.add(Err(" 5.C,6.C vj (idx $j) not in bounds")) val wj = (vj - j.toElementModQ(group) * cj) ExpandedChaumPedersenProof( @@ -222,7 +222,7 @@ fun ChaumPedersenProof.verifyDecryption( val b = (encryptedVote.pad powP this.r) * (M powP this.c) // 9.3 // 9.A The given value v is in the set Z_q. - if (!this.r.inBounds()) { + if (!this.r.isValidElement()) { return false } // The challenge value c = H(HE ; 0x30, K, A, B, a, b, M ). eq 71, 9.B. @@ -253,7 +253,7 @@ fun ChaumPedersenProof.verifyContestDataDecryption( val b = (hashedCiphertext.c0 powP this.r) * (beta powP this.c) // 11.2 // 11.A The given value v is in the set Z_q. - if (!this.r.inBounds()) { + if (!this.r.isValidElement()) { return false } // The challenge value c = H(HE ; 0x31, K, C0 , C1 , C2 , a, b, β) // 11.B diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt b/src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt index bff6315..c405d8c 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/GroupContext.kt @@ -21,12 +21,6 @@ interface GroupContext { /** Useful constant: the group generator */ val G_MOD_P: ElementModP - /** Useful constant: the inverse of the group generator */ - val GINV_MOD_P: ElementModP - - /** Useful constant: the group generator, squared */ - val G_SQUARED_MOD_P: ElementModP - /** Useful constant: zero mod q */ val ZERO_MOD_Q: ElementModQ @@ -134,6 +128,9 @@ interface Element { */ val context: 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 @@ -164,16 +161,10 @@ 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 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 9c71d63..ba2c7ed 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/Schnorr.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/Schnorr.kt @@ -31,7 +31,7 @@ data class SchnorrProof( // therefore, whoever generated v knows s val inBoundsK = publicCommitment.isValidElement() // 2.A - val inBoundsU = response.inBounds() // 2.B + val inBoundsU = response.isValidElement() // 2.B val validChallenge = c == challenge // 2.C val success = inBoundsK && inBoundsU && validChallenge 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 af2e12f..a352b88 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModP.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModP.kt @@ -25,7 +25,7 @@ class EcElementModP(val group: EcGroupContext, val ec: VecElementP): ElementModP /** 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) + return this.ec.isValidElement() } override fun multInv(): ElementModP { diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModQ.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModQ.kt index 6446ade..e17d1dc 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModQ.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcElementModQ.kt @@ -15,7 +15,7 @@ class EcElementModQ(val group: EcGroupContext, val element: BigInteger): Element override val context: GroupContext get() = group - override fun inBounds() = element >= BigInteger.ZERO && element < group.vecGroup.order + override fun isValidElement() = element >= BigInteger.ZERO && element < group.vecGroup.order override operator fun compareTo(other: ElementModQ): Int = element.compareTo(other.getCompat(group)) 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 59917ff..66a292b 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcGroupContext.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/EcGroupContext.kt @@ -9,9 +9,7 @@ class EcGroupContext(val name: String, useNative: Boolean = true): GroupContext val vecGroup: VecGroup = VecGroups.getEcGroup(name, useNative) val ONE = EcElementModP(this, vecGroup.ONE) - override val GINV_MOD_P: ElementModP = EcElementModP(this, vecGroup.g.inv()) override val G_MOD_P: ElementModP = EcElementModP(this, vecGroup.g) - override val G_SQUARED_MOD_P: ElementModP = EcElementModP(this, vecGroup.g.square()) override val NUM_P_BITS: Int = vecGroup.pbitLength override val MAX_BYTES_P: Int = vecGroup.pbyteLength + 1 // x plus sign of y override val ONE_MOD_P: ElementModP = this.ONE diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecElementP.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecElementP.kt index 91f6aca..d668d1a 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecElementP.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecElementP.kt @@ -39,19 +39,23 @@ open class VecElementP( val pGroup: VecGroup, val x: BigInteger, val y: BigInteger, - safe: Boolean = false // eg randomElement() knows its safe ) { val modulus = pGroup.primeModulus constructor(group: VecGroup, xs: String, ys: String): this(group, BigInteger(xs,16), BigInteger(ys, 16)) init { - if (!safe && !pGroup.isPointOnCurve(x, y)) { - pGroup.isPointOnCurve(x, y) + if (!isValidElement()) { throw RuntimeException("Given point is not on the described curve") } } + fun isValidElement(): Boolean { + // TODO this has to be true unless it was constructed with safe=true. + // Note this also checks that both x and y are in [0,P) + return pGroup.isPointOnCurve(x, y) + } + open fun acceleratePow(): VecElementP { return this } @@ -90,7 +94,7 @@ open class VecElementP( // Otherwise we perform multiplication of two points in general position. // s = (y-e.y)/(x-e.x) - val s = y.subtract(other.y).multiply(x.subtract(other.x).modInverse(modulus).mod(modulus)); + val s = y.subtract(other.y).multiply(x.subtract(other.x).modInverse(modulus).mod(modulus)) // rx = s^2 - (x + e.x) val rx = s.multiply(s).subtract(this.x).subtract(other.x).mod(modulus) diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecElementPnative.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecElementPnative.kt index 7bf6c84..7a00a9e 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecElementPnative.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecElementPnative.kt @@ -37,14 +37,13 @@ open class VecElementPnative( pGroup: VecGroup, x: BigInteger, y: BigInteger, - safe: Boolean = false -) : VecElementP(pGroup, x, y, safe) { +) : VecElementP(pGroup, x, y) { val vgNative = pGroup as VecGroupNative constructor(group: VecGroup, xs: String, ys: String): this(group, BigInteger(xs,16), BigInteger(ys, 16)) override fun acceleratePow(): VecElementP { - return VecElementPnativeAcc(pGroup, x, y, true) + return VecElementPnativeAcc(pGroup, x, y) } /** Compute the product of this element with other. */ @@ -89,8 +88,7 @@ class VecElementPnativeAcc( pGroup: VecGroup, x: BigInteger, y: BigInteger, - safe: Boolean = false -) : VecElementPnative(pGroup, x, y, safe) { +) : VecElementPnative(pGroup, x, y) { val tablePtr: ByteArray by lazy { // TODO free the native memory.... /** 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 bf97715..b743538 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroup.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroup.kt @@ -89,7 +89,7 @@ open class VecGroup( pIs3mod4 = primeModulus.testBit(0) && primeModulus.testBit(1) // p mod 4 = 3, true for P-256 } - open fun makeVecModP(x: BigInteger, y: BigInteger, safe: Boolean = false) = VecElementP(this, x, y, safe) + open fun makeVecModP(x: BigInteger, y: BigInteger) = VecElementP(this, x, y) fun elementFromByteArray(ba: ByteArray): VecElementP? = elementFromByteArray1(ba) @@ -144,7 +144,7 @@ open class VecGroup( if (jacobiSymbol(fx, primeModulus) == 1) { val y2 = sqrt(fx) - return makeVecModP(x, y2, true) + return makeVecModP(x, y2) } } catch (e: RuntimeException) { throw RuntimeException("Unexpected format exception", e) diff --git a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroupNative.kt b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroupNative.kt index 49afbc9..2e75926 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroupNative.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/ecgroup/VecGroupNative.kt @@ -48,7 +48,7 @@ class VecGroupNative( /** Pointer to curve parameters in native space. */ val nativePointer: ByteArray = VEC.getCurve("P-256") - override fun makeVecModP(x: BigInteger, y: BigInteger, safe: Boolean) = VecElementPnative(this, x, y, safe) + override fun makeVecModP(x: BigInteger, y: BigInteger) = VecElementPnative(this, x, y) override fun sqrt(x: BigInteger): BigInteger { return VEC.sqrt(x, primeModulus).toPositive() 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 83541e7..cec3f56 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroup.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/core/intgroup/IntGroup.kt @@ -30,8 +30,6 @@ class ProductionGroupContext( val r: BigInteger val oneModP: ProductionElementModP val gModP: ProductionElementModP - val gInvModP by lazy { gPowP(qMinus1Q) } - val gSquaredModP: ProductionElementModP val zeroModQ: ProductionElementModQ val oneModQ: ProductionElementModQ val twoModQ: ProductionElementModQ @@ -48,7 +46,6 @@ class ProductionGroupContext( r = rBytes.toBigInteger() oneModP = ProductionElementModP(1U.toBigInteger(), this) gModP = ProductionElementModP(g, this).acceleratePow() as ProductionElementModP - gSquaredModP = ProductionElementModP((g * g).mod(p), this) zeroModQ = ProductionElementModQ(0U.toBigInteger(), this) oneModQ = ProductionElementModQ(1U.toBigInteger(), this) twoModQ = ProductionElementModQ(2U.toBigInteger(), this) @@ -70,12 +67,6 @@ class ProductionGroupContext( override val G_MOD_P get() = gModP - override val GINV_MOD_P - get() = gInvModP - - override val G_SQUARED_MOD_P - get() = gSquaredModP - override val ZERO_MOD_Q get() = zeroModQ @@ -189,7 +180,7 @@ class ProductionElementModQ(internal val element: BigInteger, val groupContext: override fun isZero() = element == BigInteger.ZERO - override fun inBounds() = element >= BigInteger.ZERO && element < groupContext.q + override fun isValidElement() = element >= BigInteger.ZERO && element < groupContext.q override operator fun compareTo(other: ElementModQ): Int = element.compareTo(other.getCompat(groupContext)) @@ -238,8 +229,9 @@ open class ProductionElementModP(internal val element: BigInteger, val groupCont override operator fun compareTo(other: ElementModP): Int = element.compareTo(other.getCompat(groupContext)) /** - * 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 + * 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() diff --git a/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyDecryption.kt b/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyDecryption.kt index 8cfd048..9ee18dd 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyDecryption.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyDecryption.kt @@ -75,7 +75,7 @@ class VerifyDecryption( continue } - if (!selection.proof.r.inBounds()) { + if (!selection.proof.r.isValidElement()) { val what = if (isBallot) "12.A" else "9.A" serrs.add(" $what response out of bounds") } @@ -128,7 +128,7 @@ class VerifyDecryption( private fun verifyContestData(decryptedContestData: DecryptedTallyOrBallot.DecryptedContestData, errs: ErrorMessages){ // (11.A,14.A) The given value v is in the set Zq. - if (!decryptedContestData.proof.r.inBounds()) { + if (!decryptedContestData.proof.r.isValidElement()) { errs.add(" (11.A,14.A) The value v is not in the set Zq.") } if (!decryptedContestData.proof.verifyContestDataDecryption(publicKey, extendedBaseHash, decryptedContestData.beta, decryptedContestData.encryptedContestData)) { diff --git a/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyPreEncryptedBallots.kt b/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyPreEncryptedBallots.kt index 271b4b0..6639d48 100644 --- a/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyPreEncryptedBallots.kt +++ b/src/main/kotlin/org/cryptobiotic/eg/verifier/VerifyPreEncryptedBallots.kt @@ -7,11 +7,12 @@ import org.cryptobiotic.util.ErrorMessages ////////////////////////////////////////////////////////////////////////////// // pre-encryption +// TODO Is verification boxes 15, 16 the same as 5 and 6? /* Every step of verification that applies to traditional ElectionGuard ballots also applies to pre- encrypted ballots – with the exception of the process for computing confirmation codes. However, -52there are some additional verification steps that must be applied to pre-encrypted ballots. Specifi- +there are some additional verification steps that must be applied to pre-encrypted ballots. Specifi- cally, the following verifications should be done for every pre-encrypted cast ballot contained in the election record. • The ballot confirmation code correctly matches the hash of all contest hashes on the ballot diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/GroupTest.kt b/src/test/kotlin/org/cryptobiotic/eg/core/GroupTest.kt index 9d778b1..9994d58 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/GroupTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/GroupTest.kt @@ -58,7 +58,7 @@ class GroupTest { fun qInBounds(context: GroupContext) { runTest { - forAll(propTestFastConfig, elementsModQ(context)) { it.inBounds() } + forAll(propTestFastConfig, elementsModQ(context)) { it.isValidElement() } } } @@ -128,7 +128,7 @@ class GroupTest { checkAll(propTestFastConfig, Arb.int(min = 0, max = intTestQ - 1)) { i -> val iq = context.uIntToElementModQ(i.toUInt()) val q = context.ZERO_MOD_Q - iq - assertTrue(q.inBounds()) + assertTrue(q.isValidElement()) assertEquals(context.ZERO_MOD_Q, q + iq) } } diff --git a/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/PowRadixTest.kt b/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/PowRadixTest.kt index 650262b..de7186b 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/PowRadixTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/PowRadixTest.kt @@ -117,7 +117,7 @@ class PowRadixTest { assertEquals(ctxSlow.ONE_MOD_P, powRadix.pow(0.toElementModQ(ctxSlow))) assertEquals(ctxSlow.G_MOD_P, powRadix.pow(1.toElementModQ(ctxSlow))) - assertEquals(ctxSlow.G_SQUARED_MOD_P, powRadix.pow(2.toElementModQ(ctxSlow))) + // assertEquals(ctxSlow.G_SQUARED_MOD_P, powRadix.pow(2.toElementModQ(ctxSlow))) // check fewer cases because it's so much slower checkAll(propTestFastConfig, elementsModQ(ctxSlow)) { e -> 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 bf46416..c3ec4fc 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/TinyGroup.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/core/intgroup/TinyGroup.kt @@ -74,9 +74,9 @@ internal class TinyGroupContext( get() = oneModP override val G_MOD_P: ElementModP get() = gModP - override val GINV_MOD_P: ElementModP + val GINV_MOD_P: ElementModP get() = gInvModP - override val G_SQUARED_MOD_P: ElementModP + val G_SQUARED_MOD_P: ElementModP get() = gSquaredModP override val ZERO_MOD_Q: ElementModQ get() = zeroModQ @@ -294,7 +294,7 @@ internal class TinyElementModQ(val element: UInt, val groupContext: TinyGroupCon override val context: GroupContext get() = groupContext - override fun inBounds(): Boolean = element < groupContext.q + override fun isValidElement(): Boolean = element < groupContext.q override fun isZero(): Boolean = element == 0U diff --git a/src/test/kotlin/org/cryptobiotic/eg/decrypt/LagrangeTest.kt b/src/test/kotlin/org/cryptobiotic/eg/decrypt/LagrangeTest.kt index ae0acc1..19a55e3 100644 --- a/src/test/kotlin/org/cryptobiotic/eg/decrypt/LagrangeTest.kt +++ b/src/test/kotlin/org/cryptobiotic/eg/decrypt/LagrangeTest.kt @@ -110,11 +110,11 @@ class LagrangeTest { present: List) { val available = trustees.filter {present.contains(it.xCoordinate())} val lagrangeCoefficients = available.associate { it.id to computeLagrangeCoefficient(group, it.xCoordinate, present) } - lagrangeCoefficients.values.forEach { assertTrue( it.inBounds()) } + lagrangeCoefficients.values.forEach { assertTrue( it.isValidElement()) } val weightedSum = group.addQ( trustees.map { - assertTrue(it.computeSecretKeyShare().inBounds()) + assertTrue(it.computeSecretKeyShare().isValidElement()) val coeff = lagrangeCoefficients[it.id] ?: throw IllegalArgumentException() it.computeSecretKeyShare() * coeff }