def test_decrypt_selection_with_all_guardians_present(self): # Arrange available_guardians = self.guardians # find the first selection first_contest = list(self.ciphertext_tally.contests.values())[0] first_selection = list(first_contest.selections.values())[0] print(first_contest.object_id) print(first_selection.object_id) # precompute decryption shares for specific selection for the guardians shares: Dict[GUARDIAN_ID, Tuple[ELECTION_PUBLIC_KEY, DecryptionShare]] = { guardian.id: ( guardian.share_election_public_key().key, compute_decryption_share( guardian._election_keys, self.ciphertext_tally, self.context, ).contests[first_contest.object_id].selections[ first_selection.object_id], ) for guardian in available_guardians } # Act result = decrypt_selection_with_decryption_shares( first_selection, shares, self.context.crypto_extended_base_hash) # Assert self.assertIsNotNone(result) self.assertEqual( self.expected_plaintext_tally[first_selection.object_id], result.tally)
def test_decrypt_selection_all_present(self): # Arrange # find the first selection first_contest = [contest for contest in self.ciphertext_tally.cast.values()][0] first_selection = list(first_contest.tally_selections.values())[0] # precompute decryption shares for the guardians first_share = compute_decryption_share( self.guardians[0], self.ciphertext_tally, self.context ) second_share = compute_decryption_share( self.guardians[1], self.ciphertext_tally, self.context ) third_share = compute_decryption_share( self.guardians[2], self.ciphertext_tally, self.context ) # build type: Dict[GUARDIAN_ID, Tuple[ELECTION_PUBLIC_KEY, TallyDecryptionShare]] shares = { self.guardians[0].object_id: ( self.guardians[0].share_election_public_key().key, first_share.contests[first_contest.object_id].selections[ first_selection.object_id ], ), self.guardians[1].object_id: ( self.guardians[1].share_election_public_key().key, second_share.contests[first_contest.object_id].selections[ first_selection.object_id ], ), self.guardians[2].object_id: ( self.guardians[2].share_election_public_key().key, third_share.contests[first_contest.object_id].selections[ first_selection.object_id ], ), } # act result = decrypt_selection_with_decryption_shares( first_selection, shares, self.context.crypto_extended_base_hash ) # assert self.assertIsNotNone(result) self.assertEqual( self.expected_plaintext_tally[first_selection.object_id], result.tally )
def process_message(self, message_type: str, message: dict, context: Context): context.shares[message['guardian_id']] = message if len(context.shares) == context.number_of_guardians: tally_shares = self._prepare_shares_for_decryption(context.shares) results: Dict[CONTEST_ID, PlaintextTallyContest] = {} for contest in context.tally.cast.values(): selections: Dict[SELECTION_ID, PlaintextTallySelection] = dict( pair_with_object_id( decrypt_selection_with_decryption_shares( selection, tally_shares[selection.object_id], context.election_context.crypto_extended_base_hash) ) for selection in contest.tally_selections.values()) results[contest.object_id] = PlaintextTallyContest( contest.object_id, selections) return serialize(results)
def test_compute_compensated_selection(self): """ demonstrates the complete workflow for computing a comepnsated decryption share For one selection. It is useful for verifying that the workflow is correct """ # Arrange first_selection = [ selection for contest in self.ciphertext_tally.cast.values() for selection in contest.tally_selections.values() ][0] # Compute lagrange coefficients for the guardians that are present lagrange_0 = compute_lagrange_coefficient( self.guardians[0].sequence_order, *[self.guardians[1].sequence_order], ) lagrange_1 = compute_lagrange_coefficient( self.guardians[1].sequence_order, *[self.guardians[0].sequence_order], ) print( f"lagrange: sequence_orders: ({self.guardians[0].sequence_order}, {self.guardians[1].sequence_order}, {self.guardians[2].sequence_order})\n" ) print(lagrange_0) print(lagrange_1) # compute their shares share_0 = compute_decryption_share_for_selection( self.guardians[0], first_selection, self.context) share_1 = compute_decryption_share_for_selection( self.guardians[1], first_selection, self.context) self.assertIsNotNone(share_0) self.assertIsNotNone(share_1) # compute compensations shares for the missing guardian compensation_0 = compute_compensated_decryption_share_for_selection( self.guardians[0], self.guardians[2].object_id, first_selection, self.context, identity_auxiliary_decrypt, ) compensation_1 = compute_compensated_decryption_share_for_selection( self.guardians[1], self.guardians[2].object_id, first_selection, self.context, identity_auxiliary_decrypt, ) self.assertIsNotNone(compensation_0) self.assertIsNotNone(compensation_1) print("\nSHARES:") print(compensation_0) print(compensation_1) # Check the share proofs self.assertTrue( compensation_0.proof.is_valid( first_selection.message, get_optional(self.guardians[0].recovery_public_key_for( self.guardians[2].object_id)), compensation_0.share, self.context.crypto_extended_base_hash, )) self.assertTrue( compensation_1.proof.is_valid( first_selection.message, get_optional(self.guardians[1].recovery_public_key_for( self.guardians[2].object_id)), compensation_1.share, self.context.crypto_extended_base_hash, )) share_pow_p = [ pow_p(compensation_0.share, lagrange_0), pow_p(compensation_1.share, lagrange_1), ] print("\nSHARE_POW_P") print(share_pow_p) # reconstruct the missing share from the compensation shares reconstructed_share = mult_p(*[ pow_p(compensation_0.share, lagrange_0), pow_p(compensation_1.share, lagrange_1), ]) print("\nRECONSTRUCTED SHARE\n") print(reconstructed_share) share_2 = CiphertextDecryptionSelection( first_selection.object_id, self.guardians[2].object_id, first_selection.description_hash, reconstructed_share, { self.guardians[0].object_id: compensation_0, self.guardians[1].object_id: compensation_1, }, ) # Decrypt the result result = decrypt_selection_with_decryption_shares( first_selection, { self.guardians[0].object_id: ( self.guardians[0].share_election_public_key().key, share_0, ), self.guardians[1].object_id: ( self.guardians[1].share_election_public_key().key, share_1, ), self.guardians[2].object_id: ( self.guardians[2].share_election_public_key().key, share_2, ), }, self.context.crypto_extended_base_hash, ) print(result) self.assertIsNotNone(result) self.assertEqual( result.plaintext, self.expected_plaintext_tally[first_selection.object_id])
def test_compute_compensated_selection(self): """ demonstrates the complete workflow for computing a comepnsated decryption share For one selection. It is useful for verifying that the workflow is correct """ # Arrange available_guardian_1 = self.guardians[0] available_guardian_2 = self.guardians[1] missing_guardian = self.guardians[2] available_guardian_1_key = available_guardian_1.share_election_public_key( ) available_guardian_2_key = available_guardian_2.share_election_public_key( ) missing_guardian_key = missing_guardian.share_election_public_key() first_selection = [ selection for contest in self.ciphertext_tally.contests.values() for selection in contest.selections.values() ][0] # Compute lagrange coefficients for the guardians that are present lagrange_0 = compute_lagrange_coefficient( available_guardian_1.sequence_order, *[available_guardian_2.sequence_order], ) lagrange_1 = compute_lagrange_coefficient( available_guardian_2.sequence_order, *[available_guardian_1.sequence_order], ) print(( f"lagrange: sequence_orders: ({available_guardian_1.sequence_order}, " f"{available_guardian_2.sequence_order}, {missing_guardian.sequence_order})\n" )) print(lagrange_0) print(lagrange_1) # compute their shares share_0 = compute_decryption_share_for_selection( available_guardian_1._election_keys, first_selection, self.context) share_1 = compute_decryption_share_for_selection( available_guardian_2._election_keys, first_selection, self.context) self.assertIsNotNone(share_0) self.assertIsNotNone(share_1) # compute compensations shares for the missing guardian compensation_0 = compute_compensated_decryption_share_for_selection( available_guardian_1.share_election_public_key(), available_guardian_1._auxiliary_keys, missing_guardian.share_election_public_key(), missing_guardian.share_election_partial_key_backup( available_guardian_1.id), first_selection, self.context, identity_auxiliary_decrypt, ) compensation_1 = compute_compensated_decryption_share_for_selection( available_guardian_2.share_election_public_key(), available_guardian_2._auxiliary_keys, missing_guardian.share_election_public_key(), missing_guardian.share_election_partial_key_backup( available_guardian_2.id), first_selection, self.context, identity_auxiliary_decrypt, ) self.assertIsNotNone(compensation_0) self.assertIsNotNone(compensation_1) print("\nSHARES:") print(compensation_0) print(compensation_1) # Check the share proofs self.assertTrue( compensation_0.proof.is_valid( first_selection.ciphertext, compute_recovery_public_key(available_guardian_1_key, missing_guardian_key), compensation_0.share, self.context.crypto_extended_base_hash, )) self.assertTrue( compensation_1.proof.is_valid( first_selection.ciphertext, compute_recovery_public_key(available_guardian_2_key, missing_guardian_key), compensation_1.share, self.context.crypto_extended_base_hash, )) share_pow_p = [ pow_p(compensation_0.share, lagrange_0), pow_p(compensation_1.share, lagrange_1), ] print("\nSHARE_POW_P") print(share_pow_p) # reconstruct the missing share from the compensation shares reconstructed_share = mult_p(*[ pow_p(compensation_0.share, lagrange_0), pow_p(compensation_1.share, lagrange_1), ]) print("\nRECONSTRUCTED SHARE\n") print(reconstructed_share) share_2 = create_ciphertext_decryption_selection( first_selection.object_id, missing_guardian.id, reconstructed_share, { available_guardian_1.id: compensation_0, available_guardian_2.id: compensation_1, }, ) # Decrypt the result result = decrypt_selection_with_decryption_shares( first_selection, { available_guardian_1.id: ( available_guardian_1.share_election_public_key().key, share_0, ), available_guardian_2.id: ( available_guardian_2.share_election_public_key().key, share_1, ), missing_guardian.id: ( missing_guardian.share_election_public_key().key, share_2, ), }, self.context.crypto_extended_base_hash, ) print(result) self.assertIsNotNone(result) self.assertEqual( result.tally, self.expected_plaintext_tally[first_selection.object_id])