def shuffle_with_proof(self): """ Produce a verifiable shuffle of this ciphertext collection. This method returns a tuple containing a new CiphertextCollection object, which encodes the same plaintexts as the current collection but reencrypted and randomly permuted, and a zero-knowledge proof of shuffling. The proof of shuffling can be used (via the ShuffleProof.verify(...) method) to check that both collections contain encryptions of the same set of plaintexts, under the same cryptosystem and public key. On the other hand, neither the collections nor the proof give any information regarding which ciphertext in the first collection corresponds to which ciphertext in the second, shuffled, collection (hence why we say the proof is done in zero-knowledge). (see Josh Benaloh, "Simple Verifiable Elections", http://www.usenix.org/event/evt06/tech/full_papers/benaloh/benaloh.pdf for more information.) Returns: (shuffled_collection, proof):: (CiphertextCollection, ShufflingProof) -- shuffled_collection is a shuffled version of the current collection, containing different ciphertexts but encoding the same plaintexts as the current collection (in different order). proof is a zero-knowledge proof asserting that the current collection and shuffled_collection are the equivalent (that is, contain representations of the same plaintext in equal numbers). This proof can be verified through the verify(...) method of ShuffleProof itself. Throws: ValueError -- If params.SHUFFLING_PROOF_SECURITY_PARAMETER is within an invalid range. """ # Import CiphertextCollectionMapping and ShufflingProof ## Note: # python has some well known issues with circular imports # (see http://effbot.org/zone/import-confusion.htm) so putting this # import statements at the top of our module in the usual way # will not work. # # Besides, CiphertextCollection as a class is conceptually at a lower # layer of our architecture than both these other classes. Only this # convenience method, designed to make the API easier to use from # outside of plonevotecryptolib, depends on those classes. So it # actually makes more sense to import these classes within this # method only. ## from plonevotecryptolib.Mixnet.CiphertextCollectionMapping import CiphertextCollectionMapping from plonevotecryptolib.Mixnet.ShufflingProof import ShufflingProof # Create a mapping from the current collection into a random shuffling mapping = CiphertextCollectionMapping.new(self) # Apply the mapping to obtain the resulting shuffled collection try: shuffled_collection = mapping.apply(self) except IncompatibleCiphertextCollectionError: assert False, "IncompatibleCiphertextCollectionError may not be " \ "raised when applying a mapping M created using " \ "CiphertextCollectionMapping.new(C) to the same C." # Generate the zero-knowledge proof of shuffling try: proof = ShufflingProof.new(self, shuffled_collection, mapping) except InvalidCiphertextCollectionMappingError: assert False, "InvalidCiphertextCollectionMappingError may not be " \ "raised when shuffled_collection was created from the " \ "original collection (self) by applying the mapping." # Form tuple and return it return (shuffled_collection, proof)
def new(cls, original_collection, shuffled_collection, mapping): """ Constructs a new proof of equivalence between original_collection and shuffled_collection. This method should not be used outside of plonevotecryptolib.Mixnet. Consider using CiphertextCollection.shuffle_with_proof() instead. The given CiphertextCollectionMapping must be a valid mapping between original_collection and shuffled_collection. Arguments: original_collection::CiphertextCollection -- The original collection to be shuffled. shuffled_collection::CiphertextCollection -- The shuffled collection resulting from applying mapping to original_collection. mapping::CiphertextCollectionMapping -- The mapping between original_collection and shuffled_collection. Returns: proof::ShufflingProof -- The zero-knowledge proof of equivalence between original_collection and shuffled_collection. Throws: InvalidCiphertextCollectionMappingError -- If mapping is not a valid mapping between original_collection and shuffled_collection. ValueError -- If params.SHUFFLING_PROOF_SECURITY_PARAMETER is within an invalid range. """ # Check that we have a valid mapping between the two collections: if(not mapping.verify(original_collection, shuffled_collection)): raise InvalidCiphertextCollectionMappingError( \ "mapping was expected to be a CiphertextCollectionMapping "\ "object representing a valid mapping between "\ "original_collection and shuffled_collection. However, "\ "mapping.verify(original_collection, shuffled_collection) "\ "returns False. A zero-knowledge proof of equivalence "\ "between two shuffled collections cannot be constructed "\ "without first having a valid mapping between them.") # Get the security parameter P security_parameter = params.SHUFFLING_PROOF_SECURITY_PARAMETER # Check that the security parameter is <= 256, since that is the most # bits of challenge we can currently have. Also check that P is at least 1 if(security_parameter < 1 or security_parameter > 256): raise ValueError("Security parameter for shuffling proof " \ "(params.SHUFFLING_PROOF_SECURITY_PARAMETER) is out of " \ "range. The security parameter must be between 1 and 256, "\ "its current value is %d. If you have set " \ "CUSTOM_SHUFFLING_PROOF_SECURITY_PARAMETER in the file " \ "params.py, please correct this value or set it to None, " \ "in order for the security parameter to be decided based " \ "on the global SECURITY_LEVEL of plonevotecryptolib. It " \ "is recommended that " \ "CUSTOM_SHUFFLING_PROOF_SECURITY_PARAMETER be always set " \ "to None for deployment operation of plonevotecryptolib." \ % security_parameter) # Construct a new empty proof proof = ShufflingProof() # Populate proof._collections with P random shuffles of # original_collection. We save each mapping for now in proof._mappings. # (ie. every mapping in proof._mappings[i] will initially be from the # original collection into proof._collections[i]) for i in range(0, security_parameter): ## print "Generating collection #%d" % i # replace with TaskMonitor API calls new_mapping = CiphertextCollectionMapping.new(original_collection) proof._mappings.append(new_mapping) proof._collections.append(new_mapping.apply(original_collection)) # Generate the challenge proof._challenge = \ proof._generate_challenge(original_collection, shuffled_collection) # Get the challenge as a BitStream for easier manipulation challenge_bits = BitStream() challenge_bits.put_hex(proof._challenge) challenge_bits.seek(0) # back to the beginning of the stream # For each of the first P bits in the stream for i in range(0, security_parameter): ## print "Processing challenge bit %d" % i # replace with TaskMonitor API calls bit = challenge_bits.get_num(1) if(bit == 0): # Do nothing. # proof._mappings[i] is already a mapping from # original_collection unto proof._collections[i] pass elif(bit == 1): # Change proof._mappings[i] to be a mapping from # proof._collections[i] unto shuffled_collection, using # CiphertextCollectionMapping.rebase(...) # rebase(O->D, O->C_{i}) => C_{i}->D rebased_mapping = mapping.rebase(proof._mappings[i]) # Replace O->C_{i} with C_{i}->D proof._mappings[i] = rebased_mapping else: assert False, "We took a single bit, its value must be either "\ "0 or 1." # return the proof object return proof