Skip to content

Commit 97e13e7

Browse files
Add support for writeins and overvotes. (#272)
* Add support for writeins and overvotes. * Looks like my serialization changed broke a C test, not sure why the tests happily proceed on my machine. * Update naming on an enum. * Update naming of variables. * Ooops got somethings wrong for key calculations. Also an updated test that should help with interop. * Update with changes for interop. Overvote and writein labels were wrong. Updated some test data. Co-authored-by: Jeff <[email protected]>
1 parent 5d449cb commit 97e13e7

File tree

23 files changed

+729
-287
lines changed

23 files changed

+729
-287
lines changed

bindings/netstandard/ElectionGuard/ElectionGuard.Encryption.Tests/TestEncrypt.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public void Test_Encrypt_Ballot_Undervote_Succeeds()
7373
}
7474

7575
[Test]
76-
public void Test_Encrypt_Ballot_Overvote_Fails()
76+
public void Test_Encrypt_Ballot_Overvote_Succeeds()
7777
{
7878
// Configure the election context
7979
var keypair = ElGamalKeyPair.FromSecret(Constants.TWO_MOD_Q);
@@ -86,8 +86,9 @@ public void Test_Encrypt_Ballot_Overvote_Fails()
8686

8787
// Act
8888
var ballot = BallotGenerator.GetFakeBallot(internalManifest, 2);
89-
Assert.Throws<ArgumentException>(() => mediator.Encrypt(ballot));
89+
var ciphertext = mediator.Encrypt(ballot);
9090

91+
Assert.That(ciphertext.IsValidEncryption(context.ManifestHash, keypair.PublicKey, context.CryptoExtendedBaseHash));
9192
}
9293

9394
[Test]

include/electionguard/ballot.hpp

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,12 @@ namespace electionguard
4343
/// <returns>BallotBoxState.unknown if the value cannot be resolved</returns>
4444
/// </Summary>
4545
EG_API BallotBoxState getBallotBoxState(const std::string &value);
46-
46+
4747
/// <summary>
4848
/// ExtendedData represents any arbitrary data expressible as a string with a length.
4949
///
50-
/// This class is used primarily as a field on a selection to indicate a write-in candidate text value
50+
/// This class is used primarily as a field to hold encrypted data on a
51+
/// contest, overvote data and writeins.
5152
/// </summary>
5253
struct ExtendedData {
5354
public:
@@ -71,7 +72,7 @@ namespace electionguard
7172
return std::make_unique<ExtendedData>(this->value, this->length);
7273
}
7374
};
74-
75+
7576
/// <summary>
7677
/// A BallotSelection represents an individual selection on a ballot.
7778
///
@@ -94,8 +95,7 @@ namespace electionguard
9495
PlaintextBallotSelection(const PlaintextBallotSelection &other);
9596
PlaintextBallotSelection(PlaintextBallotSelection &&other);
9697
PlaintextBallotSelection(std::string objectId, uint64_t vote,
97-
bool isPlaceholderSelection = false,
98-
std::unique_ptr<ExtendedData> extendedData = nullptr);
98+
bool isPlaceholderSelection = false, std::string writeIn = "");
9999
~PlaintextBallotSelection();
100100

101101
PlaintextBallotSelection &operator=(PlaintextBallotSelection other);
@@ -118,9 +118,9 @@ namespace electionguard
118118
bool getIsPlaceholder() const;
119119

120120
/// <summary>
121-
/// an optional field of arbitrary data, such as the value of a write-in candidate
121+
/// an optional field holding the value of a write-in candidate
122122
/// </summary>
123-
ExtendedData *getExtendedData() const;
123+
std::string getWriteIn() const;
124124

125125
/// <summary>
126126
/// Given a PlaintextBallotSelection validates that the object matches an expected object
@@ -330,6 +330,14 @@ namespace electionguard
330330
//TODO: void setNonce(ElementModQ *nonce);
331331
};
332332

333+
typedef enum eg_valid_contest_return_e {
334+
SUCCESS = 0,
335+
OVERVOTE = 1,
336+
OVERVOTE_ERROR = 2,
337+
INVALID_OBJECT_ID_ERROR = 2,
338+
TOO_MANY_SELECTIONS_ERROR = 3,
339+
} eg_valid_contest_return_type_t;
340+
333341
/// <summary>
334342
/// A PlaintextBallotContest represents the selections made by a voter for a specific ContestDescription
335343
///
@@ -370,8 +378,10 @@ namespace electionguard
370378
///
371379
/// Note: because this class supports partial representations, undervotes are considered a valid state.
372380
/// </Summary>
373-
bool isValid(const std::string &expectedObjectId, uint64_t expectedNumberSelections,
374-
uint64_t expectedNumberElected, uint64_t votesAllowd = 0) const;
381+
eg_valid_contest_return_type_t isValid(const std::string &expectedObjectId,
382+
uint64_t expectedNumberSelections,
383+
uint64_t expectedNumberElected, uint64_t votesAllowd = 0,
384+
bool supportOvervotes = true) const;
375385

376386
private:
377387
class Impl;
@@ -404,7 +414,8 @@ namespace electionguard
404414
std::unique_ptr<ElementModQ> nonce,
405415
std::unique_ptr<ElGamalCiphertext> ciphertextAccumulation,
406416
std::unique_ptr<ElementModQ> cryptoHash,
407-
std::unique_ptr<ConstantChaumPedersenProof> proof);
417+
std::unique_ptr<ConstantChaumPedersenProof> proof,
418+
std::unique_ptr<HashedElGamalCiphertext> hashedElGamal);
408419
~CiphertextBallotContest();
409420

410421
CiphertextBallotContest &operator=(CiphertextBallotContest other);
@@ -452,6 +463,12 @@ namespace electionguard
452463
/// </summary>
453464
ConstantChaumPedersenProof *getProof() const;
454465

466+
/// <summary>
467+
/// The hashed elgamal ciphertext is the encrypted extended data (overvote information
468+
/// and writeins).
469+
/// </summary>
470+
std::unique_ptr<HashedElGamalCiphertext> getHashedElGamalCiphertext() const;
471+
455472
/// <summary>
456473
/// Given an encrypted BallotContest, generates a hash, suitable for rolling up
457474
/// into a hash / tracking code for an entire ballot. Of note, this particular hash examines
@@ -479,7 +496,8 @@ namespace electionguard
479496
const ElementModQ &proofSeed, const uint64_t numberElected,
480497
std::unique_ptr<ElementModQ> nonce = nullptr,
481498
std::unique_ptr<ElementModQ> cryptoHash = nullptr,
482-
std::unique_ptr<ConstantChaumPedersenProof> proof = nullptr);
499+
std::unique_ptr<ConstantChaumPedersenProof> proof = nullptr,
500+
std::unique_ptr<HashedElGamalCiphertext> hashedElGamal = nullptr);
483501

484502
/// <summary>
485503
/// An aggregate nonce for the contest composed of the nonces of the selections.

include/electionguard/ballot_compact.hpp

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ namespace electionguard
3939
CompactPlaintextBallot(const CompactPlaintextBallot &other);
4040
CompactPlaintextBallot(const CompactPlaintextBallot &&other);
4141
CompactPlaintextBallot(const std::string &objectId, const std::string &styleId,
42-
std::vector<uint64_t> selections,
43-
std::map<uint64_t, std::unique_ptr<ExtendedData>> extendedData);
42+
std::vector<uint64_t> selections, std::vector<std::string> writeins);
4443
~CompactPlaintextBallot();
4544

4645
CompactPlaintextBallot &operator=(CompactPlaintextBallot other);
@@ -66,22 +65,16 @@ namespace electionguard
6665
std::vector<uint64_t> getSelections() const;
6766

6867
/// <Summary>
69-
/// The mapping of extended data selections as they apply to the selections on the ballot
70-
/// by index order when calling `getSelections`.
68+
/// The collection of writeins on the ballot ordered by the contest sequence order
69+
/// and the selection sequence order. It is up to the consumer to guarantee the order of elements
7170
/// </Summary>
72-
std::map<uint64_t, std::reference_wrapper<ExtendedData>> getExtendedData() const;
71+
std::vector<std::string> getWriteIns() const;
7372

7473
/// <Summary>
7574
/// Make a compact representation of a plaintext ballot
7675
/// </Summary>
7776
static std::unique_ptr<CompactPlaintextBallot> make(const PlaintextBallot &plaintext);
7877

79-
/// <Summary>
80-
/// Convenience accessor for retrieving the extended data for a selection index
81-
/// <returns>a value or a null pointer if none exists</returns>
82-
/// </Summary>
83-
std::unique_ptr<ExtendedData> getExtendedDataFor(const uint64_t index) const;
84-
8578
/// <Summary>
8679
/// Export the ballot representation as BSON
8780
/// </Summary>

include/electionguard/elgamal.hpp

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ namespace electionguard
3737
/// <Summary>
3838
/// Make an elgamal keypair from a secret.
3939
/// </Summary>
40-
static std::unique_ptr<ElGamalKeyPair> fromSecret(const ElementModQ &secretKey, bool isFixedBase = true);
40+
static std::unique_ptr<ElGamalKeyPair> fromSecret(const ElementModQ &secretKey,
41+
bool isFixedBase = true);
4142

4243
private:
4344
class Impl;
@@ -131,23 +132,19 @@ namespace electionguard
131132
/// <returns>A ciphertext tuple.</returns>
132133
/// </summary>
133134
EG_API std::unique_ptr<ElGamalCiphertext>
134-
elgamalEncrypt_with_precomputed(uint64_t m, ElementModP &gToRho,
135-
ElementModP &pubkeyToRho);
135+
elgamalEncrypt_with_precomputed(uint64_t m, ElementModP &gToRho, ElementModP &pubkeyToRho);
136136
/// <summary>
137137
/// Accumulate the ciphertexts by adding them together.
138138
/// </summary>
139139
EG_API std::unique_ptr<ElGamalCiphertext>
140140
elgamalAdd(const std::vector<std::reference_wrapper<ElGamalCiphertext>> &ciphertexts);
141141

142-
#define HASHED_CIPHERTEXT_BLOCK_LENGTH 32U
143-
#define _PAD_INDICATOR_SIZE sizeof(uint16_t)
142+
#define HASHED_CIPHERTEXT_BLOCK_LENGTH 32U
143+
#define HASHED_BLOCK_LENGTH_IN_BITS 256U
144+
#define _PAD_INDICATOR_SIZE sizeof(uint16_t)
144145

145146
typedef enum padded_data_size_e {
146147
NO_PADDING = 0,
147-
BYTES_32 = 32 - _PAD_INDICATOR_SIZE,
148-
BYTES_64 = 64 - _PAD_INDICATOR_SIZE,
149-
BYTES_128 = 128 - _PAD_INDICATOR_SIZE,
150-
BYTES_256 = 256 - _PAD_INDICATOR_SIZE,
151148
BYTES_512 = 512 - _PAD_INDICATOR_SIZE
152149
} padded_data_size_t;
153150

@@ -159,7 +156,7 @@ namespace electionguard
159156
/// result. Create one with `hashedElgamalEncrypt`. Decrypt using one the
160157
/// 'decrypt' method.
161158
/// </summary>
162-
class EG_API HashedElGamalCiphertext
159+
class EG_API HashedElGamalCiphertext : public CryptoHashable
163160
{
164161
public:
165162
HashedElGamalCiphertext(const HashedElGamalCiphertext &other);
@@ -207,14 +204,17 @@ namespace electionguard
207204
/// </Summary>
208205
std::vector<uint8_t> getMac() const;
209206

207+
virtual std::unique_ptr<ElementModQ> crypto_hash() override;
208+
virtual std::unique_ptr<ElementModQ> crypto_hash() const override;
209+
210210
/// <summary>
211211
/// Decrypts ciphertext with the Auxiliary Encryption method (as specified in the
212212
/// ElectionGuard specification) given a random nonce, an ElGamal public key,
213213
/// and a description hash. The encrypt may be called to look for padding to
214214
/// verify and remove, in this case the plaintext will be smaller than
215215
/// the ciphertext, or not to look for padding in which case the
216-
/// plaintext will be the same size as the ciphertext.
217-
///
216+
/// plaintext will be the same size as the ciphertext.
217+
///
218218
/// <param name="nonce"> Randomly chosen nonce in [1,Q). </param>
219219
/// <param name="publicKey"> ElGamal public key. </param>
220220
/// <param name="descriptionHash"> Hash of the ballot description. </param>
@@ -232,7 +232,7 @@ namespace electionguard
232232
private:
233233
class Impl;
234234
#pragma warning(suppress : 4251)
235-
std::unique_ptr<Impl> pimpl;
235+
std::unique_ptr<Impl> pimpl;
236236
};
237237

238238
/// <summary>
@@ -244,24 +244,32 @@ namespace electionguard
244244
/// This value indicates the maximum length of the plaintext that may be
245245
/// encrypted. The padding scheme applies two bytes for length of padding
246246
/// plus padding bytes. If padding is not to be applied then the
247-
/// max_len parameter must be NO_PADDING and the plaintext must
247+
/// max_len parameter must be NO_PADDING. and the plaintext must
248248
/// be a multiple of the block length (32) and the ciphertext will be
249-
/// the same size.
249+
/// the same size. If the max_len is set to something other than
250+
/// NO_PADDING and the allow_truncation parameter is set to
251+
/// true then if the message parameter data is longer than
252+
/// max_len then it will be truncated to max_len. If the max_len is set to
253+
/// something other than NO_PADDING and the allow_truncation parameter
254+
/// is set to false then if the message parameter data is longer than
255+
/// max_len then an exception will be thrown.
250256
///
251-
/// <param name="plaintext"> Message to hashed elgamal encrypt. </param>
257+
/// <param name="message"> Message to hashed elgamal encrypt. </param>
252258
/// <param name="nonce"> Randomly chosen nonce in [1,Q). </param>
253259
/// <param name="publicKey"> ElGamal public key. </param>
254260
/// <param name="descriptionHash"> Hash of the ballot description. </param>
255261
/// <param name="max_len"> If padding is to be applied then this indicates the
256262
/// maximum length of plaintext, must be one padded_data_size_t enumeration
257263
/// values. If padding is not to be applied then this parameter must use
258264
/// the NO_PADDING padded_data_size_t enumeration value.</param>
265+
/// <param name="allow_truncation"> Truncates data to the max_len if set to
266+
/// true. If max_len is set to NO_PADDING then this parameter is ignored. </param>
259267
/// <returns>A ciphertext triple.</returns>
260268
/// </summary>
261269
EG_API std::unique_ptr<HashedElGamalCiphertext>
262270
hashedElgamalEncrypt(std::vector<uint8_t> plaintext, const ElementModQ &nonce,
263271
const ElementModP &publicKey, const ElementModQ &descriptionHash,
264-
padded_data_size_t max_len);
272+
padded_data_size_t max_len, bool allow_truncation);
265273

266274
} // namespace electionguard
267275

include/electionguard/encrypt.hpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,14 +111,30 @@ namespace electionguard
111111
const ElementModQ &cryptoExtendedBaseHash, const ElementModQ &nonceSeed,
112112
bool isPlaceholder = false, bool shouldVerifyProofs = true);
113113

114+
/// <summary>
115+
/// Gets overvote and write in information from the selections in a contest
116+
/// puts the information in a json string. The internalManifest is needed because
117+
/// it has the candidates and the candidate holds the indicator if a
118+
/// selection is a write in.
119+
///
120+
/// <param name="contest">the contest in valid input form</param>
121+
/// <param name="internalManifest">the `InternalManifest` which defines this ballot's structure</param>
122+
/// <param name="is_overvote">indicates if an overvote was detected</param>
123+
/// <returns>string holding the json with the write ins</returns>
124+
/// </summary>
125+
std::string getOvervoteAndWriteIns(const PlaintextBallotContest &contest,
126+
const InternalManifest &internalManifest,
127+
eg_valid_contest_return_type_t is_overvote);
128+
114129
/// <summary>
115130
/// Encrypt a specific `BallotContest` in the context of a specific `Ballot`
116131
///
117132
/// This method accepts a contest representation that only includes `True` selections.
118133
/// It will fill missing selections for a contest with `False` values, and generate `placeholder`
119134
/// selections to represent the number of seats available for a given contest. By adding `placeholder`
120135
/// votes
121-
/// <param name="plaintext">the selection in the valid input form</param>
136+
/// <param name="contest">the contest in valid input form</param>
137+
/// <param name="internalManifest">the `InternalManifest` which defines this ballot's structure</param>
122138
/// <param name="description">the `ContestDescriptionWithPlaceholders` from the `ContestDescription`
123139
/// which defines this contest's structure</param>
124140
/// <param name="elgamalPublicKey">the public key (K) used to encrypt the ballot</param>
@@ -131,6 +147,7 @@ namespace electionguard
131147
/// </summary>
132148
EG_API std::unique_ptr<CiphertextBallotContest>
133149
encryptContest(const PlaintextBallotContest &contest,
150+
const InternalManifest &internalManifest,
134151
const ContestDescriptionWithPlaceholders &description,
135152
const ElementModP &elgamalPublicKey, const ElementModQ &cryptoExtendedBaseHash,
136153
const ElementModQ &nonceSeed, bool shouldVerifyProofs = true);

include/electionguard/group.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,8 @@ namespace electionguard
283283
/// </summary>
284284
EG_API std::unique_ptr<ElementModQ> rand_q();
285285

286+
std::string vector_uint8_t_to_hex(const std::vector<uint8_t> &bytes);
287+
286288
} // namespace electionguard
287289

288290
#endif /* __ELECTIONGUARD_CPP_GROUP_HPP_INCLUDED__ */

include/electionguard/hash.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ namespace electionguard
2222
std::vector<std::reference_wrapper<const CryptoHashable>>,
2323
std::vector<std::reference_wrapper<const ElementModP>>,
2424
std::vector<std::reference_wrapper<const ElementModQ>>, std::vector<uint64_t>,
25-
std::vector<std::string>>;
25+
std::vector<std::string>,
26+
std::vector<uint8_t>>;
2627

2728
/// <Summary>
2829
/// Given zero or more elements, calculate their cryptographic hash

include/electionguard/manifest.hpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -971,6 +971,7 @@ namespace electionguard
971971
InternalManifest(InternalManifest &&other);
972972
explicit InternalManifest(
973973
std::vector<std::unique_ptr<GeopoliticalUnit>> geopoliticalUnits,
974+
std::vector<std::unique_ptr<Candidate>> candidates,
974975
std::vector<std::unique_ptr<ContestDescriptionWithPlaceholders>> contests,
975976
std::vector<std::unique_ptr<BallotStyle>> ballotStyles, const ElementModQ &manifestHash);
976977
InternalManifest(const Manifest &description);
@@ -989,6 +990,11 @@ namespace electionguard
989990
/// </Summary>
990991
std::vector<std::reference_wrapper<GeopoliticalUnit>> getGeopoliticalUnits() const;
991992

993+
/// <Summary>
994+
/// Collection of candidates for this election.
995+
/// </Summary>
996+
std::vector<std::reference_wrapper<Candidate>> getCandidates() const;
997+
992998
/// <Summary>
993999
/// Collection of contests for this election.
9941000
/// </Summary>
@@ -1052,6 +1058,9 @@ namespace electionguard
10521058
static std::vector<std::unique_ptr<GeopoliticalUnit>>
10531059
copyGeopoliticalUnits(const Manifest &description);
10541060

1061+
static std::vector<std::unique_ptr<Candidate>>
1062+
copyCandidates(const Manifest &description);
1063+
10551064
static std::vector<std::unique_ptr<BallotStyle>>
10561065
copyBallotStyles(const Manifest &description);
10571066

0 commit comments

Comments
 (0)