def compare_results(self) -> None: """ Compare the results to ensure the decryption was done correctly """ print(f""" {'-'*40} # Election Results: """) # Create a representation of each contest's tally selection_ids = [ selection.object_id for contest in self.metadata.contests for selection in contest.ballot_selections ] expected_plaintext_tally: Dict[str, int] = { key: 0 for key in selection_ids } # Tally the expected values from the loaded ballots for ballot in self.plaintext_ballots: if (get_optional(self.ballot_store.get( ballot.object_id)).state == BallotBoxState.CAST): for contest in ballot.contests: for selection in contest.ballot_selections: expected_plaintext_tally[ selection.object_id] += selection.to_int() # Compare the expected tally to the decrypted tally for tally_contest in self.plaintext_tally.contests.values(): print(f" Contest: {tally_contest.object_id}") for tally_selection in tally_contest.selections.values(): expected = expected_plaintext_tally[tally_selection.object_id] self._assert_message( f" - Selection: {tally_selection.object_id}", f"expected: {expected}, actual: {tally_selection.plaintext}", expected == tally_selection.plaintext, ) print(f"\n{'-'*40}\n") # Compare the expected values for each spoiled ballot for ballot_id, accepted_ballot in self.ciphertext_tally.spoiled_ballots.items( ): if accepted_ballot.state == BallotBoxState.SPOILED: for plaintext_ballot in self.plaintext_ballots: if ballot_id == plaintext_ballot.object_id: print(f"\nSpoiled Ballot: {ballot_id}") for contest in plaintext_ballot.contests: print(f"\n Contest: {contest.object_id}") for selection in contest.ballot_selections: expected = selection.to_int() decrypted_selection = self.plaintext_tally.spoiled_ballots[ ballot_id][contest.object_id].selections[ selection.object_id] self._assert_message( f" - Selection: {selection.object_id}", f"expected: {expected}, actual: {decrypted_selection.plaintext}", expected == decrypted_selection.plaintext, )
def test_flatmap(self): good: Optional[int] = 3 bad: Optional[int] = None self.assertEqual(5, get_optional(flatmap_optional(good, lambda x: x + 2))) self.assertIsNone(flatmap_optional(bad, lambda x: x + 2))
def encrypt_ballots(request: EncryptBallotsRequest = Body(...)) -> Any: """ Encrypt one or more ballots """ ballots = [ PlaintextBallot.from_json_object(ballot) for ballot in request.ballots ] description = InternalElectionDescription( ElectionDescription.from_json_object(request.description)) context = CiphertextElectionContext.from_json_object(request.context) seed_hash = read_json_object(request.seed_hash, ElementModQ) nonce: Optional[ElementModQ] = (read_json_object( request.nonce, ElementModQ) if request.nonce else None) encrypted_ballots = [] current_hash = seed_hash for ballot in ballots: encrypted_ballot = encrypt_ballot(ballot, description, context, current_hash, nonce) if not encrypted_ballot: raise HTTPException(status_code=500, detail="Ballot failed to encrypt") encrypted_ballots.append(encrypted_ballot) current_hash = get_optional(encrypted_ballot.tracking_hash) response = EncryptBallotsResponse( encrypted_ballots=[ ballot.to_json_object() for ballot in encrypted_ballots ], next_seed_hash=write_json_object(current_hash), ) return response
def test_djcp_proof_broken(self, keypair: ElGamalKeyPair, nonce: ElementModQ, seed: ElementModQ): # verify two different ways to generate an invalid C-P proof. message = get_optional(elgamal_encrypt(0, nonce, keypair.public_key)) message_bad = get_optional( elgamal_encrypt(2, nonce, keypair.public_key)) proof = make_disjunctive_chaum_pedersen_zero(message, nonce, keypair.public_key, ONE_MOD_Q, seed) proof_bad = make_disjunctive_chaum_pedersen_zero( message_bad, nonce, keypair.public_key, ONE_MOD_Q, seed) self.assertFalse( proof_bad.is_valid(message_bad, keypair.public_key, ONE_MOD_Q)) self.assertFalse( proof.is_valid(message_bad, keypair.public_key, ONE_MOD_Q))
def test_elgamal_encryption_decryption_with_known_nonce_inverses( self, message: int, nonce: ElementModQ, keypair: ElGamalKeyPair): ciphertext = get_optional( elgamal_encrypt(message, nonce, keypair.public_key)) plaintext = ciphertext.decrypt_known_nonce(keypair.public_key, nonce) self.assertEqual(message, plaintext)
def test_ccp_proof( self, keypair: ElGamalKeyPair, nonce: ElementModQ, seed: ElementModQ, constant: int, bad_constant: int, ): if constant == bad_constant: bad_constant = constant + 1 message = get_optional(elgamal_encrypt(constant, nonce, keypair.public_key)) decryption = message.partial_decrypt(keypair.secret_key) proof = make_chaum_pedersen( message, keypair.secret_key, decryption, seed, ONE_MOD_Q ) bad_proof = make_chaum_pedersen( message, keypair.secret_key, int_to_p(bad_constant), seed, ONE_MOD_Q ) self.assertTrue( proof.is_valid(message, keypair.public_key, decryption, ONE_MOD_Q) ) self.assertFalse( bad_proof.is_valid(message, keypair.public_key, decryption, ONE_MOD_Q) )
def step_2_encrypt_votes(self) -> None: """ Using the `CiphertextElectionContext` encrypt ballots for the election """ # Configure the Encryption Device self.device = ElectionFactory.get_encryption_device() self.encrypter = EncryptionMediator(self.internal_manifest, self.context, self.device) self._assert_message( EncryptionDevice.__qualname__, f"Ready to encrypt at location: {self.device.location}", ) # Load some Ballots self.plaintext_ballots = BallotFactory().get_simple_ballots_from_file() self._assert_message( PlaintextBallot.__qualname__, f"Loaded ballots: {len(self.plaintext_ballots)}", len(self.plaintext_ballots) > 0, ) # Encrypt the Ballots for plaintext_ballot in self.plaintext_ballots: encrypted_ballot = self.encrypter.encrypt(plaintext_ballot) self._assert_message( EncryptionMediator.encrypt.__qualname__, f"Ballot Id: {plaintext_ballot.object_id}", encrypted_ballot is not None, ) self.ciphertext_ballots.append(get_optional(encrypted_ballot))
def step_4_decrypt_tally(self) -> None: """ Homomorphically combine the selections made on all of the cast ballots and use the Available Guardians to decrypt the combined tally. In this way, no individual voter's cast ballot is ever decrypted drectly. """ # Generate a Homomorphically Accumulated Tally of the ballots self.ciphertext_tally = get_optional( tally_ballots(self.ballot_store, self.metadata, self.context) ) self._assert_message( tally_ballots.__qualname__, f""" - cast: {self.ciphertext_tally.count()} - spoiled: {len(self.ciphertext_tally.spoiled_ballots)} Total: {len(self.ciphertext_tally)} """, self.ciphertext_tally is not None, ) # Configure the Decryption self.decrypter = DecryptionMediator( self.metadata, self.context, self.ciphertext_tally ) # Announce each guardian as present for guardian in self.guardians: decryption_share = self.decrypter.announce(guardian) self._assert_message( DecryptionMediator.announce.__qualname__, f"Guardian Present: {guardian.object_id}", decryption_share is not None, ) # Get the Plain Text Tally self.plaintext_tally = get_optional(self.decrypter.get_plaintext_tally()) self._assert_message( DecryptionMediator.get_plaintext_tally.__qualname__, "Tally Decrypted", self.plaintext_tally is not None, ) # Now, compare the results self.compare_results()
def test_djcp_proof_one(self, keypair: ElGamalKeyPair, nonce: ElementModQ, seed: ElementModQ): message = get_optional(elgamal_encrypt(1, nonce, keypair.public_key)) proof = make_disjunctive_chaum_pedersen_one(message, nonce, keypair.public_key, seed) proof_bad = make_disjunctive_chaum_pedersen_zero( message, nonce, keypair.public_key, seed) self.assertTrue(proof.is_valid(message, keypair.public_key)) self.assertFalse(proof_bad.is_valid(message, keypair.public_key))
def get_fake_ciphertext_election( self, description: ElectionDescription, elgamal_public_key: ElementModP ) -> Tuple[InternalElectionDescription, CiphertextElectionContext]: builder = ElectionBuilder(number_of_guardians=1, quorum=1, description=description) builder.set_public_key(elgamal_public_key) metadata, election = get_optional(builder.build()) return metadata, election
def get_hamilton_election_with_encryption_context( self, ) -> Tuple[AllPublicElectionData, AllPrivateElectionData]: guardians: List[Guardian] = [] coefficient_validation_sets: List[CoefficientValidationSet] = [] # Configure the election builder description = self.get_hamilton_election_from_file() builder = ElectionBuilder(NUMBER_OF_GUARDIANS, QUORUM, description) # Setup Guardians for i in range(NUMBER_OF_GUARDIANS): guardians.append( Guardian( "hamilton-county-canvass-board-member-" + str(i), i, NUMBER_OF_GUARDIANS, QUORUM, )) # Run the key ceremony mediator = KeyCeremonyMediator(guardians[0].ceremony_details) for guardian in guardians: mediator.announce(guardian) mediator.orchestrate() mediator.verify() # Joint Key joint_key = mediator.publish_joint_key() # Save Validation Keys for guardian in guardians: coefficient_validation_sets.append( guardian.share_coefficient_validation_set()) builder.set_public_key(get_optional(joint_key).joint_public_key) builder.set_commitment_hash(get_optional(joint_key).commitment_hash) metadata, context = get_optional(builder.build()) constants = ElectionConstants() return ( AllPublicElectionData(description, metadata, context, constants, coefficient_validation_sets), AllPrivateElectionData(guardians), )
def _equivalent_decrypt_helper( ied: InternalElectionDescription, base_hash: ElementModQ, public_key: ElementModP, secret_key: ElementModQ, cballot: CiphertextAcceptedBallot, ) -> PlaintextBallot: # pragma: no cover return get_optional( decrypt_ballot_with_secret(cballot, ied, base_hash, public_key, secret_key, True, True))
def get_fake_ciphertext_election( manifest: Manifest, elgamal_public_key: ElementModP ) -> Tuple[InternalManifest, CiphertextElectionContext]: builder = ElectionBuilder(number_of_guardians=1, quorum=1, manifest=manifest) builder.set_public_key(elgamal_public_key) builder.set_commitment_hash(TWO_MOD_Q) internal_manifest, context = get_optional(builder.build()) return internal_manifest, context
def test_ccp_proofs_simple_encryption_of_one(self): keypair = elgamal_keypair_from_secret(TWO_MOD_Q) nonce = ONE_MOD_Q seed = TWO_MOD_Q message = get_optional(elgamal_encrypt(1, nonce, keypair.public_key)) proof = make_constant_chaum_pedersen(message, 1, nonce, keypair.public_key, seed) bad_proof = make_constant_chaum_pedersen(message, 0, nonce, keypair.public_key, seed) self.assertTrue(proof.is_valid(message, keypair.public_key)) self.assertFalse(bad_proof.is_valid(message, keypair.public_key))
def process_message( self, message_type: Literal["end_key_ceremony"], message: Content, context: TrusteeContext, ) -> Tuple[List[Content], ElectionStep]: key_ceremony_results = deserialize(message["content"], KeyCeremonyResults) context.election_builder.set_public_key( get_optional( key_ceremony_results.election_joint_key.joint_public_key)) context.election_builder.set_commitment_hash( get_optional( key_ceremony_results.election_joint_key.commitment_hash)) context.election_metadata, context.election_context = get_optional( context.election_builder.build()) # TODO: coefficient validation keys??? # TODO: check joint key, without using private variables if possible # serialize(elgamal_combine_public_keys(context.guardian._guardian_election_public_keys.values())) return [], ProcessTallyCast()
def test_djcp_proofs_simple(self): # doesn't get any simpler than this keypair = elgamal_keypair_from_secret(TWO_MOD_Q) nonce = ONE_MOD_Q seed = TWO_MOD_Q message0 = get_optional(elgamal_encrypt(0, nonce, keypair.public_key)) proof0 = make_disjunctive_chaum_pedersen_zero(message0, nonce, keypair.public_key, seed) proof0bad = make_disjunctive_chaum_pedersen_one( message0, nonce, keypair.public_key, seed) self.assertTrue(proof0.is_valid(message0, keypair.public_key)) self.assertFalse(proof0bad.is_valid(message0, keypair.public_key)) message1 = get_optional(elgamal_encrypt(1, nonce, keypair.public_key)) proof1 = make_disjunctive_chaum_pedersen_one(message1, nonce, keypair.public_key, seed) proof1bad = make_disjunctive_chaum_pedersen_zero( message1, nonce, keypair.public_key, seed) self.assertTrue(proof1.is_valid(message1, keypair.public_key)) self.assertFalse(proof1bad.is_valid(message1, keypair.public_key))
def test_ccp_proof( self, keypair: ElGamalKeyPair, nonce: ElementModQ, seed: ElementModQ, constant: int, bad_constant: int, ): # assume() slows down the test-case generation # so assume(constant != bad_constant) if constant == bad_constant: bad_constant = constant + 1 message = get_optional( elgamal_encrypt(constant, nonce, keypair.public_key)) message_bad = get_optional( elgamal_encrypt(bad_constant, nonce, keypair.public_key)) proof = make_constant_chaum_pedersen(message, constant, nonce, keypair.public_key, seed, ONE_MOD_Q) self.assertTrue(proof.is_valid(message, keypair.public_key, ONE_MOD_Q)) proof_bad1 = make_constant_chaum_pedersen(message_bad, constant, nonce, keypair.public_key, seed, ONE_MOD_Q) self.assertFalse( proof_bad1.is_valid(message_bad, keypair.public_key, ONE_MOD_Q)) proof_bad2 = make_constant_chaum_pedersen(message, bad_constant, nonce, keypair.public_key, seed, ONE_MOD_Q) self.assertFalse( proof_bad2.is_valid(message, keypair.public_key, ONE_MOD_Q)) proof_bad3 = ConstantChaumPedersenProof(proof.pad, proof.data, proof.challenge, proof.response, -1) self.assertFalse( proof_bad3.is_valid(message, keypair.public_key, ONE_MOD_Q))
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 process_message( self, message_type: Literal["key_ceremony.trustee_verification"], message: Content, context: BulletinBoardContext, ) -> Tuple[List[Content], Optional[ElectionStep]]: content = deserialize(message["content"], TrusteeVerification) self.verifications_received.add(content.guardian_id) # TO-DO: check verifications? if len(self.verifications_received) < context.number_of_guardians: return [], None sorted_trustee_elections_keys = sorted( context.trustee_election_keys.items()) election_joint_key = combine_election_public_keys( trustee_election_key.public_key_set.election for guardian_id, trustee_election_key in sorted_trustee_elections_keys) context.election_builder.set_public_key( get_optional(election_joint_key.joint_public_key)) context.election_builder.set_commitment_hash( get_optional(election_joint_key.commitment_hash)) context.election_metadata, context.election_context = get_optional( context.election_builder.build()) return [{ "message_type": "end_key_ceremony", "content": serialize( KeyCeremonyResults( election_joint_key=election_joint_key, constants=ElectionConstants(), context=context.election_context, )), }], ProcessStartVote()
def get_hamilton_manifest_with_encryption_context( self, ) -> Tuple[AllPublicElectionData, AllPrivateElectionData]: guardians: List[Guardian] = [] guardian_records: List[GuardianRecord] = [] # Configure the election builder manifest = self.get_hamilton_manifest_from_file() builder = ElectionBuilder(NUMBER_OF_GUARDIANS, QUORUM, manifest) # Run the Key Ceremony ceremony_details = CeremonyDetails(NUMBER_OF_GUARDIANS, QUORUM) guardians = KeyCeremonyHelper.create_guardians(ceremony_details) mediator = KeyCeremonyMediator("key-ceremony-mediator", ceremony_details) KeyCeremonyHelper.perform_full_ceremony(guardians, mediator) # Final: Joint Key joint_key = mediator.publish_joint_key() # Publish Guardian Records guardian_records = [guardian.publish() for guardian in guardians] builder.set_public_key(get_optional(joint_key).joint_public_key) builder.set_commitment_hash(get_optional(joint_key).commitment_hash) internal_manifest, context = get_optional(builder.build()) constants = ElectionConstants() return ( AllPublicElectionData( manifest, internal_manifest, context, constants, guardian_records, ), AllPrivateElectionData(guardians), )
def test_djcp_proof_invalid_inputs(self): # this is here to push up our coverage keypair = elgamal_keypair_from_secret(TWO_MOD_Q) nonce = ONE_MOD_Q seed = TWO_MOD_Q message0 = get_optional(elgamal_encrypt(0, nonce, keypair.public_key)) self.assertRaises( Exception, make_disjunctive_chaum_pedersen, message0, nonce, keypair.public_key, seed, 3, )
def test_cp_proofs_simple(self): keypair = elgamal_keypair_from_secret(TWO_MOD_Q) nonce = ONE_MOD_Q seed = TWO_MOD_Q message = get_optional(elgamal_encrypt(0, nonce, keypair.public_key)) decryption = message.partial_decrypt(keypair.secret_key) proof = make_chaum_pedersen(message, keypair.secret_key, decryption, seed, ONE_MOD_Q) bad_proof = make_chaum_pedersen(message, keypair.secret_key, TWO_MOD_Q, seed, ONE_MOD_Q) self.assertTrue( proof.is_valid(message, keypair.public_key, decryption, ONE_MOD_Q)) self.assertFalse( bad_proof.is_valid(message, keypair.public_key, decryption, ONE_MOD_Q))
def compensate_decrypt( guardian_auxiliary_keys: AuxiliaryKeyPair, missing_guardian_backup: ElectionPartialKeyBackup, ciphertext: ElGamalCiphertext, extended_base_hash: ElementModQ, nonce_seed: ElementModQ = None, decrypt: AuxiliaryDecrypt = rsa_decrypt, ) -> Optional[Tuple[ElementModP, ChaumPedersenProof]]: """ Compute a compensated partial decryption of an elgamal encryption on behalf of the missing guardian :param guardian_auxiliary_keys: Auxiliary key pair for guardian decrypting :param missing_guardian_backup: Missing guardians backup :param ciphertext: the `ElGamalCiphertext` that will be partially decrypted :param extended_base_hash: the extended base hash of the election that was used to generate t he ElGamal Ciphertext :param nonce_seed: an optional value used to generate the `ChaumPedersenProof` if no value is provided, a random number will be used. :param decrypt: an `AuxiliaryDecrypt` function to decrypt the missing guardina private key backup :return: a `Tuple[ElementModP, ChaumPedersenProof]` of the decryption and its proof """ if nonce_seed is None: nonce_seed = rand_q() decrypted_value = decrypt(missing_guardian_backup.encrypted_value, guardian_auxiliary_keys.secret_key) if decrypted_value is None: log_warning( (f"compensate decrypt guardian {guardian_auxiliary_keys.owner_id}" f" failed decryption for {missing_guardian_backup.owner_id}")) return None partial_secret_key = get_optional(hex_to_q(decrypted_value)) # 𝑀_{𝑖,l} = 𝐴^P𝑖_{l} partial_decryption = ciphertext.partial_decrypt(partial_secret_key) # 𝑀_{𝑖,l} = 𝐴^𝑠𝑖 mod 𝑝 and 𝐾𝑖 = 𝑔^𝑠𝑖 mod 𝑝 proof = make_chaum_pedersen( ciphertext, partial_secret_key, partial_decryption, nonce_seed, extended_base_hash, ) return (partial_decryption, proof)
def chaum_pedersen_bench(bi: BenchInput) -> Tuple[float, float]: """ Given an input (instance of the BenchInput tuple), constructs and validates a disjunctive Chaum-Pedersen proof, returning the time (in seconds) to do each operation. """ (keypair, r, s) = bi ciphertext = get_optional(elgamal_encrypt(0, r, keypair.public_key)) start1 = timer() proof = make_disjunctive_chaum_pedersen_zero(ciphertext, r, keypair.public_key, s) end1 = timer() valid = proof.is_valid(ciphertext, keypair.public_key) end2 = timer() if not valid: raise Exception( "Wasn't expecting an invalid proof during a benchmark!") return end1 - start1, end2 - end1
def run_bench(filename: str, output_dir: Optional[str], use_progressbar: bool) -> None: start_time = timer() print(f"Benchmarking: {filename}") cvrs = read_dominion_csv(filename) if cvrs is None: print(f"Failed to read {filename}, terminating.") exit(1) rows, cols = cvrs.data.shape parse_time = timer() print( f" Parse time: {parse_time - start_time: .3f} sec, {rows / (parse_time - start_time):.3f} ballots/sec" ) assert rows > 0, "can't have zero ballots!" # doesn't matter what the key is, so long as it's consistent for both runs keypair = get_optional( elgamal_keypair_from_secret(int_to_q_unchecked(31337))) rtally_start = timer() rtally = ray_tally_everything( cvrs, secret_key=keypair.secret_key, verbose=True, root_dir=output_dir, use_progressbar=use_progressbar, ) rtally_end = timer() print(f"\nOVERALL PERFORMANCE") print(f" Ray time: {rtally_end - rtally_start : .3f} sec") print( f" Ray rate: {rows / (rtally_end - rtally_start): .3f} ballots/sec" ) if output_dir: print(f"\nSANITY CHECK") assert rtally.all_proofs_valid( verbose=True, recheck_ballots_and_tallies=False, use_progressbar=use_progressbar, ), "proof failure!"
def setupElectionBuilder(self): # Create an election builder instance, and configure it for a single public-private keypair. # in a real election, you would configure this for a group of guardians. See Key Ceremony for more information. with open(os.path.join(self.manifest_path, self.manifest_file), "r") as manifest: string_representation = manifest.read() election_description = ElectionDescription.from_json( string_representation) builder = ElectionBuilder( number_of_guardians=self. NUMBER_OF_GUARDIANS, # since we will generate a single public-private keypair, we set this to 1 quorum=self. QUORUM, # since we will generate a single public-private keypair, we set this to 1 description=election_description) builder.set_public_key(self.joint_public_key) self.metadata, self.context = get_optional(builder.build()) self.builder = builder
def test_reconstruct_decryption_share_for_ballot(self): # Arrange available_guardians = self.guardians[0:2] available_guardians_keys = [ guardian.share_election_public_key() for guardian in available_guardians ] missing_guardian = self.guardians[2] missing_guardian_key = missing_guardian.share_election_public_key() missing_guardian_backups = { backup.designated_id: backup for backup in missing_guardian.share_election_partial_key_backups() } ballot = self.ciphertext_ballots[self.fake_spoiled_ballot.object_id] # Act compensated_shares: Dict[GUARDIAN_ID, CompensatedDecryptionShare] = { available_guardian.id: get_optional( compute_compensated_decryption_share_for_ballot( available_guardian.share_election_public_key(), available_guardian._auxiliary_keys, missing_guardian_key, missing_guardian_backups[available_guardian.id], ballot, self.context, identity_auxiliary_decrypt, )) for available_guardian in available_guardians } lagrange_coefficients = compute_lagrange_coefficients_for_guardians( available_guardians_keys) share = reconstruct_decryption_share_for_ballot( missing_guardian_key, ballot, compensated_shares, lagrange_coefficients) # Assert self.assertEqual(self.QUORUM, len(compensated_shares)) self.assertEqual(self.QUORUM, len(lagrange_coefficients)) self.assertIsNotNone(share)
def _generate_encrypted_tally( self, metadata: InternalElectionDescription, context: CiphertextElectionContext, ballots: List[PlaintextBallot], ) -> CiphertextTally: # encrypt each ballot store = BallotStore() for ballot in ballots: encrypted_ballot = encrypt_ballot(ballot, metadata, context, int_to_q_unchecked(1)) self.assertIsNotNone(encrypted_ballot) # add to the ballot store store.set( encrypted_ballot.object_id, from_ciphertext_ballot(encrypted_ballot, BallotBoxState.CAST), ) tally = tally_ballots(store, metadata, context) self.assertIsNotNone(tally) return get_optional(tally)
def encrypt_ballot_helper( ied: InternalElectionDescription, cec: CiphertextElectionContext, seed_hash: ElementModQ, input_tuple: Tuple[PlaintextBallot, ElementModQ], ) -> CiphertextBallot: # pragma: no cover """ Given a ballot and the associated metadata, encrypt it. Note that this method is meant to be used with `functools.partial`, so we can create a function that only takes the final tuple argument while remembering all the rest. """ b, n = input_tuple # Coverage note: you'll see a directive on this method and on the other methods # used for the parallel mapping. For whatever reason, the Python coverage tool # can't figure out that they're running, so we'll exclude them. # Performance note: Nearly 2x performance boost by disabling proof verification # here. We do verify the tally proofs at the end, so doing all this extra work # here is in the "would be nice if cycles were free" category, but this is the # inner loop of the most performance-sensitive part of our code. return get_optional( encrypt_ballot(b, ied, cec, seed_hash, n, should_verify_proofs=False))
def setUp(self): self.key_ceremony = KeyCeremonyMediator(self.CEREMONY_DETAILS) self.guardians: List[Guardian] = [] # Setup Guardians for i in range(self.NUMBER_OF_GUARDIANS): sequence = i + 2 self.guardians.append( Guardian( "guardian_" + str(sequence), sequence, self.NUMBER_OF_GUARDIANS, self.QUORUM, )) # Attendance (Public Key Share) for guardian in self.guardians: self.key_ceremony.announce(guardian) self.key_ceremony.orchestrate(identity_auxiliary_encrypt) self.key_ceremony.verify(identity_auxiliary_decrypt) self.joint_public_key = self.key_ceremony.publish_joint_key() self.assertIsNotNone(self.joint_public_key) # setup the election self.election = election_factory.get_fake_election() builder = ElectionBuilder(self.NUMBER_OF_GUARDIANS, self.QUORUM, self.election) self.assertIsNone( builder.build()) # Can't build without the public key builder.set_public_key(self.joint_public_key) self.metadata, self.context = get_optional(builder.build()) self.encryption_device = EncryptionDevice("location") self.ballot_marking_device = EncryptionMediator( self.metadata, self.context, self.encryption_device) # get some fake ballots self.fake_cast_ballot = ballot_factory.get_fake_ballot( self.metadata, "some-unique-ballot-id-cast") self.more_fake_ballots = [] for i in range(10): self.more_fake_ballots.append( ballot_factory.get_fake_ballot( self.metadata, f"some-unique-ballot-id-cast{i}")) self.fake_spoiled_ballot = ballot_factory.get_fake_ballot( self.metadata, "some-unique-ballot-id-spoiled") self.assertTrue( self.fake_cast_ballot.is_valid( self.metadata.ballot_styles[0].object_id)) self.assertTrue( self.fake_spoiled_ballot.is_valid( self.metadata.ballot_styles[0].object_id)) self.expected_plaintext_tally = accumulate_plaintext_ballots( [self.fake_cast_ballot] + self.more_fake_ballots) # Fill in the expected values with any missing selections # that were not made on any ballots selection_ids = set([ selection.object_id for contest in self.metadata.contests for selection in contest.ballot_selections ]) missing_selection_ids = selection_ids.difference( set(self.expected_plaintext_tally)) for id in missing_selection_ids: self.expected_plaintext_tally[id] = 0 # Encrypt encrypted_fake_cast_ballot = self.ballot_marking_device.encrypt( self.fake_cast_ballot) encrypted_fake_spoiled_ballot = self.ballot_marking_device.encrypt( self.fake_spoiled_ballot) self.assertIsNotNone(encrypted_fake_cast_ballot) self.assertIsNotNone(encrypted_fake_spoiled_ballot) self.assertTrue( encrypted_fake_cast_ballot.is_valid_encryption( self.context.crypto_extended_base_hash, self.joint_public_key)) # encrypt some more fake ballots self.more_fake_encrypted_ballots = [] for fake_ballot in self.more_fake_ballots: self.more_fake_encrypted_ballots.append( self.ballot_marking_device.encrypt(fake_ballot)) # configure the ballot box ballot_store = BallotStore() ballot_box = BallotBox(self.metadata, self.context, ballot_store) ballot_box.cast(encrypted_fake_cast_ballot) ballot_box.spoil(encrypted_fake_spoiled_ballot) # Cast some more fake ballots for fake_ballot in self.more_fake_encrypted_ballots: ballot_box.cast(fake_ballot) # generate encrypted tally self.ciphertext_tally = tally_ballots(ballot_store, self.metadata, self.context)