-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #444 from JohnLCaron/reorgElgamal
Reorganize ElGamal.
- Loading branch information
Showing
8 changed files
with
183 additions
and
165 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Cryptography Notes for spec 2.0 | ||
|
||
_8/07/2023_ | ||
|
||
- EKM uses an optimized encoding of an encrypted ElGamal counter, proposed by [Pereira](https://perso.uclouvain.be/olivier.pereira/). Where | ||
ElectionGuard 1.0 defines $\mathrm{Encrypt}(g^a, r, m) = \left(g^r, g^{ar}g^m\right)$, | ||
EKM instead defines $\mathrm{Encrypt}(g^a, r, m) = \left(g^r, g^{a(r + m)}\right)$. | ||
This allows for one fewer exponentiation per encryption. EKM includes corresponding | ||
changes in its Chaum-Pedersen proofs and discrete-log engine to support this. | ||
|
||
- EKM further optimizes the Chaum-Pedersen proofs with a space-optimization from [Boneh and Shoup](http://toc.cryptobook.us/) that allows | ||
the larger elements-mod-p to be elided from the proofs because they can be recomputed by the verifier. | ||
|
||
- EKM includes a Chaum-Pedersen "range proof", which is a proof that a ciphertext | ||
corresponds to a plaintext from 0 to a given constant. These are a generalization of the | ||
earlier 0-or-1 disjunctive proofs. The size of the proof will be | ||
linear with respect to the size of the constant, and when the constant is "1", | ||
the proof will be the same size as the original disjunctive proof. | ||
|
||
- EKM defines the result of a hash function as a 256-bit unsigned integer `UInt256` type rather than a `ElementModQ` | ||
type. This simplifies the code in a variety of ways. The `UInt256` type is used in a number of other contexts, | ||
like HMAC keys, allowing those implementations to be independent of the ElGamal group parameters. | ||
For serialization, the output of `UInt256` type is identical to `ElementModQ`, so this is an internal | ||
difference compatible with other implementations. | ||
|
||
- Pereira's "pow-radix" precomputation, used for common bases like | ||
the group generator or a public key, replaces modular exponentiation with | ||
table lookups and multiplies. We support three table sizes. Batch computations | ||
might then use larger tables and gain greater speedups, while memory-limited | ||
computations would use smaller tables. | ||
|
||
- In this table, we transform the numbers to [Montgomery form](https://en.wikipedia.org/wiki/Montgomery_modular_multiplication), allowing us | ||
to avoid an expensive modulo operation after each multiply. HACL* has native support | ||
for this transformation, resulting in significant speedups. | ||
We also get modest speedups within the JVM. | ||
|
||
- ElectionGuard defines two sets of global parameters: using either 3072-bit | ||
or 4096-bit modular arithmetic. EKM supports both sets of parameters as well as a | ||
"tiny" set of 32-bit parameters, used to radically speed up the unit tests. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
90 changes: 90 additions & 0 deletions
90
egklib/src/commonMain/kotlin/electionguard/core/ElGamalCiphertext.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
package electionguard.core | ||
|
||
/** | ||
* An "exponential ElGamal ciphertext" (i.e., with the plaintext in the exponent to allow for | ||
* homomorphic addition). (See [ElGamal 1982](https://ieeexplore.ieee.org/abstract/document/1057074)) | ||
* | ||
* In a "normal" ElGamal encryption where the message goes into the exponent, a secret key `a` | ||
* with corresponding public key `g^a`, message `M` and nonce `R` would be encoded as the tuple | ||
* `(g^R, (g^a)^r * g^M)`. | ||
* | ||
* In this particular ElGamal implementation, we're instead encoding the ciphertext as | ||
* `(g^R, (g^a)^{R+M})`. This accelerates both the encryption process and the process of generating | ||
* the corresponding Chaum-Pedersen proofs. | ||
* | ||
* This also means that this ElGamal ciphertext is *not compatible with ElectionGuard 1.0*, but | ||
* is anticipated to be the standard for ElectionGuard 2.0 and later. | ||
*/ | ||
data class ElGamalCiphertext(val pad: ElementModP, val data: ElementModP) { | ||
override fun toString() = "pad=${pad.toStringShort()} data=${data.toStringShort()} " | ||
|
||
|
||
/** Decrypts using the secret key from the keypair. If the decryption fails, `null` is returned. */ | ||
fun decrypt(keypair: ElGamalKeypair): Int? { | ||
compatibleContextOrFail(pad, data, keypair.secretKey.key) | ||
val blind = pad powP keypair.secretKey.negativeKey | ||
val kPowM = data * blind | ||
|
||
return keypair.publicKey.dLog(kPowM) | ||
} | ||
|
||
/** Compute the share of the decryption from the secret key. */ | ||
fun computeShare(secretKey: ElGamalSecretKey): ElementModP { | ||
compatibleContextOrFail(pad, data, secretKey.key) | ||
return pad powP secretKey.key | ||
} | ||
|
||
fun decryptWithShares(publicKey: ElGamalPublicKey, shares: Iterable<ElementModP>): Int? { | ||
val sharesList = shares.toList() | ||
val context = compatibleContextOrFail(pad, data, publicKey.key, *(sharesList.toTypedArray())) | ||
val allSharesProductM: ElementModP = with(context) { sharesList.multP() } | ||
val decryptedValue: ElementModP = this.data / allSharesProductM | ||
return publicKey.dLog(decryptedValue) | ||
} | ||
|
||
/** Decrypts a message by knowing the nonce. If the decryption fails, `null` is returned. */ | ||
fun decryptWithNonce(publicKey: ElGamalPublicKey, nonce: ElementModQ, maxResult: Int = -1): Int? { | ||
compatibleContextOrFail(pad, data, publicKey.key, nonce) | ||
|
||
val blind = publicKey powP (-nonce) | ||
val kPowM = data * blind // data * blind = publicKey ^ m | ||
return publicKey.dLog(kPowM, maxResult) | ||
} | ||
|
||
/** Homomorphically "adds" two ElGamal ciphertexts together through piecewise multiplication. */ | ||
operator fun plus(o: ElGamalCiphertext): ElGamalCiphertext { | ||
compatibleContextOrFail(this.pad, this.data, o.pad, o.data) | ||
return ElGamalCiphertext(pad * o.pad, data * o.data) | ||
} | ||
|
||
// reencrypt with another nonce. used in mixnet | ||
fun reencrypt(publicKey: ElGamalPublicKey, nonce: ElementModQ): ElGamalCiphertext { | ||
// Encr(m) = (g^ξ, K^(m+ξ)) = (a, b) | ||
// ReEncr(m) = (g^(ξ+ξ'), K^(m+ξ+ξ')) = (a * g^ξ', b * K^ξ') | ||
// Encr(0) = (g^ξ', K^ξ') = (a', b'), so ReEncr(m) = (a * a', b * b') = Encr(0) * Encr(m) | ||
|
||
val group = publicKey.context | ||
val ap = group.gPowP(nonce) | ||
val bp = publicKey.key powP nonce | ||
return ElGamalCiphertext(this.pad * ap, this.data * bp) | ||
} | ||
|
||
} | ||
|
||
/** | ||
* Homomorphically "adds" a sequence of ElGamal ciphertexts through piecewise multiplication. | ||
* @throws ArithmeticException if the sequence is empty | ||
*/ | ||
fun List<ElGamalCiphertext>.encryptedSum(): ElGamalCiphertext? { | ||
return if (this.isEmpty()) null else this.reduce { a, b -> a + b } | ||
} | ||
|
||
/** Add two lists by component-wise multiplication */ | ||
fun List<ElGamalCiphertext>.add(other: List<ElGamalCiphertext>): List<ElGamalCiphertext> { | ||
require(this.size == other.size) | ||
val result = mutableListOf<ElGamalCiphertext>() | ||
this.forEachIndexed { index, value -> | ||
result.add(value + other[index]) | ||
} | ||
return result | ||
} |
Oops, something went wrong.