def create_single_dummy(self, pk, vid=None, counter=None, dummy=True): voting_key = pk infinity = voting_key.group.infinite() ts_key = self.pk dummy_vote = VoteVector( [elgamal.Ciphertext(infinity, infinity) for _ in range(self.nr_candidates)] ) dummy_tag = elgamal.Ciphertext( ts_key.group.infinite(), ts_key.group.generator() ) real_tag = elgamal.Ciphertext(ts_key.group.infinite(), ts_key.group.infinite()) if vid and counter: encrypted_vid = ts_key.encrypt(vid) counter_elem = (counter - 1) * self.generator self.decrypted_vid.append(vid) self.decrypted_counter.append(counter_elem) encrypted_counter = ts_key.encrypt(counter_elem) self.counter_lookup_table[counter_elem] = counter - 1 # Add deterministic tag encrypted_tag = dummy_tag if dummy else real_tag return ( BallotBundle( encrypted_vid, encrypted_counter, encrypted_tag, dummy_vote ), None, None, ) else: vid = self.order.random() * self.generator counter = self.order.random() counter_elem = counter * self.generator self.counter_lookup_table[counter_elem] = counter self.decrypted_vid.append(vid) self.decrypted_counter.append(counter_elem) encrypted_vid = ts_key.encrypt(vid) encrypted_counter = ts_key.encrypt(counter_elem) encrypted_tag = dummy_tag return ( BallotBundle( encrypted_vid, encrypted_counter, encrypted_tag, dummy_vote ), vid, counter, )
def generate_ballots(pk, vids, counters, single_vote=True, revotes_fraction=0.0): G = pk.group infinity = pk.group.infinite() lookup_table = {} lookup_table[infinity] = 0 vote = VoteVector([pk.encrypt(1 * G.generator())]) ctxts = [] nr_revotes = 0 if single_vote: for i, vid in enumerate(vids): repr = counters[i] * pk.generator lookup_table[repr] = counters[i] ctxts.append( BallotBundle( pk.encrypt(vid * G.generator()), pk.encrypt(counters[i] * G.generator()), elgamal.Ciphertext(infinity, infinity), vote, )) else: # Since it doesn't matter, dump all revotes on the first voter # voter_revoted = np.random.randint(max_votes_added, size=len(vids)) voter_revoted = np.zeros(len(vids), dtype=int) voter_revoted[0] = round(revotes_fraction * len(vids)) nr_revotes = sum(voter_revoted) for i, vid in enumerate(vids): while voter_revoted[i] >= 0: repr = counters[i] * pk.generator lookup_table[repr] = counters[i] ctxts.append( BallotBundle( pk.encrypt(vid * G.generator()), pk.encrypt(counters[i] * G.generator()), elgamal.Ciphertext(infinity, infinity), vote, )) counters[i] += 1 voter_revoted[i] -= 1 return ctxts, lookup_table, nr_revotes
def prepare_ctxts(ctxts, m, election_key): """ Prepares the ctxts list to a compatible ctxts list for the format m * n for the given m, i.e. we append encrypted zeros (with randomization 0) till we reach a length of m * (ceil(len(ctxts) / m) """ import math if len(ctxts) < m: raise ValueError( "Lengths of ciphertexts expected greater than value m.") n = math.ceil(len(ctxts) / m) if type(ctxts[0]) == elgamal.Ciphertext: group = ctxts[0].group zeros = [elgamal.Ciphertext(group.infinite(), group.infinite()) ] * (m * n - len(ctxts)) elif type(ctxts[0]) == BallotBundle: # todo: attention, we are assuming all values in the BallotBundle come from the same group. nr_candidates = ctxts[0].vote.length group = ctxts[0].vid.group vid = group.order().random() counter = group.order().random() encrypted_vid = election_key.encrypt(vid * group.generator()) encrypted_counter = election_key.encrypt(counter * group.generator()) encrypted_tag = election_key.encrypt(1 * group.generator()) zeros = [ BallotBundle( encrypted_vid, encrypted_counter, encrypted_tag, VoteVector([ elgamal.Ciphertext(group.infinite(), group.infinite()) for _ in range(nr_candidates) ]), ) ] * (m * n - len(ctxts)) else: raise ValueError( "Unexpected type of ciphertexts. Expecting Ciphertext or BallotBundle, got {0}", type(ctxts[0]), ) ctxts.extend(zeros) return ctxts, n
def prepare_ctxts(self, ctxts, m, election_key): """ Prepares the ctxts list to a compatible ctxts list for the format m * n for the given m, i.e. we append encrypted zeros (with randomization 0) till we reach a length of m * (ceil(len(ctxts) / m) """ import math if len(ctxts) < m: raise ValueError("Lengths of ciphertexts expected greater than value m.") n = math.ceil(len(ctxts) / m) infinity = election_key.pk.group.infinite() dummies = [] if type(ctxts[0]) == BallotBundle: # TODO: attention, we are assuming all values in the BallotBundle come from the same group. for _ in range(m * n - len(ctxts)): dummy, vid, counter = self.create_single_dummy(election_key, dummy=True) dummies.append(dummy) elif type(ctxts[0]) == VoteVector: # TODO: this should not be necessary anymore for _ in range(m * n - len(ctxts)): dummy_vote = VoteVector( [ elgamal.Ciphertext(infinity, infinity) for _ in range(self.nr_candidates) ] ) dummies.append(dummy_vote) elif type(ctxts[0]) == elgamal.Ciphertext: for _ in range(m * n - len(ctxts)): dummy_vote = elgamal.Ciphertext(infinity, infinity) dummies.append(dummy_vote) else: raise ValueError( "Unexpected type of ciphertexts. Expecting BallotBundle or VoteVector, got {0}", type(ctxts[0]), ) ctxts.extend(dummies) return ctxts, n
def verify(self): """Verify that the full tally proceedure has been correctly performed. Again, to avoid passing variables, we define everything that should be posted in the BB as a class variable. This should not be done in a proper implementation. todo: verify function should get the result of the filtering as input # Example: # >>> G = EcGroup() # >>> key_pair = elgamal.KeyPair(G) # >>> pk = key_pair.pk # >>> m = 2 # >>> nr_candidates = 1 # >>> number_ballots = 10 # >>> security_param = 128 # >>> vids, counters = election_setup(G, number_ballots, security_param) # >>> ctxts, counter_lookup_table, _ = generate_ballots(pk, vids, counters, nr_candidates, single_vote=False, revotes_fraction=0.4) # >>> # >>> tally_proof = Filter(key_pair, key_pair.pk, ctxts, m, counter_lookup_table) # >>> tally_proof.verify() # True """ verification_start = time.process_time() # Step 2a + 2b: verify tags of dummy ballots # verify all dummies encrypt zero dummies_verif = [] zero_vote = VoteVector( [self.election_key.encrypt(self.group.infinite(), 0)] * self.nr_candidates ) dummy_tag = elgamal.Ciphertext(self.group.infinite(), self.group.generator()) for dummies in self.dummies: dummies_verif.append(dummies.vote == zero_vote) # TODO: added this check, should be here, make sure this doesn't break things dummies_verif.append(dummies.tag == dummy_tag) dummies_time_verif = time.process_time() # Step 2c: Verify the shuffle proof ver_shuffle_proof = self.proof_of_shuffle.verify( self.com_pk, self.pk, self.ctxts, self.shuffled_ctxts ) shuffle_time_ver_end = time.process_time() # Step 2d: Verify correctness of decryptions of vid and indexes proof_decryptions = [] for index, entry in enumerate(self.decrypted_vid_index): proof_decryptions.append( entry[1].verify(self.reshaped_shuffled_ctxts[index].vid, entry[0]) ) proof_decryptions.append( entry[3].verify(self.reshaped_shuffled_ctxts[index].index, entry[2]) ) dec_time_ver = time.process_time() # Step 2e: Verify reencryption and grouping # MISISNG: verify should comput its own grouping, but ok # Verify correctness of reencryptions. Here we are verifying the reencryptions of each ciphertext corresponding # to a particular candidate. # TODO: are we sure this "corresponding to a particular candidate" is still correct? proof_reencryptions = [] for index, proof in enumerate(self.reencryption_proofs): proof_reencryptions.append( proof[1].verify( self.pk, self.pk, getattr(self.tags, str(self.decrypted_vid_index[proof[0]][0])), self.reshaped_shuffled_ctxts[proof[0]].vote, ) ) reenc_time_ver = time.process_time() # Step 2f: Verify the final shuffle proof ver_final_shuffle_proof = self.final_proof_of_shuffle.verify( self.final_com_pk, self.pk, self.selected_votes_padded, self.selected_votes_shuffled, ) final_shuffle_time = time.process_time() # Step 2g: Verify opening of dummy ballots before tallying # TODO: for now just recomputing ciphertexts for ind, rand in zip( self.revealed_dummy_indices, self.revealed_dummy_randomizers ): zero_vote = VoteVector( [self.election_key.encrypt(self.group.infinite(), rand)] * self.nr_candidates ) # TODO: actually compare against something final_open_time = time.process_time() self.dummies_time_ver = dummies_time_verif - verification_start self.shufle_time_ver = shuffle_time_ver_end - dummies_time_verif self.dec_time_ver = dec_time_ver - shuffle_time_ver_end self.reenc_time_ver = reenc_time_ver - dec_time_ver self.final_shuffle_time_ver = final_shuffle_time - reenc_time_ver self.final_open_time_ver = final_open_time - final_shuffle_time return ( ver_shuffle_proof and all(proof_decryptions) and all(proof_reencryptions) and all(dummies_verif) and ver_final_shuffle_proof )
def ctxt_weighted_sum(list_ctxts, weights): """ Function wsum applied to our object of ciphertexts Example: >>> G = EcGroup() >>> key_pair = elgamal.KeyPair(G) >>> pk = key_pair.pk >>> ctxts = [pk.encrypt((i) * G.generator()) for i in range(9)] >>> weights = [Bn.from_num(i) for i in range(9)] >>> function_sum = MultiExponantiation.ctxt_weighted_sum(ctxts, weights) >>> weighted_sum = prod([ctxts[i] ** weights[i] for i in range(9)]) >>> function_sum == weighted_sum True """ ctxt_type = type(list_ctxts[0]) if ctxt_type == elgamal.Ciphertext: group = list_ctxts[0].group c1s = [ctxts.c1 for ctxts in list_ctxts] c2s = [ctxts.c2 for ctxts in list_ctxts] return elgamal.Ciphertext(group.wsum(weights, c1s), group.wsum(weights, c2s)) elif ctxt_type == BallotBundle: # todo: again, we are assuming that all elements of BallotBundle come from the same group. group = list_ctxts[0].vid.group nr_candidates = list_ctxts[0].vote.length c1s_vid = [] c2s_vid = [] c1s_index = [] c2s_index = [] c1s_tag = [] c2s_tag = [] c1s_vote = [[] for _ in range(nr_candidates)] c2s_vote = [[] for _ in range(nr_candidates)] for ctxts in list_ctxts: c1s_vid.append(ctxts.vid.c1) c2s_vid.append(ctxts.vid.c2) c1s_index.append(ctxts.index.c1) c2s_index.append(ctxts.index.c2) c1s_tag.append(ctxts.tag.c1) c2s_tag.append(ctxts.tag.c2) candidates_c1 = ctxts.vote.c1() for a, b in zip(c1s_vote, candidates_c1): a.extend(b) candidates_c2 = ctxts.vote.c2() for a, b in zip(c2s_vote, candidates_c2): a.extend(b) return BallotBundle( elgamal.Ciphertext(group.wsum(weights, c1s_vid), group.wsum(weights, c2s_vid)), elgamal.Ciphertext(group.wsum(weights, c1s_index), group.wsum(weights, c2s_index)), elgamal.Ciphertext(group.wsum(weights, c1s_tag), group.wsum(weights, c2s_tag)), VoteVector([ elgamal.Ciphertext( group.wsum(weights, c1s_votes), group.wsum(weights, c2s_votes), ) for c1s_votes, c2s_votes in zip(c1s_vote, c2s_vote) ]), ) else: raise ValueError( "Unexpected type of ciphertexts. Expecting Ciphertext or BallotBundle, got {0}", type(ctxt_type), )
def __init__( self, com_pk, pk, ciphertexts, exponantiated_reencrypted_product, exponents_commitment, exponents, commitment_randomizer, reencrypted_randomizer, ): """Shuffle works for both ciphertext of type Ciphertext, or ciphertexts of type BallotBundle""" self.order = com_pk.group.order() self.infinity = pk.group.infinite() self.m = len(ciphertexts) self.n = len(ciphertexts[0]) self.G = pk.generator self.type = type(ciphertexts[0][0]) # If entry is a ballot bundle, then calculate the number of ciphertexts if self.type == BallotBundle: self.nr_candidates = ciphertexts[0][0].vote.length else: self.nr_candidates = None # Prepare announcement announcementA_values = [self.order.random() for _ in range(self.n)] announcementA_randomiser = self.order.random() exponents.insert(0, announcementA_values) commitment_randomizer.insert(0, announcementA_randomiser) announcementB_values = [self.order.random() for _ in range(2 * self.m)] announcementB_randomisers = [ self.order.random() for _ in range(2 * self.m) ] announcement_reencryption_randomisers = [ self.order.random() for _ in range(2 * self.m) ] announcementB_values[self.m] = 0 announcementB_randomisers[self.m] = 0 announcement_reencryption_randomisers[self.m] = reencrypted_randomizer self.announcementA = com_pk.commit(announcementA_values, announcementA_randomiser)[0] self.announcementB = [ com_pk.commit_reduced([announcementB_values[i]], 1, announcementB_randomisers[i])[0] for i in range(2 * self.m) ] diagonals = [] for k in range(2 * self.m): # Initiate diagonal as the zero BallotBundle diagonal = (BallotBundle( elgamal.Ciphertext(self.infinity, self.infinity), elgamal.Ciphertext(self.infinity, self.infinity), elgamal.Ciphertext(self.infinity, self.infinity), VoteVector([ elgamal.Ciphertext(self.infinity, self.infinity) for _ in range(self.nr_candidates) ]), ) if self.type != elgamal.Ciphertext else elgamal.Ciphertext( self.infinity, self.infinity)) for i in range(self.m): j = k - self.m + i + 1 if j < 0: continue if j > self.m: break diagonal *= self.ctxt_weighted_sum(ciphertexts[i], exponents[j]) diagonals.append(diagonal) # We begin with additive notation for the public keys if self.type == elgamal.Ciphertext: self.announcement_reencryption = [ pk.encrypt( announcementB_values[i] * self.G, announcement_reencryption_randomisers[i], ) * diagonals[i] for i in range(2 * self.m) ] elif self.type == BallotBundle: self.announcement_reencryption = [ BallotBundle( pk.encrypt( announcementB_values[i] * self.G, announcement_reencryption_randomisers[i], ), pk.encrypt( announcementB_values[i] * self.G, announcement_reencryption_randomisers[i], ), pk.encrypt( announcementB_values[i] * self.G, announcement_reencryption_randomisers[i], ), VoteVector([ pk.encrypt( announcementB_values[i] * self.G, announcement_reencryption_randomisers[i], ) for _ in range(self.nr_candidates) ]), ) * diagonals[i] for i in range(2 * self.m) ] else: raise ValueError( "Unexpected type of ciphertexts. Expecting Ciphertext or BallotBundle, got {0}", type(self.type), ) # Compute challenge # todo: change challenge self.challenge = compute_challenge( self.announcementB + [self.announcementA], self.order) # Prepare response challenge_powers = [ self.challenge.mod_pow(i, self.order) for i in range(self.m + 1) ] self.responseA = [ sum([ exponents[j][i] * challenge_powers[j] for j in range(self.m + 1) ]) for i in range(self.n) ] self.responseA_randomizers = sum([ commitment_randomizer[i] * challenge_powers[i] for i in range(self.m + 1) ]) self.responseB = sum([ announcementB_values[i] * (self.challenge.mod_pow(i, self.order)) for i in range(self.m * 2) ]) self.responseB_randomizers = sum([ announcementB_randomisers[i] * (self.challenge.mod_pow(i, self.order)) for i in range(self.m * 2) ]) self.response_reencryption_randomisers = sum([ announcement_reencryption_randomisers[i] * (self.challenge.mod_pow(i, self.order)) for i in range(self.m * 2) ])
def verify( self, com_pk, pk, ciphertexts, exponantiated_reencrypted_product, exponents_commitment, ): """ Verify multi-exponantiation argument. Example: >>> G = EcGroup() >>> com_pk = com.PublicKey(G, 3) >>> key_pair = elgamal.KeyPair(G) >>> pk = key_pair.pk >>> ctxts = [pk.encrypt((i) * G.generator()) for i in range(9)] >>> ctxts = [ctxts[i*3:(i+1)*3] for i in range(3)] >>> exponents = [2, 0, 1, 3, 5, 8, 6, 7, 4] >>> exponents_Bn = [Bn.from_num(i) for i in exponents] >>> exponents = [exponents_Bn[i * 3:(i + 1) * 3] for i in range(3)] >>> randomizers = [G.order().random() for _ in range(3)] >>> >>> reencryption_randomization = G.order().random() >>> product_ctxts = prod([MultiExponantiation.ctxt_weighted_sum(ctxts[i], exponents[i]) for i in range(3)]) >>> >>> exponantiated_reencrypted_product = pk.encrypt(G.infinite(), reencryption_randomization) * product_ctxts >>> >>> commitment_permutation = [com_pk.commit(exponents[i], randomizers[i])[0] for i in range(3)] >>> proof = MultiExponantiation(com_pk, pk, ctxts, exponantiated_reencrypted_product, commitment_permutation, exponents, randomizers, reencryption_randomization) >>> proof.verify(com_pk, pk, ctxts, exponantiated_reencrypted_product, commitment_permutation) True >>> ctxts_fake = [pk.encrypt((i + 1) * G.generator()) for i in range(9)] >>> ctxts_fake = [ctxts_fake[i*3:(i+1)*3] for i in range(3)] >>> exponents = [2, 0, 1, 3, 5, 8, 6, 7, 4] >>> exponents_Bn = [Bn.from_num(i) for i in exponents] >>> exponents = [exponents_Bn[i * 3:(i + 1) * 3] for i in range(3)] >>> randomizers = [G.order().random() for _ in range(3)] >>> >>> reencryption_randomization = G.order().random() >>> product_ctxts = prod([MultiExponantiation.ctxt_weighted_sum(ctxts[i], exponents[i]) for i in range(3)]) >>> >>> exponantiated_reencrypted_product = pk.encrypt(G.infinite(), reencryption_randomization) * product_ctxts >>> >>> commitment_permutation = [com_pk.commit(exponents[i], randomizers[i])[0] for i in range(3)] >>> proof = MultiExponantiation(com_pk, pk, ctxts_fake, exponantiated_reencrypted_product, commitment_permutation, exponents, randomizers, reencryption_randomization) >>> proof.verify(com_pk, pk, ctxts_fake, exponantiated_reencrypted_product, commitment_permutation) False """ check1 = com_pk.group.check_point(self.announcementA.commitment) check2 = all([ com_pk.group.check_point(self.announcementB[i].commitment) for i in range(self.m) ]) if self.type == elgamal.Ciphertext: check3 = all([ pk.group.check_point(self.announcement_reencryption[i].c1) and pk.group.check_point(self.announcement_reencryption[i].c2) for i in range(self.m * 2) ]) elif self.type == BallotBundle: check3 = all([ pk.group.check_point(self.announcement_reencryption[i].vid.c1) and pk.group.check_point( self.announcement_reencryption[i].vid.c2) and pk.group.check_point( self.announcement_reencryption[i].index.c1) and pk.group.check_point( self.announcement_reencryption[i].index.c2) and pk.group.check_point(self.announcement_reencryption[i].tag.c1) and pk.group.check_point( self.announcement_reencryption[i].tag.c2) and all([ pk.group.check_point(c1s[0]) for c1s in self.announcement_reencryption[i].vote.c1() ]) and all([ pk.group.check_point(c2s[0]) for c2s in self.announcement_reencryption[i].vote.c2() ]) for i in range(self.m * 2) ]) else: raise ValueError( "Unexpected ciphertext type. Expected either 'Ciphertext' or 'BallotBundle'. Got {0})", self.type, ) check4 = self.announcementB[self.m] == com_pk.commit_reduced([0], 1, 0)[0] check5 = (self.announcement_reencryption[self.m] == exponantiated_reencrypted_product) exponents_product_A = [ self.challenge.mod_pow(i, self.order) for i in range(1, self.m + 1) ] product_A = self.announcementA * self.comm_weighted_sum( exponents_commitment, exponents_product_A) check6 = (product_A == com_pk.commit(self.responseA, self.responseA_randomizers)[0]) exponents_product_B = [ self.challenge.mod_pow(i, self.order) for i in range(self.m * 2) ] product_B = self.comm_weighted_sum(self.announcementB, exponents_product_B) check7 = (product_B == com_pk.commit_reduced( [self.responseB], 1, self.responseB_randomizers)[0]) exponents_product_E = [ self.challenge.mod_pow(i, self.order) for i in range(self.m * 2) ] product_E = self.ctxt_weighted_sum(self.announcement_reencryption, exponents_product_E) encryption_responseB = pk.encrypt( self.responseB * self.G, self.response_reencryption_randomisers) reencryption_value = (BallotBundle( encryption_responseB, encryption_responseB, encryption_responseB, VoteVector( [encryption_responseB for _ in range(self.nr_candidates)]), ) if self.type != elgamal.Ciphertext else encryption_responseB) verification_product_E = reencryption_value * prod([ self.ctxt_weighted_sum( ciphertexts[i], [(self.challenge.mod_pow(self.m - (i + 1), self.order)) * self.responseA[j] for j in range(self.n)], ) for i in range(self.m) ]) check8 = product_E == verification_product_E return all( [check1, check2, check3, check4, check5, check6, check7, check8])
def __init__(self, kp_tally, pk_vote, tokens, vote, max_tag=None): # Security parameters self.sec_param = 256 self.bn_two = Bn.from_num(2) self.hash_reduction = self.bn_two.pow(self.sec_param) self.pk_tally = kp_tally.pk self.group_tally = self.pk_tally.group self.order_tally = self.group_tally.order() self.pk_vote = pk_vote self.tokens = tokens if max_tag: self.max_tag = max_tag else: self.max_tag = self.tokens[-1] self.dummy = self.max_tag.decrypt( kp_tally.sk) == 1 * kp_tally.group.generator() self.vote = vote self.number_candidates = self.vote.length self.number_dummies = len(tokens) last_token_decryption = self.tokens[-1].decrypt(kp_tally.sk) self.added_token = tokens[0] for token in tokens[1:]: self.added_token = self.added_token * token if type(self.vote) != VoteVector: raise ValueError("Expected vote type of VoteVector") # reencryption of vote randomizer_reencryption = self.order_tally.random() if self.dummy: self.reencrypted_vote = VoteVector([ pk_vote.encrypt(0 * kp_tally.group.generator(), randomizer_reencryption) ] * self.number_candidates) else: self.reencrypted_vote = pk_vote.reencrypt(vote, randomizer_reencryption) # Prover for dummy prover_dummy = DummyVoterReencryptionProver( kp_tally, pk_vote, self.added_token, self.reencrypted_vote, randomizer_reencryption, self.number_dummies, ) # Prover for real prover_real = RealVoterReencryptionProver( kp_tally, pk_vote, self.max_tag, vote, self.reencrypted_vote, randomizer_reencryption, ) # If the voter is real if last_token_decryption == 0 * self.group_tally.generator(): self.proof_dummy = prover_dummy.simulate() commitment_pk, commitment_token, commitment_vote1, commitment_vote2 = list( prover_real.commit()) # Challenge big proof self.main_challenge = compute_challenge( [commitment_pk] + [commitment_token] + commitment_vote1.tolist() + commitment_vote2.tolist() + [self.proof_dummy.commitment_pk] + [self.proof_dummy.commitment_token] + self.proof_dummy.commitment_vote1.tolist() + self.proof_dummy.commitment_vote2.tolist() + self.vote.tolist() + self.added_token.tolist() + [self.pk_vote.pk] + [self.pk_tally.pk], self.hash_reduction, ) self.true_challenge = self.main_challenge - self.proof_dummy.challenge self.proof_real = prover_real.create_proof( [ commitment_pk, commitment_token, commitment_vote1, commitment_vote2 ], self.true_challenge, ) # If the voter is dummy else: self.proof_real = prover_real.simulate() commitment_pk, commitment_token, commitment_vote1, commitment_vote2 = list( prover_dummy.commit()) # Challenge big proof self.main_challenge = compute_challenge( [self.proof_real.commitment_pk] + [self.proof_real.commitment_token] + self.proof_real.commitment_vote1.tolist() + self.proof_real.commitment_vote2.tolist() + [commitment_pk] + [commitment_token] + commitment_vote1.tolist() + commitment_vote2.tolist() + self.vote.tolist() + self.added_token.tolist() + [self.pk_vote.pk] + [self.pk_tally.pk], self.hash_reduction, ) self.true_challenge = self.main_challenge - self.proof_real.challenge self.proof_dummy = prover_dummy.create_proof( [ commitment_pk, commitment_token, commitment_vote1, commitment_vote2 ], self.true_challenge, )