コード例 #1
0
ファイル: test_ray_reduce.py プロジェクト: nealmcb/arlo-e2e
def r_elgamal_add(progressbar_actor: Optional[ActorHandle],
                  *counters: ElGamalCiphertext) -> ElGamalCiphertext:
    num_counters = len(counters)
    result = elgamal_add(*counters)
    if progressbar_actor:
        progressbar_actor.update_completed.remote("Tallies", num_counters)
    return result
コード例 #2
0
ファイル: test_ray_reduce.py プロジェクト: nealmcb/arlo-e2e
    def test_reduce_with_ray_wait_with_progress(
            self, counters: List[int], keypair: ElGamalKeyPair) -> None:
        nonces = Nonces(int_to_q(3))[0:len(counters)]
        pbar = ProgressBar({
            "Ballots": len(counters),
            "Tallies": len(counters),
            "Iterations": 0
        })

        ciphertexts: List[ObjectRef] = [
            r_encrypt.remote(pbar.actor, p, n, keypair.public_key)
            for p, n in zip(counters, nonces)
        ]

        # compute in parallel
        ptotal = ray.get(
            ray_reduce_with_ray_wait(
                inputs=ciphertexts,
                shard_size=3,
                reducer_first_arg=pbar.actor,
                reducer=r_elgamal_add.remote,
                progressbar=pbar,
                progressbar_key="Tallies",
                timeout=None,
                verbose=False,
            ))

        # recompute serially
        stotal = elgamal_add(*ray.get(ciphertexts))

        self.assertEqual(stotal, ptotal)
コード例 #3
0
def _accumulate_encrypted_ballots(
        encrypted_zero: ElGamalCiphertext,
        ballots: List[CiphertextBallot]) -> Dict[str, ElGamalCiphertext]:
    """
    Internal helper function for testing: takes a list of encrypted ballots as input,
    digs into all of the individual selections and then accumulates them, using
    their `object_id` fields as keys. This function only knows what to do with
    `n_of_m` elections. It's not a general-purpose tallying mechanism for other
    election types.

    Note that the output will include both "normal" and "placeholder" selections.

    :param encrypted_zero: an encrypted zero, used for the accumulation
    :param ballots: a list of encrypted ballots
    :return: a dict from selection object_id's to `ElGamalCiphertext` totals
    """
    tally: Dict[str, ElGamalCiphertext] = {}
    for ballot in ballots:
        for contest in ballot.contests:
            for selection in contest.ballot_selections:
                desc_id = (
                    selection.object_id
                )  # this should be the same as in the PlaintextBallot!
                if desc_id not in tally:
                    tally[desc_id] = encrypted_zero
                tally[desc_id] = elgamal_add(tally[desc_id],
                                             selection.ciphertext)
    return tally
コード例 #4
0
ファイル: tally.py プロジェクト: nealmcb/arlo-e2e
def sequential_tally(
        ptallies: Sequence[Optional[TALLY_INPUT_TYPE]]) -> TALLY_TYPE:
    """
    Internal function: sequentially tallies all of the ciphertext ballots, or other partial tallies,
    and returns a partial tally. If any input tally happens to be `None` or an empty dict,
    the result is an empty dict.
    """
    # log_and_print(f"Sequential, local tally with {len(ptallies)} inputs")

    num_nones = sum([1 for p in ptallies if p is None or p == {}])
    if num_nones > 0 in ptallies:
        log_and_print(
            f"Found {num_nones} failed partial tallies, returning an empty tally"
        )
        return {}

    result: TALLY_TYPE = {}
    for ptally in ptallies:
        # we want do our computation purely in terms of TALLY_TYPE, so we'll convert CiphertextBallots
        if isinstance(ptally, CiphertextBallot):
            ptally = ciphertext_ballot_to_dict(ptally)

        if ptally is None:
            # should never happen, but paranoia to keep the type system happy
            return {}

        for k in ptally.keys():
            if k not in result:
                result[k] = ptally[k]
            else:
                counter_sum = result[k]
                counter_partial = ptally[k]
                counter_sum = elgamal_add(counter_sum, counter_partial)
                result[k] = counter_sum
    return result
コード例 #5
0
def elgamal_reencrypt(
    public_key: ElementModP, nonce: ElementModQ, ciphertext: ElGamalCiphertext
) -> Optional[ElGamalCiphertext]:
    return flatmap_optional(
        elgamal_encrypt(0, nonce, public_key),
        lambda zero: elgamal_add(zero, ciphertext),
    )
コード例 #6
0
    def test_elgamal_add_homomorphic_accumulation_decrypts_successfully(
        self,
        keypair: ElGamalKeyPair,
        m1: int,
        r1: ElementModQ,
        m2: int,
        r2: ElementModQ,
    ):
        c1 = get_optional(elgamal_encrypt(m1, r1, keypair.public_key))
        c2 = get_optional(elgamal_encrypt(m2, r2, keypair.public_key))
        c_sum = elgamal_add(c1, c2)
        total = c_sum.decrypt(keypair.secret_key)

        self.assertEqual(total, m1 + m2)
コード例 #7
0
    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
コード例 #8
0
ファイル: test_ray_reduce.py プロジェクト: nealmcb/arlo-e2e
    def test_reduce_with_rounds_without_progress(
            self, counters: List[int], keypair: ElGamalKeyPair) -> None:
        nonces = Nonces(int_to_q(3))[0:len(counters)]

        ciphertexts: List[ObjectRef] = [
            r_encrypt.remote(None, p, n, keypair.public_key)
            for p, n in zip(counters, nonces)
        ]

        # compute in parallel
        ptotal = ray.get(
            ray_reduce_with_rounds(
                inputs=ciphertexts,
                shard_size=3,
                reducer_first_arg=None,
                reducer=r_elgamal_add.remote,
                progressbar=None,
                verbose=True,
            ))

        # recompute serially
        stotal = elgamal_add(*ray.get(ciphertexts))

        self.assertEqual(stotal, ptotal)
コード例 #9
0
    def test_encrypt_ballot_with_derivative_nonces_regenerates_valid_proofs(
            self, keypair: ElGamalKeyPair):
        """
        This test verifies that we can regenerate the contest and selection proofs from the cached nonce values
        """

        # TODO: Hypothesis test instead

        # Arrange
        election = election_factory.get_simple_election_from_file()
        metadata, context = election_factory.get_fake_ciphertext_election(
            election, keypair.public_key)

        data = ballot_factory.get_simple_ballot_from_file()
        self.assertTrue(data.is_valid(metadata.ballot_styles[0].object_id))

        device = EncryptionDevice("Location")
        subject = EncryptionMediator(metadata, context, device)

        # Act
        result = subject.encrypt(data)
        self.assertTrue(
            result.is_valid_encryption(context.crypto_extended_base_hash,
                                       keypair.public_key))

        # Assert
        for contest in result.contests:
            # Find the contest description
            contest_description = list(
                filter(lambda i: i.object_id == contest.object_id,
                       metadata.contests))[0]

            # Homomorpically accumulate the selection encryptions
            elgamal_accumulation = elgamal_add(
                *
                [selection.message for selection in contest.ballot_selections])
            # accumulate the selection nonce's
            aggregate_nonce = add_q(
                *[selection.nonce for selection in contest.ballot_selections])

            regenerated_constant = make_constant_chaum_pedersen(
                elgamal_accumulation,
                contest_description.number_elected,
                aggregate_nonce,
                keypair.public_key,
                add_q(contest.nonce, TWO_MOD_Q),
            )

            self.assertTrue(
                regenerated_constant.is_valid(elgamal_accumulation,
                                              keypair.public_key))

            for selection in contest.ballot_selections:
                # Since we know the nonce, we can decrypt the plaintext
                representation = selection.message.decrypt_known_nonce(
                    keypair.public_key, selection.nonce)

                # one could also decrypt with the secret key:
                # representation = selection.message.decrypt(keypair.secret_key)

                regenerated_disjuctive = make_disjunctive_chaum_pedersen(
                    selection.message,
                    selection.nonce,
                    keypair.public_key,
                    add_q(selection.nonce, TWO_MOD_Q),
                    representation,
                )

                self.assertTrue(
                    regenerated_disjuctive.is_valid(selection.message,
                                                    keypair.public_key))