From a3fc3221efc7069d7486099b2b7f238e0fec1226 Mon Sep 17 00:00:00 2001 From: JohnLCaron Date: Sun, 20 Aug 2023 10:40:32 -0600 Subject: [PATCH] spec 2.0 hashing, KeyCeremony hash integers are 4 bytes manifest hash: file preceeded by file size redo intToByteArray() in Hash extendedBaseHash uses K, not Kij updated spec references --- .../electionguard/ballot/ElectionConfig.kt | 5 +- .../kotlin/electionguard/core/Hash.kt | 31 ++++++---- .../kotlin/electionguard/core/Schnorr.kt | 4 +- .../keyceremony/ElectionPolynomial.kt | 8 ++- .../electionguard/keyceremony/KeyCeremony.kt | 23 ++++--- .../keyceremony/KeyCeremonyTrustee.kt | 35 ++++++----- .../decryptBallot/EncryptDecryptBallotTest.kt | 4 +- .../testvectors/DecryptBallotTestVector.kt | 4 +- .../testvectors/KeyCeremonyTestVector.kt | 12 ++-- .../testvectors/ParametersTestVector.kt | 61 +++++++++++++++---- .../testvectors/TallyDecryptionTestVector.kt | 4 +- 11 files changed, 117 insertions(+), 74 deletions(-) diff --git a/egklib/src/commonMain/kotlin/electionguard/ballot/ElectionConfig.kt b/egklib/src/commonMain/kotlin/electionguard/ballot/ElectionConfig.kt index 65398786..b2ebf898 100644 --- a/egklib/src/commonMain/kotlin/electionguard/ballot/ElectionConfig.kt +++ b/egklib/src/commonMain/kotlin/electionguard/ballot/ElectionConfig.kt @@ -143,6 +143,7 @@ fun manifestHash(Hp: UInt256, manifestBytes : ByteArray) : UInt256 { return hashFunction( Hp.bytes, 0x01.toByte(), + manifestBytes.size, // b(len(file), 4) ∥ b(file, len(file)) , section 5.1.5 manifestBytes, ) } @@ -153,8 +154,8 @@ fun electionBaseHash(Hp: UInt256, HM: UInt256, n : Int, k : Int) : UInt256 { Hp.bytes, 0x02.toByte(), HM.bytes, - n.toUShort(), - k.toUShort(), + n, + k, ) } diff --git a/egklib/src/commonMain/kotlin/electionguard/core/Hash.kt b/egklib/src/commonMain/kotlin/electionguard/core/Hash.kt index 3d9637a6..b1ff2888 100644 --- a/egklib/src/commonMain/kotlin/electionguard/core/Hash.kt +++ b/egklib/src/commonMain/kotlin/electionguard/core/Hash.kt @@ -3,7 +3,7 @@ package electionguard.core import io.ktor.utils.io.core.toByteArray /** - * The hash function H used in ElectionGuard is HMAC-SHA-256, i.e. HMAC instantiated with SHA-25639 . + * The hash function H used in ElectionGuard is HMAC-SHA-256, i.e. HMAC instantiated with SHA-256. * Therefore, H takes two byte arrays as inputs. * * The first input corresponds to the key in HMAC. The HMAC-SHA-256 specification allows arbitrarily long keys, @@ -15,11 +15,17 @@ import io.ktor.utils.io.core.toByteArray * The second input can have arbitrary length and is only restricted by the maximal input length * for SHA-256 and HMAC. Hence we view the function H formally as follows (understanding that * HMAC implementations pad the 32-byte keys to exactly 64 bytes by appending 32 00 bytes): - * H : B^32 × B* → B^32 , H(B0 ; B1 ) → HMAC-SHA-256(B0 , B1 ) ; spec 1.9 eq 104 + * H : B^32 × B* → B^32 , H(B0 ; B1 ) → HMAC-SHA-256(B0 , B1 ) ; spec 2.0.0 eq 105 + * + * ElectionGuard uses HMAC not as a keyed hash function with a secret key or a message authentication code, + * but instead uses it as a general purpose hash function to implement a random oracle. + * The first input is used to bind hash values to a specific election by including the parameter base + * hash Hp , the election base hash Hb or the extended base hash He . The second input consists of + * domain separation tags for different use cases and the actual data that is being hashed. * * @param key HMAC key. * @param elements Zero or more elements of any of the accepted types. - * @return A cryptographic hash of these elements, converted to strings and suitably concatenated. + * @return A cryptographic hash of these elements. */ fun hashFunction(key: ByteArray, vararg elements: Any): UInt256 { val hmac = HmacSha256(key) @@ -45,9 +51,9 @@ private fun HmacSha256.addToHash(element : Any) { is UInt256 -> element.bytes is Element -> element.byteArray() is String -> element.toByteArray() - is Short -> ByteArray(2) { if (it == 0) (element / 256).toByte() else (element % 256).toByte() } - is UShort -> ByteArray(2) { if (it == 0) (element / U256).toByte() else (element % U256).toByte() } - is Int -> numberToByteArray(element as Number, 4) // only used in Nonces + // is Short -> ByteArray(2) { if (it == 0) (element / 256).toByte() else (element % 256).toByte() } + // is UShort -> ByteArray(2) { if (it == 0) (element / U256).toByte() else (element % U256).toByte() } + is Int -> intToByteArray(element) else -> throw IllegalArgumentException("unknown type in hashElements: ${element::class}") } this.update(ba) @@ -56,6 +62,13 @@ private fun HmacSha256.addToHash(element : Any) { private val U256 = 256.toUShort() +private fun intToByteArray (data: Int) : ByteArray { + require (data >= 0) // 0 <= data < 2^31 // LOOK do we need negative numbers?? + val dataLong = data.toLong() + return ByteArray(4) { i -> (dataLong shr (i * 8)).toByte() } +} + +//////////////////////////////////// //// test concatenation vs update fun hashFunctionConcat(key: ByteArray, vararg elements: Any): UInt256 { @@ -91,17 +104,13 @@ private fun hashElementsToByteArray(element : Any) : ByteArray { is String -> element.toByteArray() is Short -> ByteArray(2) { if (it == 0) (element / 256).toByte() else (element % 256).toByte() } is UShort -> ByteArray(2) { if (it == 0) (element / U256).toByte() else (element % U256).toByte() } - is Int -> numberToByteArray(element as Number, 4) + is Int -> intToByteArray(element) else -> throw IllegalArgumentException("unknown type in hashElements: ${element::class}") } return ba } } -private fun numberToByteArray (data: Number, size: Int = 4) : ByteArray = - ByteArray (size) {i -> (data.toLong() shr (i*8)).toByte()} - - ////////////////////////////// KDF /** diff --git a/egklib/src/commonMain/kotlin/electionguard/core/Schnorr.kt b/egklib/src/commonMain/kotlin/electionguard/core/Schnorr.kt index 67e5e51d..f9d33a0d 100644 --- a/egklib/src/commonMain/kotlin/electionguard/core/Schnorr.kt +++ b/egklib/src/commonMain/kotlin/electionguard/core/Schnorr.kt @@ -55,10 +55,10 @@ fun ElGamalKeypair.schnorrProof( coeff: Int, // j nonce: ElementModQ = context.randomElementModQ() // u_ij ): SchnorrProof { - // spec 1.9, p 19 + // spec 2.0.0, eq 12 val context = compatibleContextOrFail(publicKey.key, secretKey.key, nonce) val h = context.gPowP(nonce) // eq 10 - val c = hashFunction(context.constants.hp.bytes, 0x10.toByte(), guardianXCoord.toUShort(), coeff.toUShort(), publicKey.key, h).toElementModQ(context) // eq 11 + val c = hashFunction(context.constants.hp.bytes, 0x10.toByte(), guardianXCoord, coeff, publicKey.key, h).toElementModQ(context) // eq 11 val v = nonce - secretKey.key * c // response return SchnorrProof(publicKey.key, c, v) diff --git a/egklib/src/commonMain/kotlin/electionguard/keyceremony/ElectionPolynomial.kt b/egklib/src/commonMain/kotlin/electionguard/keyceremony/ElectionPolynomial.kt index 2cf24827..1491e393 100644 --- a/egklib/src/commonMain/kotlin/electionguard/keyceremony/ElectionPolynomial.kt +++ b/egklib/src/commonMain/kotlin/electionguard/keyceremony/ElectionPolynomial.kt @@ -2,7 +2,7 @@ package electionguard.keyceremony import electionguard.core.* -/** Pi(x), spec 1.9, section 3.2.1. Must be kept secret. */ +/** Pi(x), spec 2.0.0, section 3.2.1. Must be kept secret. */ data class ElectionPolynomial( val guardianId: String, // ith guardian @@ -28,6 +28,7 @@ data class ElectionPolynomial( var result: ElementModQ = group.ZERO_MOD_Q var xcoordPower: ElementModQ = group.ONE_MOD_Q + // spec 2.0.0, p 22, eq 9 for (coefficient in this.coefficients) { val term = coefficient * xcoordPower result += term @@ -40,7 +41,7 @@ data class ElectionPolynomial( /** * Calculate g^Pi(xcoord) mod p = Product ((K_i,j)^xcoord^j) mod p, j = 0, quorum-1. * Used to test secret key share by KeyCeremonyTrustee, and verifying results in TallyDecryptor. - * spec 1.9, sec 3.2.2 eq 19: + * spec 2.0.0, sec 3.2.2, p 24, eq 21: */ fun calculateGexpPiAtL( xcoord: Int, // evaluated at xcoord ℓ @@ -59,7 +60,7 @@ fun calculateGexpPiAtL( return result } -/** Generate random coefficients for a polynomial of degree quorum-1. spec 1.9, p 19, eq 8 and 9. */ +/** Generate random coefficients for a polynomial of degree quorum-1. spec 2.0.0, p 22, eq 9 and 10. */ fun GroupContext.generatePolynomial( guardianId: String, guardianXCoord: Int, @@ -78,6 +79,7 @@ fun GroupContext.generatePolynomial( return ElectionPolynomial(guardianId, coefficients, commitments, proofs) } +// possibly only used in testing? fun GroupContext.regeneratePolynomial( guardianId: String, guardianXCoord: Int, diff --git a/egklib/src/commonMain/kotlin/electionguard/keyceremony/KeyCeremony.kt b/egklib/src/commonMain/kotlin/electionguard/keyceremony/KeyCeremony.kt index b179e5b4..d6eabb7f 100644 --- a/egklib/src/commonMain/kotlin/electionguard/keyceremony/KeyCeremony.kt +++ b/egklib/src/commonMain/kotlin/electionguard/keyceremony/KeyCeremony.kt @@ -85,18 +85,18 @@ fun keyCeremonyExchange(trustees: List, allowEncryptedFail } } - // spec 1.9, p 21 + // spec 2.0.0, p 24 "Share verification" // If the recipient guardian Gℓ reports not receiving a suitable value Pi (ℓ), it becomes incumbent on the // sending guardian Gi to publish this Pi (ℓ) together with the nonce ξi,ℓ it used to encrypt Pi (ℓ) // under the public key Kℓ of recipient guardian Gℓ . If guardian Gi fails to produce a suitable Pi (ℓ) // and nonce ξi,ℓ that match both the published encryption and the above equation, it should be - // excluded from the election and the key generation process should be restarted with an alternate guardian. - // If, however, the published Pi (ℓ) and ξi,ℓ satisfy both the published encryption and the - // equation (19) above, the claim of malfeasance is dismissed, and the key generation process continues undeterred. - // It is also permissible to dismiss any guardian that makes a false claim of malfeasance. However, this is not - // required as the sensitive information that is released as a result of the claim could have been released by the - // claimant in any case. - // TODO should KeyShare include ξi,ℓ + // excluded from the election and the key generation process should be restarted with an alternate + // guardian. If, however, the published Pi (ℓ) and ξi,ℓ satisfy both the published encryption and the + // equation above, the claim of malfeasance is dismissed, and the key generation process continues undeterred. + // footnote 28 It is also permissible to dismiss any guardian that makes a false claim of malfeasance. However, this is not + // required as the sensitive information that is released as a result of the claim could have been released by the claimant + // in any case. + // TODO KeyShare should include ξi,ℓ // Phase Two: if any secretKeyShares fail to validate, send and validate KeyShares val keyResults: MutableList> = mutableListOf() @@ -145,13 +145,12 @@ data class KeyCeremonyResults( config: ElectionConfig, metadata: Map = emptyMap(), ): ElectionInitialized { + // spec 2.0.0 p.25, eq 8. val jointPublicKey: ElementModP = publicKeysSorted.map { it.publicKey().key }.reduce { a, b -> a * b } - // He = H(HB ; 12, K, K1,0 , K1,1 , . . . , K1,k−1 , K2,0 , . . . , Kn,k−2 , Kn,k−1 ) spec 1.9 p.22, eq 20. - val commitments: MutableList = mutableListOf() - publicKeysSorted.forEach { commitments.addAll(it.coefficientCommitments()) } - val extendedBaseHash = hashFunction(config.electionBaseHash.bytes, 0x12.toByte(), jointPublicKey, commitments) + // He = H(HB ; 0x12, K) ; spec 2.0.0 p.25, eq 23. + val extendedBaseHash = hashFunction(config.electionBaseHash.bytes, 0x12.toByte(), jointPublicKey) val guardians: List = publicKeysSorted.map { makeGuardian(it) } diff --git a/egklib/src/commonMain/kotlin/electionguard/keyceremony/KeyCeremonyTrustee.kt b/egklib/src/commonMain/kotlin/electionguard/keyceremony/KeyCeremonyTrustee.kt index bac40624..db3e4b2e 100644 --- a/egklib/src/commonMain/kotlin/electionguard/keyceremony/KeyCeremonyTrustee.kt +++ b/egklib/src/commonMain/kotlin/electionguard/keyceremony/KeyCeremonyTrustee.kt @@ -51,8 +51,8 @@ open class KeyCeremonyTrustee( )) } - // The value P(i) == Gi’s share of the secret key s = (s1 + s2 + · · · + sn ) - // == (P1 (ℓ) + P2 (ℓ) + · · · + Pn (ℓ)) mod q. eq 66. + // The value P(i) == G_i’s share of the secret key s = (s1 + s2 + · · · + sn ) + // == (P1 (ℓ) + P2 (ℓ) + · · · + Pn (ℓ)) mod q. spec 2.0.0, eq 65. override fun keyShare(): ElementModQ { var result: ElementModQ = polynomial.valueAt(group, xCoordinate) myShareOfOthers.values.forEach{ result += it.yCoordinate } @@ -89,7 +89,7 @@ open class KeyCeremonyTrustee( return errors.merge() } - /** Create another guardian's share of my key, encrypted. spec 1.9 p 20 Share encryption. */ + /** Create another guardian's share of my key, encrypted. */ override fun encryptedKeyShareFor(otherGuardian: String): Result { var pkeyShare = othersShareOfMyKey[otherGuardian] if (pkeyShare == null) { @@ -98,7 +98,7 @@ open class KeyCeremonyTrustee( // Compute my polynomial's y value at the other's x coordinate = Pi(ℓ) val Pil: ElementModQ = polynomial.valueAt(group, other.guardianXCoordinate) - // My encryption of Pil, using the other's public key. spec 1.9, section 3.2.2 eq 17. + // My encryption of Pil, using the other's public key. spec 2.0.0, section 3.2.2, p 24, eq 18. val EPil : HashedElGamalCiphertext = shareEncryption(Pil, other) pkeyShare = PrivateKeyShare(this.xCoordinate, this.id, other.guardianId, EPil, Pil) @@ -129,7 +129,7 @@ open class KeyCeremonyTrustee( // Having decrypted each Pi (ℓ), guardian Gℓ can now verify its validity against // the commitments Ki,0 , Ki,1 , . . . , Ki,k−1 made by Gi to its coefficients by confirming that - // g^Pi(ℓ) = Prod{ (Kij)^ℓ^j }, for j=0..k-1 eq 19 + // g^Pi(ℓ) = Prod{ (Kij)^ℓ^j }, for j=0..k-1 ; spec 2.0.0 eq 21 if (group.gPowP(expectedPil) != calculateGexpPiAtL(this.xCoordinate, publicKeys.coefficientCommitments())) { return Err("Trustee '$id' failed to validate EncryptedKeyShare for missingGuardianId '${share.polynomialOwner}'") } @@ -164,7 +164,7 @@ open class KeyCeremonyTrustee( if (otherKeys == null) { errors.add(Err("Trustee '$id', does not have public key for missingGuardianId '${keyShare.polynomialOwner}'")) } else { - // check if the Pi(ℓ) that was sent satisfies spec 1.9, eq 12. + // check if the Pi(ℓ) that was sent satisfies eq 21. // g^Pi(ℓ) = Prod{ (Kij)^ℓ^j }, for j=0..k-1 if (group.gPowP(keyShare.yCoordinate) != calculateGexpPiAtL(this.xCoordinate, otherKeys.coefficientCommitments())) { errors.add(Err("Trustee '$id' failed to validate KeyShare for missingGuardianId '${keyShare.polynomialOwner}'")) @@ -190,6 +190,7 @@ open class KeyCeremonyTrustee( private val context = "share_encrypt" // guardian Gi encryption Eℓ of Pi(ℓ) at another guardian's Gℓ coordinate ℓ + // see section 3.2.2 "share encryption" open fun shareEncryption( Pil : ElementModQ, other: PublicKeys, @@ -201,12 +202,13 @@ open class KeyCeremonyTrustee( val i = xCoordinate.toUShort() val l = other.guardianXCoordinate.toUShort() - // (alpha, beta) = (g^R mod p, K^R mod p) spec 1.9, p 20, eq 13 + // (alpha, beta) = (g^R mod p, K^R mod p) spec 2.0.0, eq 14 // by encrypting a zero, we achieve exactly this val (alpha, beta) = 0.encrypt(K_l, nonce) - // ki,ℓ = H(HP ; 11, i, ℓ, Kℓ , αi,ℓ , βi,ℓ ) eq 14 + // ki,ℓ = H(HP ; 11, i, ℓ, Kℓ , αi,ℓ , βi,ℓ ) spec 2.0.0, eq 15 val kil = hashFunction(hp, 0x11.toByte(), i, l, K_l.key, alpha, beta).bytes + // footnote 27 // This key derivation uses the KDF in counter mode from SP 800-108r1. The second input to HMAC contains // the counter in the first byte, the UTF-8 encoding of the string "share enc keys" as the Label (encoding is denoted // by b(. . . ), see Section 5.1.4), a separation 00 byte, the UTF-8 encoding of the string "share encrypt" concatenated @@ -214,12 +216,12 @@ open class KeyCeremonyTrustee( // specifying the length of the output key material as 512 bits in total. // context = b(”share encrypt”) ∥ b(i, 2) ∥ b(ℓ, 2) - // k0 = HMAC(ki,ℓ , 01 ∥ label ∥ 00 ∥ context ∥ 0200) eq 15 + // k0 = HMAC(ki,ℓ , 01 ∥ label ∥ 00 ∥ context ∥ 0200) spec 2.0.0, eq 16 val k0 = hmacFunction(kil, 0x01.toByte(), label, 0x00.toByte(), context, i, l, 512.toShort()).bytes - // k1 = HMAC(ki,ℓ , 02 ∥ label ∥ 00 ∥ context ∥ 0200), eq 16 + // k1 = HMAC(ki,ℓ , 02 ∥ label ∥ 00 ∥ context ∥ 0200), spec 2.0.0, eq 17 val k1 = hmacFunction(kil, 0x02.toByte(), label, 0x00.toByte(), context, i, l, 512.toShort()).bytes - // eq 18 + // spec 2.0.0, eq 19 // C0 = g^nonce == alpha val c0: ElementModP = alpha // C1 = b(Pi(ℓ),32) ⊕ k1, • The symbol ⊕ denotes bitwise XOR. @@ -232,11 +234,12 @@ open class KeyCeremonyTrustee( } // Share decryption. After receiving the ciphertext (Ci,ℓ,0 , Ci,ℓ,1 , Ci,ℓ,2 ) from guardian Gi , guardian - // Gℓ decrypts it by computing βi,ℓ = (Ci,ℓ,0 )sℓ mod p, setting αi,ℓ = Ci,ℓ,0 and obtaining ki,ℓ = - // H(HP ; 11, i, ℓ, Kℓ , αi,ℓ , βi,ℓ ). + // Gℓ decrypts it by computing βi,ℓ = (Ci,ℓ,0 )sℓ mod p, setting αi,ℓ = Ci,ℓ,0 and obtaining + // ki,ℓ = H(HP ; 11, i, ℓ, Kℓ , αi,ℓ , βi,ℓ ). // Now the MAC key k0 and the encryption key k1 can be computed as - // above in Equations (15) and (16), which allows Gℓ to verify the validity of the MAC, namely that - // Ci,ℓ,2 = HMAC(k0 , b(Ci,ℓ,0 , 512) ∥ Ci,ℓ,1 ). If the MAC verifies, Gℓ decrypts b(Pi (ℓ), 32) = Ci,ℓ,1 ⊕k1 . + // above in Equations (16) and (17), which allows Gℓ to verify the validity of the MAC, namely that + // Ci,ℓ,2 = HMAC(k0 , b(Ci,ℓ,0 , 512) ∥ Ci,ℓ,1 ). + // If the MAC verifies, then Gℓ decrypts b(Pi (ℓ), 32) = Ci,ℓ,1 ⊕k1 . fun shareDecryption(share: EncryptedKeyShare): ByteArray? { // αi,ℓ = Ci,ℓ,0 // βi,ℓ = (Ci,ℓ,0 )sℓ mod p @@ -249,7 +252,7 @@ open class KeyCeremonyTrustee( val hp = group.constants.hp.bytes val kil = hashFunction(hp, 0x11.toByte(), share.ownerXcoord.toShort(), xCoordinate.toShort(), electionPublicKey(), alpha, beta).bytes - // Now the MAC key k0 and the encryption key k1 can be computed as above in Equations (15) and (16) + // Now the MAC key k0 and the encryption key k1 can be computed as above in Equations (16) and (17) // context = b(”share encrypt”) ∥ b(i, 2) ∥ b(ℓ, 2) // k0 = HMAC(ki,ℓ , 01 ∥ label ∥ 00 ∥ context ∥ 0200) eq 15 val k0 = hmacFunction(kil, 0x01.toByte(), label, 0x00.toByte(), context, share.ownerXcoord.toShort(), xCoordinate.toShort(), 512.toShort()).bytes diff --git a/egklib/src/commonTest/kotlin/electionguard/decryptBallot/EncryptDecryptBallotTest.kt b/egklib/src/commonTest/kotlin/electionguard/decryptBallot/EncryptDecryptBallotTest.kt index 3ee41a2d..f9e83cf2 100644 --- a/egklib/src/commonTest/kotlin/electionguard/decryptBallot/EncryptDecryptBallotTest.kt +++ b/egklib/src/commonTest/kotlin/electionguard/decryptBallot/EncryptDecryptBallotTest.kt @@ -100,9 +100,7 @@ fun runEncryptDecryptBallot( ////////////////////////////////////////////////////////// if (writeout) { - val commitments: MutableList = mutableListOf() - trustees.forEach { commitments.addAll(it.coefficientCommitments()) } - val extendedBaseHash = hashFunction(config.electionBaseHash.bytes, 0x12.toByte(), jointPublicKey, commitments) + val extendedBaseHash = hashFunction(config.electionBaseHash.bytes, 0x12.toByte(), jointPublicKey) val init = ElectionInitialized( config, diff --git a/egklib/src/jvmTest/kotlin/electionguard/testvectors/DecryptBallotTestVector.kt b/egklib/src/jvmTest/kotlin/electionguard/testvectors/DecryptBallotTestVector.kt index 735c238c..900058ce 100644 --- a/egklib/src/jvmTest/kotlin/electionguard/testvectors/DecryptBallotTestVector.kt +++ b/egklib/src/jvmTest/kotlin/electionguard/testvectors/DecryptBallotTestVector.kt @@ -62,15 +62,13 @@ class DecryptBallotTestVector { keyCeremonyExchange(keyCeremonyTrustees) val publicKeys = mutableListOf() - val allCommitments = mutableListOf() keyCeremonyTrustees.forEach { trustee -> publicKeys.add(trustee.electionPublicKey()) - allCommitments.addAll(trustee.coefficientCommitments()) } val electionBaseHash = UInt256.random() val publicKey = publicKeys.reduce { a, b -> a * b } - val extendedBaseHash = hashFunction(electionBaseHash.bytes, 0x12.toByte(), publicKey, allCommitments) + val extendedBaseHash = hashFunction(electionBaseHash.bytes, 0x12.toByte(), publicKey) val ebuilder = ManifestBuilder("makeDecryptBallotTestVector") val manifest: Manifest = ebuilder.addContest("onlyContest") diff --git a/egklib/src/jvmTest/kotlin/electionguard/testvectors/KeyCeremonyTestVector.kt b/egklib/src/jvmTest/kotlin/electionguard/testvectors/KeyCeremonyTestVector.kt index f73ba3c6..154415a1 100644 --- a/egklib/src/jvmTest/kotlin/electionguard/testvectors/KeyCeremonyTestVector.kt +++ b/egklib/src/jvmTest/kotlin/electionguard/testvectors/KeyCeremonyTestVector.kt @@ -55,7 +55,6 @@ class KeyCeremonyTestVector { fun makeKeyCeremonyTestVector(publish : Boolean = true) { val publicKeys = mutableListOf() - val allCommitments = mutableListOf() repeat(numberOfGuardians) { val guardianXCoord = it + 1 @@ -76,26 +75,25 @@ class KeyCeremonyTestVector { } publicKeys.add(commitments[0]) - allCommitments.addAll(commitments) guardians.add( GuardianJson( "Guardian$guardianXCoord", guardianXCoord, coefficients.map { it.publishJson() }, nonces.map { it.publishJson() }, - "Generate Schnorr proofs for Guardian coefficients. spec 1.9, section 3.2.2", + "Generate Schnorr proofs for Guardian coefficients (section 3.2.2)", proofs, ) ) } val expectedPublicKey = publicKeys.reduce { a, b -> a * b } - val extendedBaseHash = hashFunction(electionBaseHash.bytes, 0x12.toByte(), expectedPublicKey, allCommitments) + val extendedBaseHash = hashFunction(electionBaseHash.bytes, 0x12.toByte(), expectedPublicKey) val keyCeremonyTestVector = KeyCeremonyTestVector( "Test KeyCeremony guardian creation", guardians, - "Generate joint public key (eq 7) and extended base hash (eq 20)", + "Generate joint public key (eq 8) and extended base hash (eq 23)", electionBaseHash.publishJson(), expectedPublicKey.publishJson(), extendedBaseHash.publishJson(), @@ -123,7 +121,6 @@ class KeyCeremonyTestVector { } val publicKeys = mutableListOf() - val allCommitments = mutableListOf() keyCeremonyTestVector.guardians.forEach { guardianJson -> val guardianXCoord = guardianJson.coordinate val secretKey = guardianJson.polynomial_coefficients[0].import(group) @@ -133,7 +130,6 @@ class KeyCeremonyTestVector { guardianJson.polynomial_coefficients.forEach { val privateKey = it.import(group) val publicKey = group.gPowP(privateKey) - allCommitments.add(publicKey) val keypair = ElGamalKeypair(ElGamalSecretKey(privateKey), ElGamalPublicKey(publicKey)) val nonce = guardianJson.proof_nonces[coeffIdx].import(group) @@ -153,7 +149,7 @@ class KeyCeremonyTestVector { // HE = H(HB ; 12, K, K1,0 , K1,1 , . . . , K1,k−1 , K2,0 , . . . , Kn,k−2 , Kn,k−1 ) val electionBaseHash = keyCeremonyTestVector.election_base_hash.import() - val extendedBaseHash = hashFunction(electionBaseHash.bytes, 0x12.toByte(), publicKey, allCommitments) + val extendedBaseHash = hashFunction(electionBaseHash.bytes, 0x12.toByte(), publicKey) assertEquals(keyCeremonyTestVector.expected_extended_base_hash.import(), extendedBaseHash) } diff --git a/egklib/src/jvmTest/kotlin/electionguard/testvectors/ParametersTestVector.kt b/egklib/src/jvmTest/kotlin/electionguard/testvectors/ParametersTestVector.kt index 19182a0c..fc133a72 100644 --- a/egklib/src/jvmTest/kotlin/electionguard/testvectors/ParametersTestVector.kt +++ b/egklib/src/jvmTest/kotlin/electionguard/testvectors/ParametersTestVector.kt @@ -38,6 +38,14 @@ class ParametersTestVector { val expected : UInt256Json, ) + @Serializable + data class ManifestHash( + val task : String, + val parameterBaseHash : UInt256Json, + val manifest : String, + val expected : UInt256Json, + ) + @Serializable data class ElectionBaseHash( val task : String, @@ -52,6 +60,7 @@ class ParametersTestVector { data class ParametersTestVector( val desc: String, val parameter_base_hash : ParameterBaseHash, + val manifest_hash : ManifestHash, val election_base_hash : ElectionBaseHash, ) @@ -66,27 +75,35 @@ class ParametersTestVector { val primesJson = PrimesJson(constants.largePrime.toHex(), constants.smallPrime.toHex(), constants.generator.toHex(), ) val expectedParameterBaseHash = parameterBaseHash(constants) val parameterBaseHash = ParameterBaseHash( - "Generate parameter base hash. spec 1.9, p 15, eq 4", + "Generate parameter base hash. spec 2.0.0, eq 4", protocolVersion, primesJson, expectedParameterBaseHash.publishJson()) + + val fakeManifest = "your manifest goes here" + val expectedManifestHash = manifestHash(expectedParameterBaseHash, fakeManifest.toByteArray()) + val manifestTestVector = ManifestHash( + "Generate manifest hash. spec 2.0.0, eq 6", + expectedParameterBaseHash.publishJson(), + fakeManifest, + expectedManifestHash.publishJson(), + ) + val n = 6 val k = 4 - val date = "Nov 2, 1960" - val info = "Los Angeles County, general election" - val HM = UInt256.random() val electionBaseHash = ElectionBaseHash( - "Generate election base hash. spec 1.9, p 17, eq 6", + "Generate election base hash. spec 2.0.0, eq 7", expectedParameterBaseHash.publishJson(), - HM.publishJson(), + expectedManifestHash.publishJson(), n, k, - electionBaseHash(expectedParameterBaseHash, HM, n, k).publishJson(), + electionBaseHash(expectedParameterBaseHash, expectedManifestHash, n, k).publishJson(), ) val parametersTestVector = ParametersTestVector( - "Test hash function, generation of H_P and H_B", + "Test hash function, generation of Hp, Hm, and Hb", parameterBaseHash, + manifestTestVector, electionBaseHash ) println(jsonFormat.encodeToString(parametersTestVector)) @@ -107,7 +124,7 @@ class ParametersTestVector { val parameterV = parametersTestVector.parameter_base_hash val version = parameterV.protocolVersion.toByteArray() - val HV = ByteArray(32) { if (it < 4) version[it] else 0 } + val HV = ByteArray(32) { if (it < version.size) version[it] else 0 } val actualHp = hashFunction( HV, 0x00.toByte(), @@ -117,14 +134,36 @@ class ParametersTestVector { ) assertEquals(parameterV.expected.import(), actualHp) + val electionM = parametersTestVector.manifest_hash + val actualM = hashFunction( + electionM.parameterBaseHash.import().bytes, + 0x01.toByte(), + electionM.manifest.length, + electionM.manifest.toByteArray(), + ) + assertEquals(electionM.expected.import(), actualM) + val actualM2 = manifestHash( + electionM.parameterBaseHash.import(), + electionM.manifest.toByteArray(), + ) + assertEquals(electionM.expected.import(), actualM2) + val electionV = parametersTestVector.election_base_hash - val actualHb = electionBaseHash( + val actualHb = hashFunction( + electionV.parameterBaseHash.import().bytes, + 0x02.toByte(), + electionV.manifest_hash.import().bytes, + electionV.numberOfGuardians, + electionV.quorum, + ) + assertEquals(electionV.expected.import(), actualHb) + val actualHb2 = electionBaseHash( electionV.parameterBaseHash.import(), electionV.manifest_hash.import(), electionV.numberOfGuardians, electionV.quorum, ) - assertEquals(electionV.expected.import(), actualHb) + assertEquals(electionV.expected.import(), actualHb2) } } \ No newline at end of file diff --git a/egklib/src/jvmTest/kotlin/electionguard/testvectors/TallyDecryptionTestVector.kt b/egklib/src/jvmTest/kotlin/electionguard/testvectors/TallyDecryptionTestVector.kt index 2c6e662a..4bed19b3 100644 --- a/egklib/src/jvmTest/kotlin/electionguard/testvectors/TallyDecryptionTestVector.kt +++ b/egklib/src/jvmTest/kotlin/electionguard/testvectors/TallyDecryptionTestVector.kt @@ -106,15 +106,13 @@ class TallyDecryptionTestVector( keyCeremonyExchange(keyCeremonyTrustees) val publicKeys = mutableListOf() - val allCommitments = mutableListOf() keyCeremonyTrustees.forEach { trustee -> publicKeys.add(trustee.electionPublicKey()) - allCommitments.addAll(trustee.coefficientCommitments()) } val electionBaseHash = UInt256.random() val publicKey = publicKeys.reduce { a, b -> a * b } - val extendedBaseHash = hashFunction(electionBaseHash.bytes, 0x12.toByte(), publicKey, allCommitments) + val extendedBaseHash = hashFunction(electionBaseHash.bytes, 0x12.toByte(), publicKey) val ebuilder = ManifestBuilder("makeBallotEncryptionTestVector") val manifest: Manifest = ebuilder.addContest("onlyContest")