diff --git a/bindings/netstandard/ElectionGuard/ElectionGuard.Encryption.Tests/TestEncrypt.cs b/bindings/netstandard/ElectionGuard/ElectionGuard.Encryption.Tests/TestEncrypt.cs index 5efb10ea..c2536e6b 100644 --- a/bindings/netstandard/ElectionGuard/ElectionGuard.Encryption.Tests/TestEncrypt.cs +++ b/bindings/netstandard/ElectionGuard/ElectionGuard.Encryption.Tests/TestEncrypt.cs @@ -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); @@ -86,8 +86,9 @@ public void Test_Encrypt_Ballot_Overvote_Fails() // Act var ballot = BallotGenerator.GetFakeBallot(internalManifest, 2); - Assert.Throws(() => mediator.Encrypt(ballot)); + var ciphertext = mediator.Encrypt(ballot); + Assert.That(ciphertext.IsValidEncryption(context.ManifestHash, keypair.PublicKey, context.CryptoExtendedBaseHash)); } [Test] diff --git a/include/electionguard/ballot.hpp b/include/electionguard/ballot.hpp index 11a93443..a25a8524 100644 --- a/include/electionguard/ballot.hpp +++ b/include/electionguard/ballot.hpp @@ -43,11 +43,12 @@ namespace electionguard /// BallotBoxState.unknown if the value cannot be resolved /// EG_API BallotBoxState getBallotBoxState(const std::string &value); - + /// /// 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. /// struct ExtendedData { public: @@ -71,7 +72,7 @@ namespace electionguard return std::make_unique(this->value, this->length); } }; - + /// /// A BallotSelection represents an individual selection on a ballot. /// @@ -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 = nullptr); + bool isPlaceholderSelection = false, std::string writeIn = ""); ~PlaintextBallotSelection(); PlaintextBallotSelection &operator=(PlaintextBallotSelection other); @@ -118,9 +118,9 @@ namespace electionguard bool getIsPlaceholder() const; /// - /// 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 /// - ExtendedData *getExtendedData() const; + std::string getWriteIn() const; /// /// Given a PlaintextBallotSelection validates that the object matches an expected object @@ -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; + /// /// A PlaintextBallotContest represents the selections made by a voter for a specific ContestDescription /// @@ -370,8 +378,10 @@ namespace electionguard /// /// Note: because this class supports partial representations, undervotes are considered a valid state. /// - 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; @@ -404,7 +414,8 @@ namespace electionguard std::unique_ptr nonce, std::unique_ptr ciphertextAccumulation, std::unique_ptr cryptoHash, - std::unique_ptr proof); + std::unique_ptr proof, + std::unique_ptr hashedElGamal); ~CiphertextBallotContest(); CiphertextBallotContest &operator=(CiphertextBallotContest other); @@ -452,6 +463,12 @@ namespace electionguard /// ConstantChaumPedersenProof *getProof() const; + /// + /// The hashed elgamal ciphertext is the encrypted extended data (overvote information + /// and writeins). + /// + std::unique_ptr getHashedElGamalCiphertext() const; + /// /// 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 @@ -479,7 +496,8 @@ namespace electionguard const ElementModQ &proofSeed, const uint64_t numberElected, std::unique_ptr nonce = nullptr, std::unique_ptr cryptoHash = nullptr, - std::unique_ptr proof = nullptr); + std::unique_ptr proof = nullptr, + std::unique_ptr hashedElGamal = nullptr); /// /// An aggregate nonce for the contest composed of the nonces of the selections. diff --git a/include/electionguard/ballot_compact.hpp b/include/electionguard/ballot_compact.hpp index 9445a0c8..fb29ffca 100644 --- a/include/electionguard/ballot_compact.hpp +++ b/include/electionguard/ballot_compact.hpp @@ -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 selections, - std::map> extendedData); + std::vector selections, std::vector writeins); ~CompactPlaintextBallot(); CompactPlaintextBallot &operator=(CompactPlaintextBallot other); @@ -66,22 +65,16 @@ namespace electionguard std::vector getSelections() const; /// - /// 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 /// - std::map> getExtendedData() const; + std::vector getWriteIns() const; /// /// Make a compact representation of a plaintext ballot /// static std::unique_ptr make(const PlaintextBallot &plaintext); - /// - /// Convenience accessor for retrieving the extended data for a selection index - /// a value or a null pointer if none exists - /// - std::unique_ptr getExtendedDataFor(const uint64_t index) const; - /// /// Export the ballot representation as BSON /// diff --git a/include/electionguard/elgamal.hpp b/include/electionguard/elgamal.hpp index 0d30709d..251e81cc 100644 --- a/include/electionguard/elgamal.hpp +++ b/include/electionguard/elgamal.hpp @@ -37,7 +37,8 @@ namespace electionguard /// /// Make an elgamal keypair from a secret. /// - static std::unique_ptr fromSecret(const ElementModQ &secretKey, bool isFixedBase = true); + static std::unique_ptr fromSecret(const ElementModQ &secretKey, + bool isFixedBase = true); private: class Impl; @@ -131,23 +132,19 @@ namespace electionguard /// A ciphertext tuple. /// EG_API std::unique_ptr - elgamalEncrypt_with_precomputed(uint64_t m, ElementModP &gToRho, - ElementModP &pubkeyToRho); + elgamalEncrypt_with_precomputed(uint64_t m, ElementModP &gToRho, ElementModP &pubkeyToRho); /// /// Accumulate the ciphertexts by adding them together. /// EG_API std::unique_ptr elgamalAdd(const std::vector> &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; @@ -159,7 +156,7 @@ namespace electionguard /// result. Create one with `hashedElgamalEncrypt`. Decrypt using one the /// 'decrypt' method. /// - class EG_API HashedElGamalCiphertext + class EG_API HashedElGamalCiphertext : public CryptoHashable { public: HashedElGamalCiphertext(const HashedElGamalCiphertext &other); @@ -207,14 +204,17 @@ namespace electionguard /// std::vector getMac() const; + virtual std::unique_ptr crypto_hash() override; + virtual std::unique_ptr crypto_hash() const override; + /// /// 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. + /// /// Randomly chosen nonce in [1,Q). /// ElGamal public key. /// Hash of the ballot description. @@ -232,7 +232,7 @@ namespace electionguard private: class Impl; #pragma warning(suppress : 4251) - std::unique_ptr pimpl; + std::unique_ptr pimpl; }; /// @@ -244,11 +244,17 @@ 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. /// - /// Message to hashed elgamal encrypt. + /// Message to hashed elgamal encrypt. /// Randomly chosen nonce in [1,Q). /// ElGamal public key. /// Hash of the ballot description. @@ -256,12 +262,14 @@ namespace electionguard /// 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. + /// Truncates data to the max_len if set to + /// true. If max_len is set to NO_PADDING then this parameter is ignored. /// A ciphertext triple. /// EG_API std::unique_ptr hashedElgamalEncrypt(std::vector 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 diff --git a/include/electionguard/encrypt.hpp b/include/electionguard/encrypt.hpp index c0b05fa3..6bd9e320 100644 --- a/include/electionguard/encrypt.hpp +++ b/include/electionguard/encrypt.hpp @@ -111,6 +111,21 @@ namespace electionguard const ElementModQ &cryptoExtendedBaseHash, const ElementModQ &nonceSeed, bool isPlaceholder = false, bool shouldVerifyProofs = true); + /// + /// 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. + /// + /// the contest in valid input form + /// the `InternalManifest` which defines this ballot's structure + /// indicates if an overvote was detected + /// string holding the json with the write ins + /// + std::string getOvervoteAndWriteIns(const PlaintextBallotContest &contest, + const InternalManifest &internalManifest, + eg_valid_contest_return_type_t is_overvote); + /// /// Encrypt a specific `BallotContest` in the context of a specific `Ballot` /// @@ -118,7 +133,8 @@ namespace electionguard /// 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 - /// the selection in the valid input form + /// the contest in valid input form + /// the `InternalManifest` which defines this ballot's structure /// the `ContestDescriptionWithPlaceholders` from the `ContestDescription` /// which defines this contest's structure /// the public key (K) used to encrypt the ballot @@ -131,6 +147,7 @@ namespace electionguard /// EG_API std::unique_ptr encryptContest(const PlaintextBallotContest &contest, + const InternalManifest &internalManifest, const ContestDescriptionWithPlaceholders &description, const ElementModP &elgamalPublicKey, const ElementModQ &cryptoExtendedBaseHash, const ElementModQ &nonceSeed, bool shouldVerifyProofs = true); diff --git a/include/electionguard/group.hpp b/include/electionguard/group.hpp index e49c6dd2..fc4f0038 100644 --- a/include/electionguard/group.hpp +++ b/include/electionguard/group.hpp @@ -283,6 +283,8 @@ namespace electionguard /// EG_API std::unique_ptr rand_q(); + std::string vector_uint8_t_to_hex(const std::vector &bytes); + } // namespace electionguard #endif /* __ELECTIONGUARD_CPP_GROUP_HPP_INCLUDED__ */ diff --git a/include/electionguard/hash.hpp b/include/electionguard/hash.hpp index 5ae63859..5089606f 100644 --- a/include/electionguard/hash.hpp +++ b/include/electionguard/hash.hpp @@ -22,7 +22,8 @@ namespace electionguard std::vector>, std::vector>, std::vector>, std::vector, - std::vector>; + std::vector, + std::vector>; /// /// Given zero or more elements, calculate their cryptographic hash diff --git a/include/electionguard/manifest.hpp b/include/electionguard/manifest.hpp index d90fcee0..6f679f66 100644 --- a/include/electionguard/manifest.hpp +++ b/include/electionguard/manifest.hpp @@ -971,6 +971,7 @@ namespace electionguard InternalManifest(InternalManifest &&other); explicit InternalManifest( std::vector> geopoliticalUnits, + std::vector> candidates, std::vector> contests, std::vector> ballotStyles, const ElementModQ &manifestHash); InternalManifest(const Manifest &description); @@ -989,6 +990,11 @@ namespace electionguard /// std::vector> getGeopoliticalUnits() const; + /// + /// Collection of candidates for this election. + /// + std::vector> getCandidates() const; + /// /// Collection of contests for this election. /// @@ -1052,6 +1058,9 @@ namespace electionguard static std::vector> copyGeopoliticalUnits(const Manifest &description); + static std::vector> + copyCandidates(const Manifest &description); + static std::vector> copyBallotStyles(const Manifest &description); diff --git a/src/electionguard/ballot.cpp b/src/electionguard/ballot.cpp index d258c4d6..234a6ecb 100644 --- a/src/electionguard/ballot.cpp +++ b/src/electionguard/ballot.cpp @@ -65,12 +65,12 @@ namespace electionguard struct PlaintextBallotSelection::Impl : public ElectionObjectBase { uint64_t vote; bool isPlaceholderSelection; - unique_ptr extendedData; + string writeIn; Impl(string objectId, uint64_t vote, bool isPlaceholderSelection /* = false */, - unique_ptr extendedData /* = nullptr*/) + string writeIn) : vote(vote), isPlaceholderSelection(isPlaceholderSelection), - extendedData(move(extendedData)) + writeIn(move(writeIn)) { this->object_id = move(objectId); } @@ -79,7 +79,7 @@ namespace electionguard { return make_unique( this->object_id, this->vote, this->isPlaceholderSelection, - this->extendedData == nullptr ? nullptr : this->extendedData->clone()); + this->writeIn); } }; @@ -97,8 +97,8 @@ namespace electionguard PlaintextBallotSelection::PlaintextBallotSelection( string objectId, uint64_t vote, bool isPlaceholderSelection /* = false */, - unique_ptr extendedData /* = nullptr*/) - : pimpl(new Impl(move(objectId), vote, isPlaceholderSelection, move(extendedData))) + string writeIn) + : pimpl(new Impl(move(objectId), vote, isPlaceholderSelection, writeIn)) { } @@ -118,10 +118,7 @@ namespace electionguard { return pimpl->isPlaceholderSelection; } - ExtendedData *PlaintextBallotSelection::getExtendedData() const - { - return pimpl->extendedData.get(); - } + string PlaintextBallotSelection::getWriteIn() const { return pimpl->writeIn; } bool PlaintextBallotSelection::isValid(const std::string &expectedObjectId) const { @@ -141,7 +138,7 @@ namespace electionguard { return make_unique( pimpl->object_id, pimpl->vote, pimpl->isPlaceholderSelection, - pimpl->extendedData == nullptr ? nullptr : pimpl->extendedData->clone()); + pimpl->writeIn); } #pragma endregion @@ -464,19 +461,21 @@ namespace electionguard // Public Functions - bool PlaintextBallotContest::isValid(const string &expectedObjectId, + eg_valid_contest_return_type_t + PlaintextBallotContest::isValid(const string &expectedObjectId, uint64_t expectedNumberSelections, uint64_t expectedNumberElected, - uint64_t votesAllowd /* = 0 */) const + uint64_t votesAllowd, /* = 0 */ + bool supportOvervotes /* = true */) const { if (pimpl->object_id != expectedObjectId) { Log::info(": invalid objectId"); - return false; + return INVALID_OBJECT_ID_ERROR; } if (pimpl->selections.size() > expectedNumberSelections) { Log::info(": too many selections"); - return false; + return TOO_MANY_SELECTIONS_ERROR; } uint64_t numberElected = 0; @@ -491,7 +490,11 @@ namespace electionguard if (numberElected > expectedNumberElected) { Log::info(": too many elections"); - return false; + if (supportOvervotes) { + return OVERVOTE; + } else { + return OVERVOTE_ERROR; + } } if (votesAllowd == 0) { @@ -500,10 +503,14 @@ namespace electionguard if (votes > votesAllowd) { Log::info(": too many votes"); - return false; + if (supportOvervotes) { + return OVERVOTE; + } else { + return OVERVOTE_ERROR; + } } - return true; + return SUCCESS; } #pragma endregion @@ -518,16 +525,18 @@ namespace electionguard unique_ptr ciphertextAccumulation; unique_ptr cryptoHash; unique_ptr proof; + unique_ptr hashedElGamal; Impl(const string &objectId, uint64_t sequenceOrder, unique_ptr descriptionHash, vector> selections, unique_ptr nonce, unique_ptr ciphertextAccumulation, - unique_ptr cryptoHash, unique_ptr proof) + unique_ptr cryptoHash, unique_ptr proof, + unique_ptr hashedElGamal) : sequenceOrder(sequenceOrder), descriptionHash(move(descriptionHash)), selections(move(selections)), nonce(move(nonce)), ciphertextAccumulation(move(ciphertextAccumulation)), cryptoHash(move(cryptoHash)), - proof(move(proof)) + proof(move(proof)), hashedElGamal(move(hashedElGamal)) { this->object_id = objectId; } @@ -545,10 +554,11 @@ namespace electionguard auto _accumulation = make_unique(*ciphertextAccumulation); auto _cryptoHash = make_unique(*cryptoHash); auto _proof = make_unique(*proof); + auto _hashedElGamal = make_unique(*hashedElGamal); return make_unique( object_id, sequenceOrder, move(_descriptionHash), move(_selections), move(_nonce), - move(_accumulation), move(_cryptoHash), move(_proof)); + move(_accumulation), move(_cryptoHash), move(_proof), move(_hashedElGamal)); } }; @@ -563,10 +573,11 @@ namespace electionguard const string &objectId, uint64_t sequenceOrder, const ElementModQ &descriptionHash, vector> selections, unique_ptr nonce, unique_ptr ciphertextAccumulation, unique_ptr cryptoHash, - unique_ptr proof) + unique_ptr proof, + unique_ptr hashedElGamal) : pimpl(new Impl(objectId, sequenceOrder, make_unique(descriptionHash), move(selections), move(nonce), move(ciphertextAccumulation), - move(cryptoHash), move(proof))) + move(cryptoHash), move(proof), move(hashedElGamal))) { } CiphertextBallotContest::~CiphertextBallotContest() = default; @@ -619,6 +630,11 @@ namespace electionguard return pimpl->proof.get(); } + unique_ptr CiphertextBallotContest::getHashedElGamalCiphertext() const + { + return pimpl->hashedElGamal.get()->clone(); + } + // Interface Overrides unique_ptr @@ -635,7 +651,8 @@ namespace electionguard const ElementModQ &cryptoExtendedBaseHash, const ElementModQ &proofSeed, uint64_t numberElected, unique_ptr nonce /* = nullptr */, unique_ptr cryptoHash /* = nullptr */, - unique_ptr proof /* = nullptr */) + unique_ptr proof /* = nullptr */, + unique_ptr hashedElGamal /*nullptr */) { vector> selectionReferences; selectionReferences.reserve(selections.size()); @@ -662,9 +679,10 @@ namespace electionguard proofSeed, cryptoExtendedBaseHash, numberElected); proof = move(owned_proof); } + return make_unique( objectId, sequenceOrder, descriptionHash, move(selections), move(nonce), - move(accumulation), move(cryptoHash), move(proof)); + move(accumulation), move(cryptoHash), move(proof), move(hashedElGamal)); } // Public Methods diff --git a/src/electionguard/ballot_compact.cpp b/src/electionguard/ballot_compact.cpp index dbc9a98e..44c6a5c7 100644 --- a/src/electionguard/ballot_compact.cpp +++ b/src/electionguard/ballot_compact.cpp @@ -32,11 +32,11 @@ namespace electionguard struct CompactPlaintextBallot::Impl : public ElectionObjectBase { string styleId; vector selections; - map> extendedData; + vector writeins; Impl(const string &objectId, const string &styleId, vector selections, - map> extendedData) - : selections(move(selections)), extendedData(move(extendedData)) + vector writeins) + : selections(move(selections)), writeins(move(writeins)) { this->object_id = objectId; this->styleId = styleId; @@ -47,8 +47,8 @@ namespace electionguard CompactPlaintextBallot::CompactPlaintextBallot( const string &objectId, const string &styleId, vector selections, - map> extendedData) - : pimpl(new Impl(objectId, styleId, move(selections), move(extendedData))) + std::vector writeins) + : pimpl(new Impl(objectId, styleId, move(selections), move(writeins))) { } CompactPlaintextBallot::~CompactPlaintextBallot() = default; @@ -65,14 +65,8 @@ namespace electionguard string CompactPlaintextBallot::getStyleId() const { return pimpl->styleId; } vector CompactPlaintextBallot::getSelections() const { return pimpl->selections; } - map> CompactPlaintextBallot::getExtendedData() const - { - map> extendedData; - for (const auto &data : pimpl->extendedData) { - extendedData.emplace(data.first, ref(*data.second)); - } - return extendedData; - } + vector CompactPlaintextBallot::getWriteIns() const { return pimpl->writeins; } + // Public Static Methods @@ -84,15 +78,6 @@ namespace electionguard // Public Methods - unique_ptr CompactPlaintextBallot::getExtendedDataFor(const uint64_t index) const - { - auto extendedData = pimpl->extendedData.find(index); - if (extendedData != pimpl->extendedData.end()) { - return extendedData->second->clone(); - } - return nullptr; - } - vector CompactPlaintextBallot::toBson() const { return CompactPlaintextBallotSerializer::toBson(*this); @@ -247,20 +232,19 @@ namespace electionguard unique_ptr compressPlaintextBallot(const PlaintextBallot &plaintext) { vector selections; + vector writeins; map> extendedData; uint32_t index = 0; for (const auto &contest : plaintext.getContests()) { for (const auto &selection : contest.get().getSelections()) { selections.push_back(selection.get().getVote()); - if (selection.get().getExtendedData() != nullptr) { - extendedData.emplace(index, selection.get().getExtendedData()->clone()); - } + writeins.push_back(selection.get().getWriteIn()); index++; } } return make_unique(plaintext.getObjectId(), plaintext.getStyleId(), - move(selections), move(extendedData)); + move(selections), move(writeins)); } unique_ptr compressCiphertextBallot(const PlaintextBallot &plaintext, @@ -304,11 +288,10 @@ namespace electionguard // Iterate through the selections on the contest and expand each selection for (const auto &selection : contest.get().getSelections()) { auto vote = compactBallot.getSelections().at(index); - auto extendedData = compactBallot.getExtendedDataFor(index); + auto writeIn = compactBallot.getWriteIns().at(index); selections.push_back(make_unique( - selection.get().getObjectId(), vote, false, - extendedData == nullptr ? nullptr : move(extendedData))); + selection.get().getObjectId(), vote, false, writeIn)); index++; } diff --git a/src/electionguard/elgamal.cpp b/src/electionguard/elgamal.cpp index 936f39aa..eb2e1c65 100644 --- a/src/electionguard/elgamal.cpp +++ b/src/electionguard/elgamal.cpp @@ -211,8 +211,8 @@ namespace electionguard #pragma endregion - unique_ptr elgamalEncrypt(uint64_t m, const ElementModQ &nonce, - const ElementModP &publicKey) + unique_ptr + elgamalEncrypt(uint64_t m, const ElementModQ &nonce, const ElementModP &publicKey) { if ((const_cast(nonce) == ZERO_MOD_Q())) { throw invalid_argument("elgamalEncrypt encryption requires a non-zero nonce"); @@ -289,6 +289,11 @@ namespace electionguard return make_unique(move(_pad), data, mac); } + [[nodiscard]] unique_ptr crypto_hash() const + { + return hash_elems({pad.get(), data, mac}); + } + bool operator==(const Impl &other) { return *pad == *other.pad && data == other.data && mac == other.mac; @@ -338,10 +343,16 @@ namespace electionguard vector HashedElGamalCiphertext::getMac() { return pimpl->mac; } vector HashedElGamalCiphertext::getMac() const { return pimpl->mac; } + unique_ptr HashedElGamalCiphertext::crypto_hash() { return pimpl->crypto_hash(); } + unique_ptr HashedElGamalCiphertext::crypto_hash() const + { + return pimpl->crypto_hash(); + } + // Public Methods vector HashedElGamalCiphertext::decrypt(const ElementModQ &secret_key, - const ElementModQ &descriptionHash, + const ElementModQ &encryption_seed, bool look_for_padding) { // Note this decryption method is primarily used for testing @@ -349,7 +360,7 @@ namespace electionguard vector plaintext; uint32_t ciphertext_len = pimpl->data.size(); - uint32_t num_xor_keys = ciphertext_len / HASHED_CIPHERTEXT_BLOCK_LENGTH; + uint32_t number_of_blocks = ciphertext_len / HASHED_CIPHERTEXT_BLOCK_LENGTH; if ((0 != (ciphertext_len % HASHED_CIPHERTEXT_BLOCK_LENGTH)) || (ciphertext_len == 0)) { throw invalid_argument("HashedElGamalCiphertext::decrypt the ciphertext " "is not a multiple of the block length 32"); @@ -357,11 +368,11 @@ namespace electionguard auto publicKey_to_r = pow_mod_p(*pimpl->pad, secret_key); - // hash g_to_r and publicKey_to_r to get the master key - auto master_key = hash_elems({pimpl->pad.get(), publicKey_to_r.get()}); + // hash g_to_r and publicKey_to_r to get the session key + auto session_key = hash_elems({pimpl->pad.get(), publicKey_to_r.get()}); - vector mac_key = get_hmac(master_key->toBytes(), descriptionHash.toBytes(), - descriptionHash.toBytes().size(), 0); + vector mac_key = get_hmac(session_key->toBytes(), encryption_seed.toBytes(), + number_of_blocks * HASHED_BLOCK_LENGTH_IN_BITS, 0); // calculate the mac (c0 is g ^ r mod p and c1 is the ciphertext, they are concatenated) vector c0_and_c1(pimpl->pad->toBytes()); @@ -375,11 +386,12 @@ namespace electionguard } uint32_t plaintext_index = 0; - for (uint32_t i = 0; i < num_xor_keys; i++) { + for (uint32_t i = 0; i < number_of_blocks; i++) { vector temp_plaintext(HASHED_CIPHERTEXT_BLOCK_LENGTH, 0); - vector xor_key = get_hmac(master_key->toBytes(), descriptionHash.toBytes(), - descriptionHash.toBytes().size(), i + 1); + vector xor_key = + get_hmac(session_key->toBytes(), encryption_seed.toBytes(), + number_of_blocks * HASHED_BLOCK_LENGTH_IN_BITS, i + 1); // XOR the key with the plaintext for (int j = 0; j < (int)HASHED_CIPHERTEXT_BLOCK_LENGTH; j++) { @@ -431,11 +443,10 @@ namespace electionguard #pragma endregion // HashedElGamalCiphertext - unique_ptr hashedElgamalEncrypt(std::vector message, - const ElementModQ &nonce, - const ElementModP &publicKey, - const ElementModQ &descriptionHash, - padded_data_size_t max_len) + unique_ptr + hashedElgamalEncrypt(std::vector message, const ElementModQ &nonce, + const ElementModP &publicKey, const ElementModQ &encryption_seed, + padded_data_size_t max_len, bool allow_truncation) { vector ciphertext; vector plaintext_on_boundary; @@ -444,35 +455,41 @@ namespace electionguard // padding bytes 0x00 are padded out to the first HASHED_CIPHERTEXT_BLOCK_LENGTH boundary // past max_len. So if max_len is 62 then it will pad to the 64 byte boundary if (max_len != NO_PADDING) { - if ((max_len == 0) || - (0 != ((max_len + sizeof(uint16_t)) % HASHED_CIPHERTEXT_BLOCK_LENGTH)) || - (max_len > 65534)) { - throw invalid_argument("HashedElGamalCiphertext::encrypt the max_len must be a " - "multiple of the block length 32"); - } - if (message.size() > max_len) { - throw invalid_argument( - "HashedElGamalCiphertext::encrypt the plaintext is greater than max_len"); - } + uint16_t pad_len = 0; + uint16_t pad_len_be = 0; + + if (allow_truncation && (message.size() > max_len)) { + // truncate the data + // insert length in big endian form + plaintext_on_boundary.insert(plaintext_on_boundary.end(), (uint8_t *)&pad_len_be, + (uint8_t *)&pad_len_be + sizeof(pad_len_be)); + // insert plaintext + plaintext_on_boundary.insert(plaintext_on_boundary.end(), &message.front(), + &message.front() + max_len); + } else { + if (message.size() > max_len) { + throw invalid_argument( + "HashedElGamalCiphertext::encrypt the plaintext is greater than max_len"); + } - //uint32_t original_plaintext_len = plaintext.size(); - uint16_t pad_len = max_len - message.size(); - uint16_t pad_len_be = htobe16(pad_len); + uint16_t pad_len = max_len - message.size(); + uint16_t pad_len_be = htobe16(pad_len); - std::vector padding(pad_len, 0); + std::vector padding(pad_len, 0); - // insert length in big endian form - plaintext_on_boundary.insert(plaintext_on_boundary.end(), (uint8_t *)&pad_len_be, - (uint8_t *)&pad_len_be + sizeof(pad_len_be)); - // insert plaintext - plaintext_on_boundary.insert(plaintext_on_boundary.end(), message.begin(), - message.end()); + // insert length in big endian form + plaintext_on_boundary.insert(plaintext_on_boundary.end(), (uint8_t *)&pad_len_be, + (uint8_t *)&pad_len_be + sizeof(pad_len_be)); + // insert plaintext + plaintext_on_boundary.insert(plaintext_on_boundary.end(), message.begin(), + message.end()); - // we dont pad 0x00s if the length field plus plaintext length falls on a block length boundary - if (pad_len > 0) { - // insert padding - plaintext_on_boundary.insert(plaintext_on_boundary.end(), padding.begin(), - padding.end()); + // we dont pad 0x00s if the length field plus plaintext length falls on a block length boundary + if (pad_len > 0) { + // insert padding + plaintext_on_boundary.insert(plaintext_on_boundary.end(), padding.begin(), + padding.end()); + } } } else { if (0 != (message.size() % HASHED_CIPHERTEXT_BLOCK_LENGTH)) { @@ -485,7 +502,7 @@ namespace electionguard } uint32_t plaintext_len = plaintext_on_boundary.size(); - uint32_t num_xor_keys = plaintext_len / HASHED_CIPHERTEXT_BLOCK_LENGTH; + uint32_t number_of_blocks = plaintext_len / HASHED_CIPHERTEXT_BLOCK_LENGTH; unique_ptr g_to_r = nullptr; unique_ptr publicKey_to_r = nullptr; @@ -500,16 +517,16 @@ namespace electionguard publicKey_to_r = pow_mod_p(publicKey, nonce); } - - // hash g_to_r and publicKey_to_r to get the master key - auto master_key = hash_elems({g_to_r.get(), publicKey_to_r.get()}); + // hash g_to_r and publicKey_to_r to get the session key + auto session_key = hash_elems({g_to_r.get(), publicKey_to_r.get()}); uint32_t plaintext_index = 0; - for (uint32_t i = 0; i < num_xor_keys; i++) { + for (uint32_t i = 0; i < number_of_blocks; i++) { vector temp_ciphertext(HASHED_CIPHERTEXT_BLOCK_LENGTH, 0); - vector xor_key = get_hmac(master_key->toBytes(), descriptionHash.toBytes(), - descriptionHash.toBytes().size(), i + 1); + vector xor_key = + get_hmac(session_key->toBytes(), encryption_seed.toBytes(), + number_of_blocks * HASHED_BLOCK_LENGTH_IN_BITS, i + 1); // XOR the key with the plaintext for (int j = 0; j < (int)HASHED_CIPHERTEXT_BLOCK_LENGTH; j++) { @@ -523,8 +540,8 @@ namespace electionguard Lib_Memzero0_memzero(&temp_ciphertext.front(), temp_ciphertext.size()); } - vector mac_key = get_hmac(master_key->toBytes(), descriptionHash.toBytes(), - descriptionHash.toBytes().size(), 0); + vector mac_key = get_hmac(session_key->toBytes(), encryption_seed.toBytes(), + number_of_blocks * HASHED_BLOCK_LENGTH_IN_BITS, 0); // calculate the mac (c0 is g ^ r mod p and c1 is the ciphertext, they are concatenated) vector c0_and_c1(g_to_r->toBytes()); diff --git a/src/electionguard/encrypt.cpp b/src/electionguard/encrypt.cpp index 4298f477..10bb805c 100644 --- a/src/electionguard/encrypt.cpp +++ b/src/electionguard/encrypt.cpp @@ -4,10 +4,11 @@ #include "electionguard/ballot_code.hpp" #include "electionguard/elgamal.hpp" #include "electionguard/hash.hpp" -#include "electionguard/precompute_buffers.hpp" #include "log.hpp" #include "nonces.hpp" #include "utils.hpp" +#include "electionguard/precompute_buffers.hpp" +#include #include #include @@ -25,6 +26,7 @@ using std::unique_ptr; using std::vector; using electionguard::getSystemTimestamp; +using nlohmann::json; namespace electionguard { @@ -262,8 +264,8 @@ namespace electionguard auto pubkey_to_exp = triple1->get_pubkey_to_exp(); // Generate the encryption using precomputed values - ciphertext = - elgamalEncrypt_with_precomputed(selection.getVote(), *g_to_exp, *pubkey_to_exp); + ciphertext = elgamalEncrypt_with_precomputed(selection.getVote(), + *g_to_exp, *pubkey_to_exp); if (ciphertext == nullptr) { throw runtime_error("encryptSelection:: Error generating ciphertext"); } @@ -275,16 +277,16 @@ namespace electionguard encrypted = CiphertextBallotSelection::make_with_precomputed( selection.getObjectId(), description.getSequenceOrder(), *descriptionHash, move(ciphertext), cryptoExtendedBaseHash, selection.getVote(), - move(precomputedTwoTriplesAndAQuad), isPlaceholder, true); + move(precomputedTwoTriplesAndAQuad), + isPlaceholder, true); } else { // Generate the encryption - ciphertext = elgamalEncrypt(selection.getVote(), *selectionNonce, elgamalPublicKey); + ciphertext = + elgamalEncrypt(selection.getVote(), *selectionNonce, elgamalPublicKey); if (ciphertext == nullptr) { throw runtime_error("encryptSelection:: Error generating ciphertext"); } - // TODO: ISSUE #134: encrypt/decrypt: encrypt the extended_data field - encrypted = CiphertextBallotSelection::make( selection.getObjectId(), description.getSequenceOrder(), *descriptionHash, move(ciphertext), elgamalPublicKey, cryptoExtendedBaseHash, selection.getVote(), @@ -302,22 +304,93 @@ namespace electionguard // verify the selection. if (encrypted->isValidEncryption(*descriptionHash, elgamalPublicKey, - cryptoExtendedBaseHash)) { + cryptoExtendedBaseHash)) { return encrypted; } - throw runtime_error("encryptSelection failed validity check"); + throw runtime_error("encryptSelection failed validity check"); + } + + string getOvervoteAndWriteIns(const PlaintextBallotContest &contest, + const InternalManifest &internalManifest, + eg_valid_contest_return_type_t is_overvote) + { + json overvoteAndWriteIns; + auto selections = contest.getSelections(); + + // if an overvote is detected then put the selections into json + if (is_overvote == OVERVOTE) { + overvoteAndWriteIns["error"] = "overvote"; + json errorData; + // run through the selections in this contest and see if any of them are writeins + // the number of selections should be short, the number of ballot selections + // and candidates will be longer but shouldn't be too long + for (const auto &selection : selections) { + if (selection.get().getVote() == 1) { + errorData.push_back(selection.get().getObjectId()); + } + } + overvoteAndWriteIns["error_data"] = errorData; + } + + json writeins; + auto candidates = internalManifest.getCandidates(); + std::vector> ballotSelections; + + // find the contest in the manifest + for (const auto &manifestContest : internalManifest.getContests()) { + if (contest.getObjectId() == manifestContest.get().getObjectId()) { + ballotSelections = manifestContest.get().getSelections(); + } + } + + // run through the selections in this contest and see if any of them are writeins + // the number of selections should be short, the number of ballot selections + // and candidates will be longer but shouldn't be too long + for (const auto &selection : selections) { + if (selection.get().getVote() == 1) { + for (const auto ballotSelection : ballotSelections) { + if (selection.get().getObjectId() == ballotSelection.get().getObjectId()) { + for (const auto &candidate : candidates) { + // check if the candidate is the correct one and if it is the writein option + if (ballotSelection.get().getCandidateId() == + candidate.get().getObjectId()) { + if (candidate.get().isWriteIn()) { + writeins[selection.get().getObjectId()] = + selection.get().getWriteIn(); + } + } + } + } + } + } + } + + if (writeins.dump() != string("null")) { + overvoteAndWriteIns["write_ins"] = writeins; + } + + string overvoteAndWriteIns_string(""); + if (overvoteAndWriteIns.dump() != string("null")) { + overvoteAndWriteIns_string = overvoteAndWriteIns.dump(); + } + + return overvoteAndWriteIns_string; } unique_ptr encryptContest(const PlaintextBallotContest &contest, + const InternalManifest &internalManifest, const ContestDescriptionWithPlaceholders &description, const ElementModP &elgamalPublicKey, const ElementModQ &cryptoExtendedBaseHash, const ElementModQ &nonceSeed, bool shouldVerifyProofs /* = true */) { // Validate Input - if (!contest.isValid(description.getObjectId(), description.getSelections().size(), - description.getNumberElected(), description.getVotesAllowed())) { + bool supportOvervotes = true; + eg_valid_contest_return_type_t is_valid_contest = + contest.isValid(description.getObjectId(), description.getSelections().size(), + description.getNumberElected(), description.getVotesAllowed(), supportOvervotes); + if ((is_valid_contest != SUCCESS) && (is_valid_contest != OVERVOTE)) { throw invalid_argument("the plaintext contest was invalid"); } @@ -333,14 +406,18 @@ namespace electionguard auto chaumPedersenNonce = nonceSequence->next(); std::shared_ptr sharedNonce(move(contestNonce)); - vector>> tasks; vector> encryptedSelections; + // get the writein data if there is any + string extendedData = getOvervoteAndWriteIns(contest, internalManifest, is_valid_contest); + // TODO: ISSUE #36: this code could be inefficient if we had a contest // with a lot of choices, although the O(n^2) iteration here is small // compared to the huge cost of doing the cryptography. uint64_t selectionCount = 0; + unique_ptr duplicate_selection; + // iterate over the actual selections for each contest description // and apply the selected value if it exists. If it does not, an explicit // false is entered instead and the selection_count is not incremented @@ -358,16 +435,20 @@ namespace electionguard // track the selection count so we can append the // appropriate number of true placeholder votes - const auto slection_ptr = &selection->get(); - selectionCount += selection->get().getVote(); + auto selection_ptr = &selection->get(); + + // if the is an overvote then we need to make all the selection votes 0 + if (is_valid_contest == OVERVOTE) { + duplicate_selection = make_unique( + selection_ptr->getObjectId(), 0, false); + selection_ptr = duplicate_selection.get(); + } - tasks.push_back(Scheduler::submit([=] { - auto encrypted = encryptSelection( - *slection_ptr, selectionDescription.get(), *elgamalPublicKey_ptr, - *cryptoExtendedBaseHash_ptr, *sharedNonce.get(), false, shouldVerifyProofs); - return encrypted; - })); + selectionCount += selection_ptr->getVote(); + encryptedSelections.push_back(encryptSelection( + *selection_ptr, selectionDescription.get(), *elgamalPublicKey_ptr, + *cryptoExtendedBaseHash_ptr, *sharedNonce.get(), false, shouldVerifyProofs)); } else { // Should never happen since the contest is normalized by emplaceMissingValues throw runtime_error("encryptedContest:: Error constructing encrypted selection"); @@ -378,26 +459,39 @@ namespace electionguard // After we loop through all of the real selections on the ballot, // we loop through each placeholder value and determine if it should be filled in for (const auto &placeholder : description.getPlaceholders()) { - // for undervotes, select the placeholder value as true for each available seat - // note this pattern is used since DisjunctiveChaumPedersen expects a 0 or 1 - // so each seat can only have a maximum value of 1 in the current implementation bool selectPlaceholder = false; - if (selectionCount < description.getNumberElected()) { + // if the is an overvote then we don't count any of the selections + if (is_valid_contest == OVERVOTE) { selectPlaceholder = true; - selectionCount += 1; + } else { + // for undervotes, select the placeholder value as true for each available seat + // note this pattern is used since DisjunctiveChaumPedersen expects a 0 or 1 + // so each seat can only have a maximum value of 1 in the current implementation + if (selectionCount < description.getNumberElected()) { + selectPlaceholder = true; + selectionCount += 1; + } } - tasks.push_back(Scheduler::submit([=] { - auto placeholderSelection = selectionFrom(placeholder, true, selectPlaceholder); - auto encrypted = encryptSelection( + auto placeholderSelection = selectionFrom(placeholder, true, selectPlaceholder); + encryptedSelections.push_back(encryptSelection( *placeholderSelection, placeholder, *elgamalPublicKey_ptr, - *cryptoExtendedBaseHash_ptr, *sharedNonce.get(), false, shouldVerifyProofs); - return encrypted; - })); + *cryptoExtendedBaseHash_ptr, *sharedNonce.get(), false, shouldVerifyProofs)); } - // wait for all tasks to complete - encryptedSelections = wait_all(tasks); + // Derive the extendedDataNonce from the selection nonce and a constant + auto noncesForExtendedData = + make_unique(*sharedNonce->clone(), "constant-extended-data"); + auto extendedDataNonce = noncesForExtendedData->get(0); + + vector extendedData_plaintext((uint8_t *)&extendedData.front(), + (uint8_t *)&extendedData.front() + + extendedData.size()); + + // Perform HashedElGamalCiphertext calculation + unique_ptr hashedElGamal = + hashedElgamalEncrypt(extendedData_plaintext, *extendedDataNonce, elgamalPublicKey, + cryptoExtendedBaseHash, BYTES_512, true); // TODO: ISSUE #33: support other cases such as cumulative voting // (individual selections being an encryption of > 1) @@ -410,7 +504,8 @@ namespace electionguard auto encryptedContest = CiphertextBallotContest::make( contest.getObjectId(), description.getSequenceOrder(), *descriptionHash, move(encryptedSelections), elgamalPublicKey, cryptoExtendedBaseHash, *chaumPedersenNonce, - description.getNumberElected(), sharedNonce->clone()); + description.getNumberElected(), sharedNonce->clone(), nullptr, + nullptr, move(hashedElGamal)); if (encryptedContest == nullptr || encryptedContest->getProof() == nullptr) { throw runtime_error("encryptedContest:: Error constructing encrypted constest"); @@ -446,7 +541,8 @@ namespace electionguard if (contest.get().getObjectId() == description.get().getObjectId()) { hasContest = true; auto encrypted = encryptContest( - contest.get(), description.get(), *context.getElGamalPublicKey(), + contest.get(), internalManifest, description.get(), + *context.getElGamalPublicKey(), *context.getCryptoExtendedBaseHash(), nonceSeed, shouldVerifyProofs); encryptedContests.push_back(move(encrypted)); diff --git a/src/electionguard/facades/ballot.cpp b/src/electionguard/facades/ballot.cpp index dad31741..58a8e324 100644 --- a/src/electionguard/facades/ballot.cpp +++ b/src/electionguard/facades/ballot.cpp @@ -105,17 +105,14 @@ eg_plaintext_ballot_selection_new(const char *in_object_id, uint64_t in_vote, } } -eg_electionguard_status_t eg_plaintext_ballot_selection_new_with_extended_data( +eg_electionguard_status_t eg_plaintext_ballot_selection_new_with_write_in( const char *in_object_id, const uint64_t in_vote, bool in_is_placeholder_selection, - const char *in_extended_data_value, uint64_t in_extended_data_length, + const char *in_write_in, eg_plaintext_ballot_selection_t **out_handle) { try { - auto extendedData = - make_unique(string(in_extended_data_value), in_extended_data_length); - auto result = make_unique( - in_object_id, in_vote, in_is_placeholder_selection, move(extendedData)); + in_object_id, in_vote, in_is_placeholder_selection, in_write_in); *out_handle = AS_TYPE(eg_plaintext_ballot_selection_t, result.release()); return ELECTIONGUARD_STATUS_SUCCESS; } catch (const exception &e) { @@ -168,16 +165,16 @@ bool eg_plaintext_ballot_selection_get_is_placeholder(eg_plaintext_ballot_select } eg_electionguard_status_t -eg_plaintext_ballot_selection_get_extended_data(eg_plaintext_ballot_selection_t *handle, - eg_extended_data_t **out_extended_data_ref) +eg_plaintext_ballot_selection_get_write_in(eg_plaintext_ballot_selection_t *handle, + char **out_write_in) { try { - auto *reference = AS_TYPE(PlaintextBallotSelection, handle)->getExtendedData(); - *out_extended_data_ref = AS_TYPE(eg_extended_data_t, reference); + auto result = AS_TYPE(PlaintextBallotSelection, handle)->getWriteIn(); + *out_write_in = dynamicCopy(result); return ELECTIONGUARD_STATUS_SUCCESS; } catch (const exception &e) { - Log::error("eg_plaintext_ballot_selection_get_extended_data", e); + Log::error("eg_plaintext_ballot_selection_get_write_in", e); return ELECTIONGUARD_STATUS_ERROR_BAD_ALLOC; } } diff --git a/src/electionguard/facades/encrypt.cpp b/src/electionguard/facades/encrypt.cpp index 4d075b89..75269acf 100644 --- a/src/electionguard/facades/encrypt.cpp +++ b/src/electionguard/facades/encrypt.cpp @@ -245,20 +245,22 @@ eg_electionguard_status_t eg_encrypt_selection(eg_plaintext_ballot_selection_t * #pragma region EncryptContest eg_electionguard_status_t eg_encrypt_contest( - eg_plaintext_ballot_contest_t *in_plaintext, + eg_plaintext_ballot_contest_t *in_plaintext, eg_internal_manifest_t *in_manifest, eg_contest_description_with_placeholders_t *in_description, eg_element_mod_p_t *in_public_key, eg_element_mod_q_t *in_crypto_extended_base_hash, eg_element_mod_q_t *in_nonce_seed, bool in_should_verify_proofs, eg_ciphertext_ballot_contest_t **out_handle) { try { auto *plaintext = AS_TYPE(PlaintextBallotContest, in_plaintext); + auto *internalManifest = AS_TYPE(InternalManifest, in_manifest); auto *description = AS_TYPE(ContestDescriptionWithPlaceholders, in_description); auto *public_key = AS_TYPE(ElementModP, in_public_key); auto *crypto_extended_base_hash = AS_TYPE(ElementModQ, in_crypto_extended_base_hash); auto *nonce_seed_ = AS_TYPE(ElementModQ, in_nonce_seed); auto ciphertext = - encryptContest(*plaintext, *description, *public_key, *crypto_extended_base_hash, + encryptContest(*plaintext, *internalManifest, *description, *public_key, + *crypto_extended_base_hash, *nonce_seed_, in_should_verify_proofs); *out_handle = AS_TYPE(eg_ciphertext_ballot_contest_t, ciphertext.release()); diff --git a/src/electionguard/group.cpp b/src/electionguard/group.cpp index 6c7926f9..4794df20 100644 --- a/src/electionguard/group.cpp +++ b/src/electionguard/group.cpp @@ -829,6 +829,8 @@ namespace electionguard return add_mod_q(*random_q, ZERO_MOD_Q()); } + string vector_uint8_t_to_hex(const vector &bytes) { return bytes_to_hex(bytes); } + #pragma endregion } // namespace electionguard diff --git a/src/electionguard/hash.cpp b/src/electionguard/hash.cpp index af410657..8ef49306 100644 --- a/src/electionguard/hash.cpp +++ b/src/electionguard/hash.cpp @@ -48,7 +48,8 @@ namespace electionguard VECTOR_ELEMENTMODP_CONST_REF = 19, VECTOR_ELEMENTMODQ_CONST_REF = 20, VECTOR_UINT64_T = 21, - VECTOR_STRING = 22 + VECTOR_STRING = 22, + VECTOR_UINT8_T = 23 }; const char delimiter_char = '|'; @@ -235,6 +236,12 @@ namespace electionguard { return hash_inner_vector(get>(a)); } + case VECTOR_UINT8_T: // vector + { + vector temp = get>(a); + return vector_uint8_t_to_hex(temp); + } + } return null_string; diff --git a/src/electionguard/manifest.cpp b/src/electionguard/manifest.cpp index 7f55bcf5..93d45ed9 100644 --- a/src/electionguard/manifest.cpp +++ b/src/electionguard/manifest.cpp @@ -1803,14 +1803,17 @@ namespace electionguard struct InternalManifest::Impl { vector> geopoliticalUnits; + vector> candidates; vector> contests; vector> ballotStyles; unique_ptr manifestHash; Impl(vector> geopoliticalUnits, + vector> candidates, vector> contests, vector> ballotStyles, unique_ptr manifestHash) - : geopoliticalUnits(move(geopoliticalUnits)), contests(move(contests)), + : geopoliticalUnits(move(geopoliticalUnits)), candidates(move(candidates)), + contests(move(contests)), ballotStyles(move(ballotStyles)), manifestHash(move(manifestHash)) { } @@ -1822,15 +1825,17 @@ namespace electionguard InternalManifest::InternalManifest( vector> geopoliticalUnits, + vector> candidates, vector> contests, vector> ballotStyles, const ElementModQ &manifestHash) - : pimpl(new Impl(move(geopoliticalUnits), move(contests), move(ballotStyles), + : pimpl(new Impl(move(geopoliticalUnits), move(candidates), move(contests), + move(ballotStyles), make_unique(manifestHash))) { } InternalManifest::InternalManifest(const Manifest &description) - : pimpl(new Impl(copyGeopoliticalUnits(description), + : pimpl(new Impl(copyGeopoliticalUnits(description), copyCandidates(description), generateContestsWithPlaceholders(description), copyBallotStyles(description), description.crypto_hash())) { @@ -1861,6 +1866,16 @@ namespace electionguard return references; } + vector> InternalManifest::getCandidates() const + { + vector> references; + references.reserve(pimpl->candidates.size()); + for (auto &reference : pimpl->candidates) { + references.push_back(ref(*reference)); + } + return references; + } + vector> InternalManifest::getContests() const { @@ -1997,6 +2012,17 @@ namespace electionguard return collection; } + vector> InternalManifest::copyCandidates(const Manifest &description) + { + vector> collection; + auto source = description.getCandidates(); + collection.reserve(source.size()); + for (auto element : source) { + collection.push_back(make_unique(element.get())); + } + return collection; + } + vector> InternalManifest::copyBallotStyles(const Manifest &description) { vector> collection; diff --git a/src/electionguard/serialize.hpp b/src/electionguard/serialize.hpp index bf0edab5..b694ec1d 100644 --- a/src/electionguard/serialize.hpp +++ b/src/electionguard/serialize.hpp @@ -586,6 +586,8 @@ namespace electionguard auto geopoliticalUnits = geopoliticalUnitsToJson(serializable.getGeopoliticalUnits()); + auto candidates = candidatesToJson(serializable.getCandidates()); + // Contests json contests; for (const auto &contest : serializable.getContests()) { @@ -625,6 +627,7 @@ namespace electionguard json result = {{"manifest_hash", serializable.getManifestHash()->toHex()}, {"geopolitical_units", geopoliticalUnits}, + {"candidates", candidates}, {"contests", contests}, {"ballot_styles", ballotStyles}}; @@ -634,6 +637,8 @@ namespace electionguard { auto geopoliticalUnits = geopoliticalUnitsFromJson(j["geopolitical_units"]); + auto candidates = candidatesFromJson(j["candidates"]); + auto contests = j["contests"]; vector> contestDescriptions; @@ -680,7 +685,7 @@ namespace electionguard auto manifestHash = ElementModQ::fromHex(manifest_hash); return make_unique( - move(geopoliticalUnits), move(contestDescriptions), move(ballotStyles), + move(geopoliticalUnits), move(candidates), move(contestDescriptions), move(ballotStyles), *manifestHash); } @@ -808,12 +813,11 @@ namespace electionguard for (auto contest : serializable.getContests()) { json selections; - // TODO: #134 extended data - for (auto selection : contest.get().getSelections()) { json s = {{"object_id", selection.get().getObjectId()}, {"vote", selection.get().getVote()}, - {"is_placeholder_selection", selection.get().getIsPlaceholder()}}; + {"is_placeholder_selection", selection.get().getIsPlaceholder()}, + {"write_in", selection.get().getWriteIn()}}; selections.push_back(s); } contests.push_back({ @@ -853,11 +857,15 @@ namespace electionguard isPlaceholder = selection["is_placeholder_selection"].get(); } - // TODO: #134 extended data - + string write_in; + if (selection.contains("write_in") && + !selection["write_in"].is_null()) { + write_in = selection["write_in"].get(); + } + plaintextSelections.push_back( make_unique( - selection_object_id, vote, isPlaceholder)); + selection_object_id, vote, isPlaceholder, write_in)); } plaintextContests.push_back(make_unique( @@ -905,16 +913,16 @@ namespace electionguard selections.push_back(selection); } - json extendedData; - for (auto &[key, value] : serializable.getExtendedData()) { - json data = {{"value", value.get().value}, {"length", value.get().length}}; - extendedData.emplace(to_string(key), data); + json writeins; + for (auto writein : serializable.getWriteIns()) { + writeins.push_back(writein); } json result = {{"object_id", serializable.getObjectId()}, {"style_id", serializable.getStyleId()}, {"selections", selections}, - {"extended_data", extendedData}}; + {"writeins", writeins}}; + return result; } static unique_ptr toObject(json j) @@ -923,6 +931,7 @@ namespace electionguard auto style_id = j["style_id"].get(); auto selections = j["selections"]; + auto in_writeins = j["writeins"]; vector votes; votes.reserve(selections.size()); @@ -931,18 +940,13 @@ namespace electionguard votes.push_back(vote); } - map> extendedDataMap; - if (j.contains("extended_data") && !j["extended_data"].is_null()) { - for (auto &[key, body] : j["extended_data"].items()) { - auto value = body["value"].get(); - auto length = body["length"].get(); - extendedDataMap.emplace(stoul(key), - make_unique(value, length)); - } + vector writeins; + for (auto &writein_json : in_writeins) { + writeins.push_back(writein_json.dump()); } return make_unique( - object_id, style_id, move(selections), move(extendedDataMap)); + object_id, style_id, move(selections), move(writeins)); } public: @@ -1027,6 +1031,12 @@ namespace electionguard {"response", p->getResponse()->toHex()}, {"constant", p->getConstant()}, }; + auto e = contest.get().getHashedElGamalCiphertext(); + json extended_data = { + {"pad", e->getPad()->toHex()}, + {"data", bytes_to_hex(e->getData())}, + {"mac", bytes_to_hex(e->getMac())}, + }; json contest_props = { {"object_id", contest.get().getObjectId()}, {"sequence_order", contest.get().getSequenceOrder()}, @@ -1035,6 +1045,7 @@ namespace electionguard {"ciphertext_accumulation", ciphertext}, {"crypto_hash", contest.get().getCryptoHash()->toHex()}, {"proof", contest_proof}, + {"extended_data", extended_data}, }; if (withNonces) { contest_props["nonce"] = contest.get().getNonce()->toHex(); @@ -1088,6 +1099,20 @@ namespace electionguard ElementModP::fromHex(contest_ciphertext_data)); auto contest_crypto_hash = contest["crypto_hash"].get(); + auto hashed_el_gamal = contest["extended_data"]; + auto hashed_el_gamal_pad = hashed_el_gamal["pad"].get(); + auto hashed_el_gamal_data = hashed_el_gamal["data"].get(); + auto hashed_el_gamal_mac = hashed_el_gamal["mac"].get(); + + auto hashed_el_gamal_data_sanitized = sanitize_hex_string(hashed_el_gamal_data); + auto hashed_el_gamal_mac_sanitized = sanitize_hex_string(hashed_el_gamal_mac); + + auto deserializedHashedElGamal = + make_unique( + ElementModP::fromHex(hashed_el_gamal_pad), + hex_to_bytes(hashed_el_gamal_data_sanitized), + hex_to_bytes(hashed_el_gamal_mac_sanitized)); + auto proof = contest["proof"]; auto contest_proof_pad = proof["pad"].get(); auto contest_proof_data = proof["data"].get(); @@ -1176,7 +1201,8 @@ namespace electionguard contest_object_id, contest_sequence_order, *ElementModQ::fromHex(contest_description_hash), move(ciphertextSelections), move(nonce), move(deserialized_contest_ciphertext), - ElementModQ::fromHex(contest_crypto_hash), move(deserializedProof))); + ElementModQ::fromHex(contest_crypto_hash), move(deserializedProof), + move(deserializedHashedElGamal))); } auto nonce = ballot_nonce.empty() ? make_unique(ZERO_MOD_Q()) diff --git a/test/electionguard/benchmark/bench_hashed_elgamal.cpp b/test/electionguard/benchmark/bench_hashed_elgamal.cpp index 3150083c..4b1a7273 100644 --- a/test/electionguard/benchmark/bench_hashed_elgamal.cpp +++ b/test/electionguard/benchmark/bench_hashed_elgamal.cpp @@ -14,7 +14,7 @@ class HashedElgamalEncryptFixture : public benchmark::Fixture public: void SetUp(const ::benchmark::State &state) { - + nonce = rand_q(); secret = ElementModQ::fromHex(a_fixed_secret); keypair = ElGamalKeyPair::fromSecret(TWO_MOD_Q(), false); @@ -29,8 +29,7 @@ class HashedElgamalEncryptFixture : public benchmark::Fixture plaintext = plain; std::unique_ptr HEGResult = hashedElgamalEncrypt( - plaintext, *nonce, *keypair->getPublicKey(), *descriptionHash, NO_PADDING); - + plaintext, *nonce, *keypair->getPublicKey(), *descriptionHash, NO_PADDING, false); } void TearDown(const ::benchmark::State &state) {} @@ -45,8 +44,8 @@ class HashedElgamalEncryptFixture : public benchmark::Fixture BENCHMARK_DEFINE_F(HashedElgamalEncryptFixture, HashedElGamalEncrypt)(benchmark::State &state) { for (auto _ : state) { - hashedElgamalEncrypt(plaintext, *nonce, *keypair->getPublicKey(), - *descriptionHash, NO_PADDING); + hashedElgamalEncrypt(plaintext, *nonce, *keypair->getPublicKey(), *descriptionHash, + NO_PADDING, false); } } @@ -72,7 +71,7 @@ class HashedElgamalEncryptPrecomputeFixture : public benchmark::Fixture plaintext = plain; std::unique_ptr HEGResult = hashedElgamalEncrypt( - plaintext, *nonce, *keypair->getPublicKey(), *descriptionHash, NO_PADDING); + plaintext, *nonce, *keypair->getPublicKey(), *descriptionHash, NO_PADDING, false); // cause precomputed entries that will be used by the selection // encryptions, that should be more than enough and on teardown @@ -98,11 +97,10 @@ BENCHMARK_DEFINE_F(HashedElgamalEncryptPrecomputeFixture, HashedElGamalEncryptPr { for (auto _ : state) { hashedElgamalEncrypt(plaintext, *nonce, *keypair->getPublicKey(), *descriptionHash, - NO_PADDING); + NO_PADDING, false); } } BENCHMARK_REGISTER_F(HashedElgamalEncryptPrecomputeFixture, HashedElGamalEncryptPrecompute) ->Unit(benchmark::kMillisecond); - diff --git a/test/electionguard/generators/manifest.hpp b/test/electionguard/generators/manifest.hpp index 265fe74d..1ce2e35a 100644 --- a/test/electionguard/generators/manifest.hpp +++ b/test/electionguard/generators/manifest.hpp @@ -19,6 +19,10 @@ using namespace std; # define TEST_USE_SAMPLE "hamilton-general" #endif +#ifndef TEST_USE_FULL_SAMPLE +# define TEST_USE_FULL_SAMPLE "full" +#endif + namespace electionguard::tools::generators { class ManifestGenerator diff --git a/test/electionguard/test_ballot.c b/test/electionguard/test_ballot.c index d51e0d0d..a76a4686 100644 --- a/test/electionguard/test_ballot.c +++ b/test/electionguard/test_ballot.c @@ -134,13 +134,15 @@ bool test_ballot_serialization(void) // Arrange char *json = "{\"contests\":[{\"ballot_selections\":[{\"is_" - "placeholder_selection\":false,\"object_id\":\"contest-1-selection-1-id\",\"vote\":1},{\"is_" - "placeholder_selection\":false,\"object_id\":\"contest-1-selection-2-id\",\"vote\":0},{\"is_" - "placeholder_selection\":false,\"object_id\":\"contest-1-selection-3-id\",\"vote\":0}]," - "\"object_id\":\"contest-1-id\"},{\"ballot_selections\":[{\"is_placeholder_selection\":false," - "\"object_id\":\"contest-2-selection-1-id\",\"vote\":1},{\"is_placeholder_selection\":false," - "\"object_id\":\"contest-2-selection-2-id\",\"vote\":0}],\"object_id\":\"contest-2-id\"}]," - "\"object_id\":\"ballot-id-123\",\"style_id\":\"ballot-style-1\"}"; + "placeholder_selection\":false,\"object_id\":\"contest-1-selection-1-id\",\"vote\":1," + "\"write_in\":\"\"},{\"is_placeholder_selection\":false,\"object_id\":\"contest-1-sel" + "ection-2-id\",\"vote\":0,\"write_in\":\"\"},{\"is_placeholder_selection\":false,\"ob" + "ject_id\":\"contest-1-selection-3-id\",\"vote\":0,\"write_in\":\"\"}],\"object_id\":" + "\"contest-1-id\"},{\"ballot_selections\":[{\"is_placeholder_selection\":false,\"obje" + "ct_id\":\"contest-2-selection-1-id\",\"vote\":1,\"write_in\":\"\"},{\"is_placeholder" + "_selection\":false,\"object_id\":\"contest-2-selection-2-id\",\"vote\":0,\"write_in" + "\":\"\"}],\"object_id\":\"contest-2-id\"}],\"object_id\":\"ballot-id-123\",\"style_i" + "d\":\"ballot-style-1\"}"; // Act eg_plaintext_ballot_t *fromJson = NULL; diff --git a/test/electionguard/test_elgamal.cpp b/test/electionguard/test_elgamal.cpp index de49c1be..3e005a2f 100644 --- a/test/electionguard/test_elgamal.cpp +++ b/test/electionguard/test_elgamal.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include using namespace electionguard; @@ -174,14 +175,16 @@ TEST_CASE("HashedElGamalCiphertext encrypt and decrypt data") 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}; const auto nonce = make_unique(qwords_to_use); - const auto descriptionHash = make_unique(qwords_to_use); + const auto cryptoExtendedBaseHash = make_unique(qwords_to_use); const auto &secret = TWO_MOD_Q(); auto keypair = ElGamalKeyPair::fromSecret(secret, false); auto *publicKey = keypair->getPublicKey(); vector plaintext(bytes_to_use, bytes_to_use + sizeof(bytes_to_use)); - std::unique_ptr HEGResult = - hashedElgamalEncrypt(plaintext, *nonce, *publicKey, *descriptionHash, NO_PADDING); + std::unique_ptr HEGResult = hashedElgamalEncrypt( + plaintext, *nonce, *publicKey, *cryptoExtendedBaseHash, NO_PADDING, false); + + unique_ptr hash_of_HEG = HEGResult->crypto_hash(); unique_ptr pad = make_unique(*HEGResult->getPad()); vector ciphertext = HEGResult->getData(); @@ -190,7 +193,7 @@ TEST_CASE("HashedElGamalCiphertext encrypt and decrypt data") unique_ptr newHEG = make_unique(move(pad), HEGResult->getData(), HEGResult->getMac()); - vector new_plaintext = newHEG->decrypt(secret, *descriptionHash, false); + vector new_plaintext = newHEG->decrypt(secret, *cryptoExtendedBaseHash, false); CHECK(plaintext == new_plaintext); } @@ -200,30 +203,28 @@ TEST_CASE("HashedElGamalCiphertext encrypt and decrypt data with padding but on uint64_t qwords_to_use[4] = {0x0102030405060708, 0x090a0b0c0d0e0f10, 0x1112131415161718, 0x191a1b1c1d1e1f20}; - uint8_t bytes_to_use[30] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, - 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, - 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e}; + uint8_t bytes_to_use[BYTES_512] = {0x09}; const auto nonce = make_unique(qwords_to_use); - const auto descriptionHash = make_unique(qwords_to_use); + const auto cryptoExtendedBaseHash = make_unique(qwords_to_use); const auto &secret = TWO_MOD_Q(); auto keypair = ElGamalKeyPair::fromSecret(secret, false); auto *publicKey = keypair->getPublicKey(); vector plaintext(bytes_to_use, bytes_to_use + sizeof(bytes_to_use)); std::unique_ptr HEGResult = - hashedElgamalEncrypt(plaintext, *nonce, *publicKey, *descriptionHash, BYTES_32); + hashedElgamalEncrypt(plaintext, *nonce, *publicKey, *cryptoExtendedBaseHash, BYTES_512, true); + + unique_ptr hash_of_HEG = HEGResult->crypto_hash(); unique_ptr pad = make_unique(*HEGResult->getPad()); vector ciphertext = HEGResult->getData(); vector mac = HEGResult->getMac(); - - CHECK(ciphertext.size() == 32); - unique_ptr newHEG = - make_unique(move(pad), HEGResult->getData(), HEGResult->getMac()); + make_unique(move(pad), ciphertext, mac); - vector new_plaintext = newHEG->decrypt(secret, *descriptionHash, true); + CHECK(ciphertext.size() == (BYTES_512 + sizeof(uint16_t))); + vector new_plaintext = newHEG->decrypt(secret, *cryptoExtendedBaseHash, true); CHECK(plaintext == new_plaintext); } @@ -233,12 +234,9 @@ TEST_CASE("HashedElGamalCiphertext encrypt and decrypt string data with padding" uint64_t qwords_to_use[4] = {0x0102030405060708, 0x090a0b0c0d0e0f10, 0x1112131415161718, 0x191a1b1c1d1e1f20}; - uint8_t bytes_to_use[32] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, - 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, - 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}; - + uint8_t bytes_to_use[BYTES_512] = {0x09}; const auto nonce = make_unique(qwords_to_use); - const auto descriptionHash = make_unique(qwords_to_use); + const auto cryptoExtendedBaseHash = make_unique(qwords_to_use); const auto &secret = TWO_MOD_Q(); auto keypair = ElGamalKeyPair::fromSecret(secret, false); auto *publicKey = keypair->getPublicKey(); @@ -249,31 +247,73 @@ TEST_CASE("HashedElGamalCiphertext encrypt and decrypt string data with padding" (plaintext_string.size() * 2)); auto HEGResult = - hashedElgamalEncrypt(plaintext, *nonce, *publicKey, *descriptionHash, BYTES_128); + hashedElgamalEncrypt(plaintext, *nonce, *publicKey, + *cryptoExtendedBaseHash, BYTES_512, true); + + unique_ptr hash_of_HEG = HEGResult->crypto_hash(); auto pad = HEGResult->getPad(); unique_ptr p_pad = make_unique(*pad); auto ciphertext = HEGResult->getData(); auto mac = HEGResult->getMac(); - CHECK(ciphertext.size() == 128); + CHECK(ciphertext.size() == (BYTES_512 + sizeof(uint16_t))); // now lets decrypt unique_ptr newHEG = make_unique(move(p_pad), HEGResult->getData(), HEGResult->getMac()); - vector new_plaintext = newHEG->decrypt(secret, *descriptionHash, true); + vector new_plaintext = newHEG->decrypt(secret, *cryptoExtendedBaseHash, true); CHECK(plaintext == new_plaintext); } +TEST_CASE("HashedElGamalCiphertext encrypt and decrypt string data with padding and truncate") +{ + uint64_t qwords_to_use[4] = {0x0102030405060708, 0x090a0b0c0d0e0f10, 0x1112131415161718, + 0x191a1b1c1d1e1f20}; + + uint8_t bytes_to_use[BYTES_512 + 20] = {0x1a}; + uint8_t truncated_bytes[BYTES_512] = {0x1a}; + + const auto nonce = make_unique(qwords_to_use); + const auto cryptoExtendedBaseHash = make_unique(qwords_to_use); + const auto &secret = TWO_MOD_Q(); + auto keypair = ElGamalKeyPair::fromSecret(secret, false); + auto *publicKey = keypair->getPublicKey(); + + vector plaintext(bytes_to_use, bytes_to_use + sizeof(bytes_to_use)); + + auto HEGResult = + hashedElgamalEncrypt(plaintext, *nonce, *publicKey, *cryptoExtendedBaseHash, BYTES_512, true); + + unique_ptr hash_of_HEG = HEGResult->crypto_hash(); + + auto pad = HEGResult->getPad(); + unique_ptr p_pad = make_unique(*pad); + auto ciphertext = HEGResult->getData(); + auto mac = HEGResult->getMac(); + + CHECK(ciphertext.size() == (BYTES_512 + sizeof(uint16_t))); + + // now lets decrypt + unique_ptr newHEG = + make_unique(move(p_pad), HEGResult->getData(), HEGResult->getMac()); + + vector new_plaintext = newHEG->decrypt(secret, *cryptoExtendedBaseHash, true); + + vector plaintext_truncated(truncated_bytes, truncated_bytes + sizeof(truncated_bytes)); + + CHECK(plaintext_truncated == new_plaintext); +} + TEST_CASE("HashedElGamalCiphertext encrypt and decrypt no data") { uint64_t qwords_to_use[4] = {0x0102030405060708, 0x090a0b0c0d0e0f10, 0x1112131415161718, 0x191a1b1c1d1e1f20}; const auto nonce = make_unique(qwords_to_use); - const auto descriptionHash = make_unique(qwords_to_use); + const auto cryptoExtendedBaseHash = make_unique(qwords_to_use); const auto &secret = TWO_MOD_Q(); auto keypair = ElGamalKeyPair::fromSecret(secret, false); auto *publicKey = keypair->getPublicKey(); @@ -282,19 +322,22 @@ TEST_CASE("HashedElGamalCiphertext encrypt and decrypt no data") CHECK(plaintext.size() == 0); auto HEGResult = - hashedElgamalEncrypt(plaintext, *nonce, *publicKey, *descriptionHash, BYTES_64); + hashedElgamalEncrypt(plaintext, *nonce, *publicKey, *cryptoExtendedBaseHash, BYTES_512, true); + + unique_ptr hash_of_HEG = HEGResult->crypto_hash(); auto pad = HEGResult->getPad(); unique_ptr p_pad = make_unique(*pad); auto ciphertext = HEGResult->getData(); auto mac = HEGResult->getMac(); - CHECK(ciphertext.size() == 64); // two more bytes than max_len input to encrypt + CHECK(ciphertext.size() == + (BYTES_512 + sizeof(uint16_t))); // two more bytes than max_len input to encrypt // now lets decrypt unique_ptr newHEG = make_unique(move(p_pad), HEGResult->getData(), HEGResult->getMac()); - vector new_plaintext = newHEG->decrypt(secret, *descriptionHash, true); + vector new_plaintext = newHEG->decrypt(secret, *cryptoExtendedBaseHash, true); CHECK(new_plaintext.size() == 0); CHECK(plaintext == new_plaintext); @@ -313,15 +356,15 @@ TEST_CASE("HashedElGamalCiphertext encrypt and decrypt data failure different no const auto nonce = make_unique(qwords_to_use); const auto different_secret = make_unique(different_qwords_to_use); - const auto descriptionHash = make_unique(qwords_to_use); + const auto cryptoExtendedBaseHash = make_unique(qwords_to_use); const auto &secret = TWO_MOD_Q(); auto keypair = ElGamalKeyPair::fromSecret(secret, false); auto *publicKey = keypair->getPublicKey(); vector plaintext(bytes_to_use, bytes_to_use + sizeof(bytes_to_use)); bool decrypt_failed = false; - std::unique_ptr HEGResult = - hashedElgamalEncrypt(plaintext, *nonce, *publicKey, *descriptionHash, NO_PADDING); + std::unique_ptr HEGResult = hashedElgamalEncrypt( + plaintext, *nonce, *publicKey, *cryptoExtendedBaseHash, NO_PADDING, false); unique_ptr pad = make_unique(*HEGResult->getPad()); vector ciphertext = HEGResult->getData(); @@ -331,7 +374,8 @@ TEST_CASE("HashedElGamalCiphertext encrypt and decrypt data failure different no make_unique(move(pad), HEGResult->getData(), HEGResult->getMac()); try { - vector new_plaintext = newHEG->decrypt(*different_secret, *descriptionHash, false); + vector new_plaintext = + newHEG->decrypt(*different_secret, *cryptoExtendedBaseHash, false); } catch (std::runtime_error &e) { decrypt_failed = true; } @@ -348,15 +392,15 @@ TEST_CASE("HashedElGamalCiphertext encrypt and decrypt data failure - tampered w 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}; const auto nonce = make_unique(qwords_to_use); - const auto descriptionHash = make_unique(qwords_to_use); + const auto cryptoExtendedBaseHash = make_unique(qwords_to_use); const auto &secret = TWO_MOD_Q(); auto keypair = ElGamalKeyPair::fromSecret(secret, false); auto *publicKey = keypair->getPublicKey(); vector plaintext(bytes_to_use, bytes_to_use + sizeof(bytes_to_use)); bool decrypt_failed = false; - std::unique_ptr HEGResult = - hashedElgamalEncrypt(plaintext, *nonce, *publicKey, *descriptionHash, NO_PADDING); + std::unique_ptr HEGResult = hashedElgamalEncrypt( + plaintext, *nonce, *publicKey, *cryptoExtendedBaseHash, NO_PADDING, false); unique_ptr pad = make_unique(*HEGResult->getPad()); vector ciphertext = HEGResult->getData(); @@ -373,7 +417,7 @@ TEST_CASE("HashedElGamalCiphertext encrypt and decrypt data failure - tampered w make_unique(move(pad), ciphertext, HEGResult->getMac()); try { - vector new_plaintext = newHEG->decrypt(secret, *descriptionHash, false); + vector new_plaintext = newHEG->decrypt(secret, *cryptoExtendedBaseHash, false); } catch (std::runtime_error &e) { decrypt_failed = true; } @@ -388,12 +432,9 @@ TEST_CASE("HashedElGamalCiphertext encrypt failure length cases") uint8_t bytes_to_use[32] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}; - uint8_t longer_bytes_to_use[40] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, - 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, - 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, - 0x1f, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28}; + uint8_t longer_bytes_to_use[513] = {0x01}; const auto nonce = make_unique(qwords_to_use); - const auto descriptionHash = make_unique(qwords_to_use); + const auto cryptoExtendedBaseHash = make_unique(qwords_to_use); const auto &secret = TWO_MOD_Q(); auto keypair = ElGamalKeyPair::fromSecret(secret, false); auto *publicKey = keypair->getPublicKey(); @@ -404,16 +445,16 @@ TEST_CASE("HashedElGamalCiphertext encrypt failure length cases") bool encrypt_no_pad_not_block_length_failed = false; try { - std::unique_ptr HEGResult = - hashedElgamalEncrypt(longer_plaintext, *nonce, *publicKey, *descriptionHash, BYTES_32); + std::unique_ptr HEGResult = hashedElgamalEncrypt( + longer_plaintext, *nonce, *publicKey, *cryptoExtendedBaseHash, BYTES_512, false); } catch (std::invalid_argument &e) { encrypt_longer_plaintext_failed = true; } CHECK(encrypt_longer_plaintext_failed); try { - std::unique_ptr HEGResult = - hashedElgamalEncrypt(longer_plaintext, *nonce, *publicKey, *descriptionHash, NO_PADDING); + std::unique_ptr HEGResult = hashedElgamalEncrypt( + longer_plaintext, *nonce, *publicKey, *cryptoExtendedBaseHash, NO_PADDING, false); } catch (std::invalid_argument &e) { encrypt_no_pad_not_block_length_failed = true; } @@ -430,7 +471,7 @@ TEST_CASE("HashedElGamalCiphertext encrypt and decrypt string data with padding 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}; const auto nonce = make_unique(qwords_to_use); - const auto descriptionHash = make_unique(qwords_to_use); + const auto cryptoExtendedBaseHash = make_unique(qwords_to_use); const auto &secret = TWO_MOD_Q(); auto keypair = ElGamalKeyPair::fromSecret(secret, false); auto *publicKey = keypair->getPublicKey(); @@ -448,20 +489,20 @@ TEST_CASE("HashedElGamalCiphertext encrypt and decrypt string data with padding PrecomputeBufferContext::stop_populate(); auto HEGResult = - hashedElgamalEncrypt(plaintext, *nonce, *publicKey, *descriptionHash, BYTES_128); + hashedElgamalEncrypt(plaintext, *nonce, *publicKey, *cryptoExtendedBaseHash, BYTES_512, true); auto pad = HEGResult->getPad(); unique_ptr p_pad = make_unique(*pad); auto ciphertext = HEGResult->getData(); auto mac = HEGResult->getMac(); - CHECK(ciphertext.size() == 128); + CHECK(ciphertext.size() == (BYTES_512 + sizeof(uint16_t))); // now lets decrypt unique_ptr newHEG = make_unique(move(p_pad), HEGResult->getData(), HEGResult->getMac()); - vector new_plaintext = newHEG->decrypt(secret, *descriptionHash, true); + vector new_plaintext = newHEG->decrypt(secret, *cryptoExtendedBaseHash, true); CHECK(plaintext == new_plaintext); PrecomputeBufferContext::empty_queues(); @@ -493,3 +534,72 @@ TEST_CASE("elgamalEncrypt_with_precomputed encrypt 1, decrypts with secret") CHECK(1UL == decrypted); PrecomputeBufferContext::empty_queues(); } + +TEST_CASE("HashedElGamalCiphertext encrypt and decrypt with hard coded data for interop") +{ + unique_ptr secret = + ElementModQ::fromHex("094CDA6CEB3332D62438B6D37BBA774D23C420FA019368671AD330AD50456603"); + auto keypair = ElGamalKeyPair::fromSecret(*secret, false); + auto *publicKey = keypair->getPublicKey(); + string plaintext_string("{\"error\":\"overvote\",\"error_data\":[\"john-adams-selection\"," + "\"benjamin-franklin-selection\",\"write-in-selection\"],\"write_ins\"" + ":{\"write-in-selection\":\"Susan B. Anthony\"}}"); + + vector plaintext(&plaintext_string.front(), + &plaintext_string.front() + plaintext_string.size()); + const auto nonce = + ElementModQ::fromHex("46B6532A1AD7B2AFCDDAA30EEE464884883804B46058DB38E76FCDC79EE5C702"); + const auto cryptoExtendedBaseHash = + ElementModQ::fromHex("6E418518C6C244CA58399C0F47A9C761BAE7B876F8F5360D8D15FCFF26A42BAA"); + + std::unique_ptr HEGResult = hashedElgamalEncrypt( + plaintext, *nonce, *publicKey, *cryptoExtendedBaseHash, BYTES_512, false); + + unique_ptr hash_of_HEG = HEGResult->crypto_hash(); + + unique_ptr pad = make_unique(*HEGResult->getPad()); + vector data = HEGResult->getData(); + vector mac = HEGResult->getMac(); + + string hard_coded_pad_string("C102BAB526517D74FE5D5C249E" + "7F422993C0306C40A9398FBAD01A0D3547B50BDFD77C6EFC187C7B1FD7918A0B3C2A2FB0A3776A7240F9A" + "75410569379B3D16877B547F52E79542C1129F6E369F2D006D0A1AA3919F0228CA07F5C9A4DFD1118A606" + "AA4B7000F9EDC65963F130663FD4F7246F7CFE7A38F1E1DC9BC0698CAB881DCD5A75E6D7165B329C28D80" + "B719D7A2ED50031A2448A4528275FF161F541CFE304A28CBE7193A4BF8676B2D4F2DE68F175C5B4BFD14B" + "4B1D9868D00E0BD95B6491C96460159DEABF85239B10A7C86B3D975EF58BBF833C6ABFFF223DAF78C1AE4" + "C6F64D084C4118F3B5A2618628FA18852BAB55DCE95C04FFCBBAF582D75C7B8B830424C74A8F8EACD1543" + "00FD67CF753EE14FCE94DDED95F1DD2C1386D92B3FF03A9D6EDEE0F67EC80C72E6425B4EA1C17D7B9CC5B" + "2165905373A4E304496462CE2BA077F195302A39C52F0077CA682BC718074F928040D1A36F585AC187A74" + "1F51C843C5ED88BC5FB8B86ED96C42BCF84EDF833489D7D3AC407C6D0740CC94BA1D5B885EB430CE8C601" + "7F8660A6C72F4378BF133AA663DBA36CAB967AAC0F7738478110ECEABAE3E914CB7A796C5394F7DF15094" + "0BEA43264765B34851ADE4E5F1F213C25DCF66D35BE92611555D8C05ACFDF1AC5CA82B7D7F0D9BE49596F" + "8B7F3269D9887F40B4BAB5C3D2BA7049B6D2119C3D0D01501836203412869E0"); + CHECK(pad->toHex() == hard_coded_pad_string); + + string hard_coded_data_string( + "F8E994D157A065A1DB2DA5E38645C283F7CCB339E13F0DE29B83A4EFA" + "2F4366C626FC8E318AF81DCB2E6083A598F8916A5FEEC3C1A1B8EBEB4081F3CB92FA86E000B4994B77E" + "E173072D796D21EE771F4D8F50E7DC50A7945E35059F893DD0A67C53DF5A3439A89E990C5B7568912CD" + "2655B39E943511E1B0DF8A8E1FEF4EAC3923A5B5DDF1A658335E97AA6EB12E4EEE1394D91548F3E8446" + "E9BBF4207D873F54298B446A7D689FF60A6F60B3FC6B8319EC17FA424F0461949CD49B764C6360AC0D4" + "92696E43EE83A6A7CE7AEA4DDBA206F365AA81E918F63709DE796F0338CCD311360D97CDC821506D3ED" + "B434922264966B8AF7E304A403E18384DDCF53AEF1FFC19A66FBCD9C2D04EFC8F2D456BE52DB9C460E3" + "CA10AC4ABFE0B726E19A715546F1CD9CA89C57ED52DA9D78C30BEFE5FE99A8BDEA33B7C06EDFD4E92D5" + "14661CD55B99B54E5C468118E16F4827F78FB381845B093F202111E3B84CFCF8DAEE7948BA57698475F" + "3EBC3729559835BF63AAB0F5659019965A2F0CF55E953B1CD37BCBED8EA0D5F161D461E03031BA7D0B0" + "42B978F7F6776DDFBCAA7145DE30BA24C29BDFA05C7CCF54D7DD58E75143A16F8619053FCF4DE7BDCCA" + "031F0873A65ACCC56FE78F32B8FC192D2106CF1A1E5339A5C5657E6703D7F30F908CEEF05A84C67C426" + "B187CBC1599FB334307146EAECB16774C5CB7630F4CB093E840086"); + CHECK(bytes_to_hex(data) == hard_coded_data_string); + + string hard_coded_mac_string("BBCDE57B7E92BB8607696E09FE629A2B9665D809649B751333023983C0" + "01C191"); + CHECK(bytes_to_hex(mac) == hard_coded_mac_string); + + unique_ptr newHEG = + make_unique(move(pad), HEGResult->getData(), HEGResult->getMac()); + + vector new_plaintext = newHEG->decrypt(*secret, *cryptoExtendedBaseHash, true); + + CHECK(plaintext == new_plaintext); +} diff --git a/test/electionguard/test_encrypt.cpp b/test/electionguard/test_encrypt.cpp index ff32c284..b0052097 100644 --- a/test/electionguard/test_encrypt.cpp +++ b/test/electionguard/test_encrypt.cpp @@ -14,6 +14,18 @@ using namespace electionguard; using namespace electionguard::tools::generators; using namespace std; +#ifndef TEST_BALLOT_WITH_WRITEIN_SELECTED +# define TEST_BALLOT_WITH_WRITEIN_SELECTED \ + "0.95.0/sample/full/election_private_data/plaintext_ballots/" \ + "plaintext_ballot_03a29d15-667c-4ac8-afd7-549f19b8e4eb.json" +#endif + +#ifndef TEST_BALLOT_WITH_WRITEIN_NOT_SELECTED +# define TEST_BALLOT_WITH_WRITEIN_NOT_SELECTED \ + "0.95.0/sample/full/election_private_data/plaintext_ballots/" \ + "plaintext_ballot_ballot-06e7aa04-73fc-11ec-8051-acde48001122.json" +#endif + TEST_CASE("Encrypt simple selection succeeds") { // Arrange @@ -152,10 +164,11 @@ TEST_CASE("Encrypt PlaintextBallot undervote succeeds") *context->getCryptoExtendedBaseHash()) == true); } -TEST_CASE("Encrypt PlaintextBallot overvote fails") +TEST_CASE("Encrypt PlaintextBallot overvote") { // Arrange - auto keypair = ElGamalKeyPair::fromSecret(TWO_MOD_Q(), false); + const auto &secret = TWO_MOD_Q(); + auto keypair = ElGamalKeyPair::fromSecret(secret, false); auto manifest = ManifestGenerator::getJeffersonCountyManifest_Minimal(); auto internal = make_unique(*manifest); auto context = ElectionGenerator::getFakeContext(*internal, *keypair->getPublicKey()); @@ -165,10 +178,40 @@ TEST_CASE("Encrypt PlaintextBallot overvote fails") // Act auto plaintext = BallotGenerator::getFakeBallot(*internal, 2UL); - // Log::debug(plaintext->toJson()); + //Log::debug(plaintext->toJson()); + + auto ciphertext = mediator->encrypt(*plaintext); // Assert - CHECK_THROWS_AS(mediator->encrypt(*plaintext), std::exception); + CHECK(ciphertext->isValidEncryption(*context->getManifestHash(), *keypair->getPublicKey(), + *context->getCryptoExtendedBaseHash()) == true); + + // check to make sure we have a hashed elgamal ciphertext + unique_ptr heg = nullptr; + auto contests = ciphertext->getContests(); + for (auto contest : contests) { + unique_ptr new_contest = + make_unique(contest); + auto id = new_contest->getObjectId(); + if (id == string("justice-supreme-court")) { + heg = new_contest->getHashedElGamalCiphertext(); + } + } + + CHECK(heg != nullptr); + CHECK(heg->getData().size() == (size_t)(BYTES_512 + sizeof(uint16_t))); + + unique_ptr new_pad = make_unique(*heg->getPad()); + unique_ptr newHEG = + make_unique(move(new_pad), heg->getData(), heg->getMac()); + + vector new_plaintext = + newHEG->decrypt(secret, *context->getCryptoExtendedBaseHash(), true); + string new_plaintext_string((char *)&new_plaintext.front(), new_plaintext.size()); + + CHECK(new_plaintext_string == + string("{\"error\":\"overvote\",\"error_data\":[\"benjamin-franklin-selection\"" + ",\"john-adams-selection\"]}")); } TEST_CASE("Encrypt simple PlaintextBallot with EncryptionMediator succeeds") @@ -207,6 +250,68 @@ TEST_CASE("Encrypt simple PlaintextBallot with EncryptionMediator succeeds") CHECK(fromBson->getNonce()->toHex() == ZERO_MOD_Q().toHex()); } +TEST_CASE("Encrypt full PlaintextBallot with WriteIn and Overvote with EncryptionMediator succeeds") +{ + const auto &secret = TWO_MOD_Q(); + auto keypair = ElGamalKeyPair::fromSecret(secret, false); + auto manifest = ManifestGenerator::getManifestFromFile(TEST_SPEC_VERSION, TEST_USE_FULL_SAMPLE); + auto internal = make_unique(*manifest); + auto context = ElectionGenerator::getFakeContext(*internal, *keypair->getPublicKey()); + auto device = make_unique(12345UL, 23456UL, 34567UL, "Location"); + + auto mediator = make_unique(*internal, *context, *device); + + // Act + string plaintextBallot_json = string("{\"object_id\": \"03a29d15-667c-4ac8-afd7-549f19b8e4eb\"," + "\"style_id\": \"jefferson-county-ballot-style\", \"contests\": [ {\"object_id\":" + "\"justice-supreme-court\", \"sequence_order\": 0, \"ballot_selections\": [{" + "\"object_id\": \"john-adams-selection\", \"sequence_order\": 0, \"vote\": 1," + "\"is_placeholder_selection\": false, \"extended_data\": null}, {\"object_id\"" + ": \"benjamin-franklin-selection\", \"sequence_order\": 1, \"vote\": 1," + "\"is_placeholder_selection\": false, \"extended_data\": null}, {\"object_id\":" + " \"write-in-selection\", \"sequence_order\": 3, \"vote\": 1, \"is_placeholder_selection\"" + ": false, \"write_in\": \"Susan B. Anthony\"}], \"extended_data\": null}]}"); + // TODO - Once the relevant plaintext ballot file is in the environment then + // uncomment the below and stop using the hard coded string. + //auto plaintextBallot = + // BallotGenerator::getSimpleBallotFromFile(TEST_BALLOT_WITH_WRITEIN_SELECTED); + auto plaintextBallot = PlaintextBallot::fromJson(plaintextBallot_json); + auto ciphertext = mediator->encrypt(*plaintextBallot); + + // Assert + CHECK(ciphertext->isValidEncryption(*context->getManifestHash(), *keypair->getPublicKey(), + *context->getCryptoExtendedBaseHash()) == true); + + // check to make sure we have a hashed elgamal ciphertext + unique_ptr heg = nullptr; + auto contests = ciphertext->getContests(); + for (auto contest : contests) { + unique_ptr new_contest = + make_unique(contest); + auto id = new_contest->getObjectId(); + if (id == string("justice-supreme-court")) { + heg = new_contest->getHashedElGamalCiphertext(); + } + } + + CHECK(heg != nullptr); + CHECK(heg->getData().size() == (size_t)(BYTES_512 + sizeof(uint16_t))); + + unique_ptr new_pad = make_unique(*heg->getPad()); + unique_ptr newHEG = + make_unique(move(new_pad), heg->getData(), heg->getMac()); + + vector new_plaintext = + newHEG->decrypt(secret, *context->getCryptoExtendedBaseHash(), true); + string new_plaintext_string((char *)&new_plaintext.front(), new_plaintext.size()); + Log::debug(new_plaintext_string); + + CHECK(new_plaintext_string == + string("{\"error\":\"overvote\",\"error_data\":[\"john-adams-selection\"," + "\"benjamin-franklin-selection\",\"write-in-selection\"],\"write_ins\"" + ":{\"write-in-selection\":\"Susan B. Anthony\"}}")); +} + TEST_CASE("Encrypt simple CompactPlaintextBallot with EncryptionMediator succeeds") { // Arrange