Skip to content

Commit

Permalink
spec 2.0 hashing, KeyCeremony
Browse files Browse the repository at this point in the history
hash integers are 4 bytes
manifest hash: file preceeded by file size
redo intToByteArray() in Hash
extendedBaseHash uses K, not Kij
updated spec references
  • Loading branch information
JohnLCaron committed Aug 20, 2023
1 parent a573a80 commit a3fc322
Show file tree
Hide file tree
Showing 11 changed files with 117 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
}
Expand All @@ -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,
)
}

Expand Down
31 changes: 20 additions & 11 deletions egklib/src/commonMain/kotlin/electionguard/core/Hash.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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 {
Expand Down Expand Up @@ -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

/**
Expand Down
4 changes: 2 additions & 2 deletions egklib/src/commonMain/kotlin/electionguard/core/Schnorr.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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 ℓ
Expand All @@ -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,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,18 @@ fun keyCeremonyExchange(trustees: List<KeyCeremonyTrusteeIF>, 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<Result<Boolean, String>> = mutableListOf()
Expand Down Expand Up @@ -145,13 +145,12 @@ data class KeyCeremonyResults(
config: ElectionConfig,
metadata: Map<String, String> = 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<ElementModP> = 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<Guardian> = publicKeysSorted.map { makeGuardian(it) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down Expand Up @@ -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<EncryptedKeyShare, String> {
var pkeyShare = othersShareOfMyKey[otherGuardian]
if (pkeyShare == null) {
Expand All @@ -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)
Expand Down Expand Up @@ -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}'")
}
Expand Down Expand Up @@ -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}'"))
Expand All @@ -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,
Expand All @@ -201,25 +202,26 @@ 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
// with encodings of the numbers i and ℓ of the sending and receiving guardians as the Context, and the final two bytes
// 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.
Expand All @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,7 @@ fun runEncryptDecryptBallot(

//////////////////////////////////////////////////////////
if (writeout) {
val commitments: MutableList<ElementModP> = 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,13 @@ class DecryptBallotTestVector {
keyCeremonyExchange(keyCeremonyTrustees)

val publicKeys = mutableListOf<ElementModP>()
val allCommitments = mutableListOf<ElementModP>()
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")
Expand Down
Loading

0 comments on commit a3fc322

Please sign in to comment.