Skip to content

Commit

Permalink
Add support for writeins and overvotes. (#272)
Browse files Browse the repository at this point in the history
* 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 <spelmaa@wwu.edu>
  • Loading branch information
jeffspel-crypto and dryercashew authored May 12, 2022
1 parent 5d449cb commit 97e13e7
Show file tree
Hide file tree
Showing 23 changed files with 729 additions and 287 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public void Test_Encrypt_Ballot_Undervote_Succeeds()
}

[Test]
public void Test_Encrypt_Ballot_Overvote_Fails()
public void Test_Encrypt_Ballot_Overvote_Succeeds()
{
// Configure the election context
var keypair = ElGamalKeyPair.FromSecret(Constants.TWO_MOD_Q);
Expand All @@ -86,8 +86,9 @@ public void Test_Encrypt_Ballot_Overvote_Fails()

// Act
var ballot = BallotGenerator.GetFakeBallot(internalManifest, 2);
Assert.Throws<ArgumentException>(() => mediator.Encrypt(ballot));
var ciphertext = mediator.Encrypt(ballot);

Assert.That(ciphertext.IsValidEncryption(context.ManifestHash, keypair.PublicKey, context.CryptoExtendedBaseHash));
}

[Test]
Expand Down
40 changes: 29 additions & 11 deletions include/electionguard/ballot.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,12 @@ namespace electionguard
/// <returns>BallotBoxState.unknown if the value cannot be resolved</returns>
/// </Summary>
EG_API BallotBoxState getBallotBoxState(const std::string &value);

/// <summary>
/// ExtendedData represents any arbitrary data expressible as a string with a length.
///
/// This class is used primarily as a field on a selection to indicate a write-in candidate text value
/// This class is used primarily as a field to hold encrypted data on a
/// contest, overvote data and writeins.
/// </summary>
struct ExtendedData {
public:
Expand All @@ -71,7 +72,7 @@ namespace electionguard
return std::make_unique<ExtendedData>(this->value, this->length);
}
};

/// <summary>
/// A BallotSelection represents an individual selection on a ballot.
///
Expand All @@ -94,8 +95,7 @@ namespace electionguard
PlaintextBallotSelection(const PlaintextBallotSelection &other);
PlaintextBallotSelection(PlaintextBallotSelection &&other);
PlaintextBallotSelection(std::string objectId, uint64_t vote,
bool isPlaceholderSelection = false,
std::unique_ptr<ExtendedData> extendedData = nullptr);
bool isPlaceholderSelection = false, std::string writeIn = "");
~PlaintextBallotSelection();

PlaintextBallotSelection &operator=(PlaintextBallotSelection other);
Expand All @@ -118,9 +118,9 @@ namespace electionguard
bool getIsPlaceholder() const;

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

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

typedef enum eg_valid_contest_return_e {
SUCCESS = 0,
OVERVOTE = 1,
OVERVOTE_ERROR = 2,
INVALID_OBJECT_ID_ERROR = 2,
TOO_MANY_SELECTIONS_ERROR = 3,
} eg_valid_contest_return_type_t;

/// <summary>
/// A PlaintextBallotContest represents the selections made by a voter for a specific ContestDescription
///
Expand Down Expand Up @@ -370,8 +378,10 @@ namespace electionguard
///
/// Note: because this class supports partial representations, undervotes are considered a valid state.
/// </Summary>
bool isValid(const std::string &expectedObjectId, uint64_t expectedNumberSelections,
uint64_t expectedNumberElected, uint64_t votesAllowd = 0) const;
eg_valid_contest_return_type_t isValid(const std::string &expectedObjectId,
uint64_t expectedNumberSelections,
uint64_t expectedNumberElected, uint64_t votesAllowd = 0,
bool supportOvervotes = true) const;

private:
class Impl;
Expand Down Expand Up @@ -404,7 +414,8 @@ namespace electionguard
std::unique_ptr<ElementModQ> nonce,
std::unique_ptr<ElGamalCiphertext> ciphertextAccumulation,
std::unique_ptr<ElementModQ> cryptoHash,
std::unique_ptr<ConstantChaumPedersenProof> proof);
std::unique_ptr<ConstantChaumPedersenProof> proof,
std::unique_ptr<HashedElGamalCiphertext> hashedElGamal);
~CiphertextBallotContest();

CiphertextBallotContest &operator=(CiphertextBallotContest other);
Expand Down Expand Up @@ -452,6 +463,12 @@ namespace electionguard
/// </summary>
ConstantChaumPedersenProof *getProof() const;

/// <summary>
/// The hashed elgamal ciphertext is the encrypted extended data (overvote information
/// and writeins).
/// </summary>
std::unique_ptr<HashedElGamalCiphertext> getHashedElGamalCiphertext() const;

/// <summary>
/// Given an encrypted BallotContest, generates a hash, suitable for rolling up
/// into a hash / tracking code for an entire ballot. Of note, this particular hash examines
Expand Down Expand Up @@ -479,7 +496,8 @@ namespace electionguard
const ElementModQ &proofSeed, const uint64_t numberElected,
std::unique_ptr<ElementModQ> nonce = nullptr,
std::unique_ptr<ElementModQ> cryptoHash = nullptr,
std::unique_ptr<ConstantChaumPedersenProof> proof = nullptr);
std::unique_ptr<ConstantChaumPedersenProof> proof = nullptr,
std::unique_ptr<HashedElGamalCiphertext> hashedElGamal = nullptr);

/// <summary>
/// An aggregate nonce for the contest composed of the nonces of the selections.
Expand Down
15 changes: 4 additions & 11 deletions include/electionguard/ballot_compact.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,7 @@ namespace electionguard
CompactPlaintextBallot(const CompactPlaintextBallot &other);
CompactPlaintextBallot(const CompactPlaintextBallot &&other);
CompactPlaintextBallot(const std::string &objectId, const std::string &styleId,
std::vector<uint64_t> selections,
std::map<uint64_t, std::unique_ptr<ExtendedData>> extendedData);
std::vector<uint64_t> selections, std::vector<std::string> writeins);
~CompactPlaintextBallot();

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

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

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

/// <Summary>
/// Convenience accessor for retrieving the extended data for a selection index
/// <returns>a value or a null pointer if none exists</returns>
/// </Summary>
std::unique_ptr<ExtendedData> getExtendedDataFor(const uint64_t index) const;

/// <Summary>
/// Export the ballot representation as BSON
/// </Summary>
Expand Down
42 changes: 25 additions & 17 deletions include/electionguard/elgamal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ namespace electionguard
/// <Summary>
/// Make an elgamal keypair from a secret.
/// </Summary>
static std::unique_ptr<ElGamalKeyPair> fromSecret(const ElementModQ &secretKey, bool isFixedBase = true);
static std::unique_ptr<ElGamalKeyPair> fromSecret(const ElementModQ &secretKey,
bool isFixedBase = true);

private:
class Impl;
Expand Down Expand Up @@ -131,23 +132,19 @@ namespace electionguard
/// <returns>A ciphertext tuple.</returns>
/// </summary>
EG_API std::unique_ptr<ElGamalCiphertext>
elgamalEncrypt_with_precomputed(uint64_t m, ElementModP &gToRho,
ElementModP &pubkeyToRho);
elgamalEncrypt_with_precomputed(uint64_t m, ElementModP &gToRho, ElementModP &pubkeyToRho);
/// <summary>
/// Accumulate the ciphertexts by adding them together.
/// </summary>
EG_API std::unique_ptr<ElGamalCiphertext>
elgamalAdd(const std::vector<std::reference_wrapper<ElGamalCiphertext>> &ciphertexts);

#define HASHED_CIPHERTEXT_BLOCK_LENGTH 32U
#define _PAD_INDICATOR_SIZE sizeof(uint16_t)
#define HASHED_CIPHERTEXT_BLOCK_LENGTH 32U
#define HASHED_BLOCK_LENGTH_IN_BITS 256U
#define _PAD_INDICATOR_SIZE sizeof(uint16_t)

typedef enum padded_data_size_e {
NO_PADDING = 0,
BYTES_32 = 32 - _PAD_INDICATOR_SIZE,
BYTES_64 = 64 - _PAD_INDICATOR_SIZE,
BYTES_128 = 128 - _PAD_INDICATOR_SIZE,
BYTES_256 = 256 - _PAD_INDICATOR_SIZE,
BYTES_512 = 512 - _PAD_INDICATOR_SIZE
} padded_data_size_t;

Expand All @@ -159,7 +156,7 @@ namespace electionguard
/// result. Create one with `hashedElgamalEncrypt`. Decrypt using one the
/// 'decrypt' method.
/// </summary>
class EG_API HashedElGamalCiphertext
class EG_API HashedElGamalCiphertext : public CryptoHashable
{
public:
HashedElGamalCiphertext(const HashedElGamalCiphertext &other);
Expand Down Expand Up @@ -207,14 +204,17 @@ namespace electionguard
/// </Summary>
std::vector<uint8_t> getMac() const;

virtual std::unique_ptr<ElementModQ> crypto_hash() override;
virtual std::unique_ptr<ElementModQ> crypto_hash() const override;

/// <summary>
/// Decrypts ciphertext with the Auxiliary Encryption method (as specified in the
/// ElectionGuard specification) given a random nonce, an ElGamal public key,
/// and a description hash. The encrypt may be called to look for padding to
/// verify and remove, in this case the plaintext will be smaller than
/// the ciphertext, or not to look for padding in which case the
/// plaintext will be the same size as the ciphertext.
///
/// plaintext will be the same size as the ciphertext.
///
/// <param name="nonce"> Randomly chosen nonce in [1,Q). </param>
/// <param name="publicKey"> ElGamal public key. </param>
/// <param name="descriptionHash"> Hash of the ballot description. </param>
Expand All @@ -232,7 +232,7 @@ namespace electionguard
private:
class Impl;
#pragma warning(suppress : 4251)
std::unique_ptr<Impl> pimpl;
std::unique_ptr<Impl> pimpl;
};

/// <summary>
Expand All @@ -244,24 +244,32 @@ namespace electionguard
/// This value indicates the maximum length of the plaintext that may be
/// encrypted. The padding scheme applies two bytes for length of padding
/// plus padding bytes. If padding is not to be applied then the
/// max_len parameter must be NO_PADDING and the plaintext must
/// max_len parameter must be NO_PADDING. and the plaintext must
/// be a multiple of the block length (32) and the ciphertext will be
/// the same size.
/// the same size. If the max_len is set to something other than
/// NO_PADDING and the allow_truncation parameter is set to
/// true then if the message parameter data is longer than
/// max_len then it will be truncated to max_len. If the max_len is set to
/// something other than NO_PADDING and the allow_truncation parameter
/// is set to false then if the message parameter data is longer than
/// max_len then an exception will be thrown.
///
/// <param name="plaintext"> Message to hashed elgamal encrypt. </param>
/// <param name="message"> Message to hashed elgamal encrypt. </param>
/// <param name="nonce"> Randomly chosen nonce in [1,Q). </param>
/// <param name="publicKey"> ElGamal public key. </param>
/// <param name="descriptionHash"> Hash of the ballot description. </param>
/// <param name="max_len"> If padding is to be applied then this indicates the
/// maximum length of plaintext, must be one padded_data_size_t enumeration
/// values. If padding is not to be applied then this parameter must use
/// the NO_PADDING padded_data_size_t enumeration value.</param>
/// <param name="allow_truncation"> Truncates data to the max_len if set to
/// true. If max_len is set to NO_PADDING then this parameter is ignored. </param>
/// <returns>A ciphertext triple.</returns>
/// </summary>
EG_API std::unique_ptr<HashedElGamalCiphertext>
hashedElgamalEncrypt(std::vector<uint8_t> plaintext, const ElementModQ &nonce,
const ElementModP &publicKey, const ElementModQ &descriptionHash,
padded_data_size_t max_len);
padded_data_size_t max_len, bool allow_truncation);

} // namespace electionguard

Expand Down
19 changes: 18 additions & 1 deletion include/electionguard/encrypt.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,30 @@ namespace electionguard
const ElementModQ &cryptoExtendedBaseHash, const ElementModQ &nonceSeed,
bool isPlaceholder = false, bool shouldVerifyProofs = true);

/// <summary>
/// Gets overvote and write in information from the selections in a contest
/// puts the information in a json string. The internalManifest is needed because
/// it has the candidates and the candidate holds the indicator if a
/// selection is a write in.
///
/// <param name="contest">the contest in valid input form</param>
/// <param name="internalManifest">the `InternalManifest` which defines this ballot's structure</param>
/// <param name="is_overvote">indicates if an overvote was detected</param>
/// <returns>string holding the json with the write ins</returns>
/// </summary>
std::string getOvervoteAndWriteIns(const PlaintextBallotContest &contest,
const InternalManifest &internalManifest,
eg_valid_contest_return_type_t is_overvote);

/// <summary>
/// Encrypt a specific `BallotContest` in the context of a specific `Ballot`
///
/// This method accepts a contest representation that only includes `True` selections.
/// It will fill missing selections for a contest with `False` values, and generate `placeholder`
/// selections to represent the number of seats available for a given contest. By adding `placeholder`
/// votes
/// <param name="plaintext">the selection in the valid input form</param>
/// <param name="contest">the contest in valid input form</param>
/// <param name="internalManifest">the `InternalManifest` which defines this ballot's structure</param>
/// <param name="description">the `ContestDescriptionWithPlaceholders` from the `ContestDescription`
/// which defines this contest's structure</param>
/// <param name="elgamalPublicKey">the public key (K) used to encrypt the ballot</param>
Expand All @@ -131,6 +147,7 @@ namespace electionguard
/// </summary>
EG_API std::unique_ptr<CiphertextBallotContest>
encryptContest(const PlaintextBallotContest &contest,
const InternalManifest &internalManifest,
const ContestDescriptionWithPlaceholders &description,
const ElementModP &elgamalPublicKey, const ElementModQ &cryptoExtendedBaseHash,
const ElementModQ &nonceSeed, bool shouldVerifyProofs = true);
Expand Down
2 changes: 2 additions & 0 deletions include/electionguard/group.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,8 @@ namespace electionguard
/// </summary>
EG_API std::unique_ptr<ElementModQ> rand_q();

std::string vector_uint8_t_to_hex(const std::vector<uint8_t> &bytes);

} // namespace electionguard

#endif /* __ELECTIONGUARD_CPP_GROUP_HPP_INCLUDED__ */
3 changes: 2 additions & 1 deletion include/electionguard/hash.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ namespace electionguard
std::vector<std::reference_wrapper<const CryptoHashable>>,
std::vector<std::reference_wrapper<const ElementModP>>,
std::vector<std::reference_wrapper<const ElementModQ>>, std::vector<uint64_t>,
std::vector<std::string>>;
std::vector<std::string>,
std::vector<uint8_t>>;

/// <Summary>
/// Given zero or more elements, calculate their cryptographic hash
Expand Down
9 changes: 9 additions & 0 deletions include/electionguard/manifest.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,7 @@ namespace electionguard
InternalManifest(InternalManifest &&other);
explicit InternalManifest(
std::vector<std::unique_ptr<GeopoliticalUnit>> geopoliticalUnits,
std::vector<std::unique_ptr<Candidate>> candidates,
std::vector<std::unique_ptr<ContestDescriptionWithPlaceholders>> contests,
std::vector<std::unique_ptr<BallotStyle>> ballotStyles, const ElementModQ &manifestHash);
InternalManifest(const Manifest &description);
Expand All @@ -989,6 +990,11 @@ namespace electionguard
/// </Summary>
std::vector<std::reference_wrapper<GeopoliticalUnit>> getGeopoliticalUnits() const;

/// <Summary>
/// Collection of candidates for this election.
/// </Summary>
std::vector<std::reference_wrapper<Candidate>> getCandidates() const;

/// <Summary>
/// Collection of contests for this election.
/// </Summary>
Expand Down Expand Up @@ -1052,6 +1058,9 @@ namespace electionguard
static std::vector<std::unique_ptr<GeopoliticalUnit>>
copyGeopoliticalUnits(const Manifest &description);

static std::vector<std::unique_ptr<Candidate>>
copyCandidates(const Manifest &description);

static std::vector<std::unique_ptr<BallotStyle>>
copyBallotStyles(const Manifest &description);

Expand Down
Loading

0 comments on commit 97e13e7

Please sign in to comment.