def process_message(self, message_type: str, message: dict, context: Context): contests: Dict[CONTEST_ID, CiphertextDecryptionContest] = {} tally_cast: Dict[CONTEST_ID, CiphertextTallyContest] = deserialize( message, Dict[CONTEST_ID, CiphertextTallyContest]) for contest in tally_cast.values(): selections: Dict[SELECTION_ID, CiphertextDecryptionSelection] = dict( pair_with_object_id( compute_decryption_share_for_selection( context.guardian, selection, context.election_context)) for (_, selection ) in contest.tally_selections.items()) contests[contest.object_id] = CiphertextDecryptionContest( contest.object_id, context.guardian_id, contest.description_hash, selections) return serialize({ 'guardian_id': context.guardian_id, 'public_key': context.guardian.share_election_public_key().key, 'contests': contests })
def test_compute_selection(self): # Arrange first_selection = [ selection for contest in self.ciphertext_tally.cast.values() for selection in contest.tally_selections.values() ][0] # act result = compute_decryption_share_for_selection( self.guardians[0], first_selection, self.context) # assert self.assertIsNotNone(result)
def process_message( self, message_type: Literal["tally.cast"], message: Content, context: TrusteeContext, ) -> Tuple[List[Content], ElectionStep[TrusteeContext]]: contests: Dict[CONTEST_ID, CiphertextDecryptionContest] = {} tally_cast: Dict[CONTEST_ID, CiphertextTallyContest] = deserialize( message["content"], Dict[CONTEST_ID, CiphertextTallyContest]) # save for when compensating context.tally = CiphertextTally("election-results", context.election_metadata, context.election_context) context.tally.contests = tally_cast for contest in tally_cast.values(): selections: Dict[ SELECTION_ID, CiphertextDecryptionSelection] = dict( pair_with_object_id( unwrap( compute_decryption_share_for_selection( context.guardian._election_keys, selection, context.election_context, ))) for (_, selection) in contest.selections.items()) contests[contest.object_id] = CiphertextDecryptionContest( contest.object_id, context.guardian_id, contest.description_hash, selections, ) tally_share = DecryptionShare( context.tally.object_id, guardian_id=context.guardian_id, public_key=context.guardian.share_election_public_key().key, contests=contests, ) return [{ "message_type": "tally.trustee_share", "content": serialize(tally_share) }], ProcessEndTally()
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])