def test_log_methods(self): # Arrange message = "test log message" # Act log_debug(message) log_error(message) log_info(message) log_warning(message)
def _get_hash_required(self, filename: str) -> Optional[FileInfo]: """ Gets the hash for the requested filename (fully composed path, such as we might get from `utils.compose_filename`). If absent, logs an error and returns None. """ hash = self.hashes[filename] if hash is None: log_error(f"No hash available for file: {filename}") return None return hash
def test_log_methods(self): # Arrange message = "test log message" # Act log_debug(message) log_error(message) log_info(message) log_warning(message) # Assert self.assertIsNotNone(message)
def get_encrypted_ballot( self, ballot_id: str) -> Optional[CiphertextAcceptedBallot]: """ Given the ballot identifier name, returns the corresponding `CiphertextAcceptedBallot` if it exists, otherwise `None`. If the ballots are on disk, this will cause only that one ballot to be loaded and cached. """ if ballot_id not in self.encrypted_ballot_memos: log_error(f"ballot_id {ballot_id} missing") return None return self.encrypted_ballot_memos[ballot_id].contents
def is_valid_proof(self, public_key: ElementModP, hash_header: Optional[ElementModQ] = None) -> bool: """Returns true if the plaintext, ciphertext, and proof are valid and consistent, false if not.""" valid_proof: bool = self.proof.is_valid(self.decrypted_tally, self.encrypted_tally, public_key, hash_header) if not valid_proof: # pragma: no cover log_error( f"Chaum-Pedersen proof validation failed: valid_proof: {valid_proof}, selection_info: {str(self)}" ) return valid_proof
def all_hashes_unique(self) -> bool: """ Checks that every hash value is unique. If a file hash repeated, then there's a chance that something went really wrong, like an identical ballot being repeated. """ expected_num_hashes = len(self.hashes.keys()) actual_num_hashes = len({v.hash for v in self.hashes.values()}) if expected_num_hashes != actual_num_hashes: log_error( f"Expected to find {expected_num_hashes} unique ballot hashes, only found {actual_num_hashes}." ) return False else: return True
def tallies_match(provided_tally: TALLY_TYPE, recomputed_tally: TALLY_TYPE) -> bool: """ Helper function for comparing tallies. Logs useful errors if something doesn't match. """ tally_success = True for selection in recomputed_tally.keys(): recomputed_ciphertext: ElGamalCiphertext = recomputed_tally[selection] provided_ciphertext: ElGamalCiphertext = provided_tally[selection] if recomputed_ciphertext != provided_ciphertext: log_error( f"Mismatching ciphertext tallies found for selection ({selection}). Recomputed sum: ({recomputed_ciphertext}), provided sum: ({provided_ciphertext})" ) tally_success = False return tally_success
def merge_from(self, other: "Manifest") -> None: """ Given a second manifest, reads all its contents and merges them into this manifest (i.e., "self" mutates, but "other" doesn't change). This would be useful when multiple remote workers are writing files, and you want to merge the results into a single manifest object. Note: both manifests must share the same root directory, and any overlapping files in the manifests are considered an error unless they're identical. """ assert (other.root_dir == self.root_dir ), "manifests must share the same root directory" self_keys = set(self.hashes.keys()) other_keys = set(other.hashes.keys()) shared_keys = self_keys.intersection(other_keys) for k in shared_keys: if self.hashes[k] != other.hashes[k]: msg = f"cannot merge manifests: disagreeing contents for {k}: {self.hashes[k]} vs. {other.hashes[k]}" log_error(msg) raise RuntimeError(msg) for k in other_keys: self.hashes[k] = other.hashes[k] self.bytes_written += other.bytes_written
def verify_proven_ballot_proofs( extended_base_hash: ElementModQ, public_key: ElementModP, ciphertext_ballot: CiphertextAcceptedBallot, pballot: ProvenPlaintextBallot, ) -> bool: """ Returns True if the proofs are consistent with the ciphertext. """ # We're going to check the proof for validity here, even though it takes real time to compute, # because we don't expect to be decrypting very many ballots at once, and it's really valuable # to do the extra checking for correctness. selections: Dict[str, PlaintextBallotSelection] = plaintext_ballot_to_dict( pballot.ballot) ciphertexts: Dict[str, ElGamalCiphertext] = ciphertext_ballot_to_dict( ciphertext_ballot) proofs: Dict[str, ChaumPedersenDecryptionProof] = pballot.proofs for id in selections.keys(): if id not in proofs: # pragma: no cover log_error(f"No proof found for selection id {id}") return False if id not in ciphertexts: # pragma: no cover log_error(f"No ciphertext found for selection id {id}") return False if not proofs[id].is_valid(selections[id].to_int(), ciphertexts[id], public_key, extended_base_hash): log_error(f"Invalid proof for selection id {id}") return False return True
def validate_contents(self, manifest_file_name: str, file_contents: str) -> bool: """ Checks the manifest for the given file name. Returns True if the name is included in the manifest *and* the file_contents match the manifest. If anything is not properly validated, a suitable error will be written to the ElectionGuard log. """ if manifest_file_name not in self.hashes: log_error(f"File {manifest_file_name} was not in the manifest") return False file_info: FileInfo = self.hashes[manifest_file_name] file_len = len(file_contents.encode("utf-8")) if file_len != file_info.num_bytes: log_error( f"File {manifest_file_name} did not have the expected length (expected: {file_info.num_bytes} bytes, actual: {file_len} bytes)" ) return False data_hash = sha256_hash(file_contents) if data_hash != file_info.hash: log_error( f"File {manifest_file_name} did not have the expected hash (expected: {file_info.hash}, actual: {data_hash})" ) return False return True
def decode_json_file_contents( json_str: str, class_handle: Type[Serializable[T]]) -> Optional[T]: """ Wrapper around JSON deserialization. Given a string of JSON text and a handle to an ElectionGuard `Serializable` class, tries to decode the JSON into an instance of that class. If anything fails, the result will be `None`. No exceptions will be raised outside of this method, but all such failures will be logged to the ElectionGuard log. :param json_str: any JSON string :param class_handle: the class, itself, that we're trying to deserialize to :returns: the contents of the file, or `None` if there was an error """ try: result = class_handle.from_json(json_str) except DecodeError as err: # pragma: no cover log_error(f"Failed to decode an instance of {class_handle}: {err}") return None except UnfulfilledArgumentError as err: # pragma: no cover log_error(f"Decoding failure for {class_handle}: {err}") return None return result
def is_valid(self) -> bool: if self.constants != ElectionConstants(): log_error("Mismatching election constants!") return False # super-cheesy unit test to make sure keypair works m1 = randbelow(5) m2 = randbelow(5) nonce1 = rand_q() nonce2 = rand_q() c1 = get_optional(elgamal_encrypt(m1, nonce1, self.keypair.public_key)) c2 = get_optional(elgamal_encrypt(m2, nonce2, self.keypair.public_key)) csum = elgamal_add(c1, c2) psum = csum.decrypt(self.keypair.secret_key) if psum != m1 + m2: log_error("The given keypair didn't work for basic ElGamal math") return False return True
def load_file_helper( root_dir: str, file_name: Union[str, PurePath], subdirectories: List[str] = None, ) -> Optional[str]: """ Reads the requested file, by name, returning its contents as a Python string. Note: if the file_name is a path-like object, the root_dir and subdirectories are ignored and the file is directly loaded. :param root_dir: top-level directory where we'll be reading files :param file_name: name of the file, including any suffix :param subdirectories: path elements to be introduced between `root_dir` and the file; empty-list means no subdirectory :returns: the contents of the file, or `None` if there was an error """ if isinstance(file_name, PurePath): full_name = file_name else: full_name = compose_filename(root_dir, file_name, subdirectories) try: s = stat(full_name) if s.st_size == 0: # pragma: no cover log_error(f"The file ({full_name}) is empty") return None with open(full_name, "r") as f: data = f.read() return data except FileNotFoundError as e: log_error(f"Error reading file ({full_name}): {e}") return None except OSError as e: log_error(f"Error reading file ({full_name}): {e}") return None