コード例 #1
0
    def test_djcp_proofs_simple(self):
        # doesn't get any simpler than this
        keypair = elgamal_keypair_from_secret(TWO_MOD_Q)
        nonce = ONE_MOD_Q
        seed = TWO_MOD_Q
        message0 = get_optional(elgamal_encrypt(0, nonce, keypair.public_key))
        proof0 = make_disjunctive_chaum_pedersen_zero(message0, nonce,
                                                      keypair.public_key,
                                                      ONE_MOD_Q, seed)
        proof0bad = make_disjunctive_chaum_pedersen_one(
            message0, nonce, keypair.public_key, ONE_MOD_Q, seed)
        self.assertTrue(
            proof0.is_valid(message0, keypair.public_key, ONE_MOD_Q))
        self.assertFalse(
            proof0bad.is_valid(message0, keypair.public_key, ONE_MOD_Q))

        message1 = get_optional(elgamal_encrypt(1, nonce, keypair.public_key))
        proof1 = make_disjunctive_chaum_pedersen_one(message1, nonce,
                                                     keypair.public_key,
                                                     ONE_MOD_Q, seed)
        proof1bad = make_disjunctive_chaum_pedersen_zero(
            message1, nonce, keypair.public_key, ONE_MOD_Q, seed)
        self.assertTrue(
            proof1.is_valid(message1, keypair.public_key, ONE_MOD_Q))
        self.assertFalse(
            proof1bad.is_valid(message1, keypair.public_key, ONE_MOD_Q))
コード例 #2
0
    def test_ccp_proof(
        self,
        keypair: ElGamalKeyPair,
        nonce: ElementModQ,
        seed: ElementModQ,
        constant: int,
        bad_constant: int,
    ):
        # assume() slows down the test-case generation
        # so assume(constant != bad_constant)
        if constant == bad_constant:
            bad_constant = constant + 1

        message = get_optional(elgamal_encrypt(constant, nonce, keypair.public_key))
        message_bad = get_optional(
            elgamal_encrypt(bad_constant, nonce, keypair.public_key)
        )

        proof = make_constant_chaum_pedersen(
            message, constant, nonce, keypair.public_key, seed
        )
        self.assertTrue(proof.is_valid(message, keypair.public_key))

        proof_bad1 = make_constant_chaum_pedersen(
            message_bad, constant, nonce, keypair.public_key, seed
        )
        self.assertFalse(proof_bad1.is_valid(message_bad, keypair.public_key))

        proof_bad2 = make_constant_chaum_pedersen(
            message, bad_constant, nonce, keypair.public_key, seed
        )
        self.assertFalse(proof_bad2.is_valid(message, keypair.public_key))

        proof_bad3 = proof._replace(constant=-1)
        self.assertFalse(proof_bad3.is_valid(message, keypair.public_key))
コード例 #3
0
    def test_djcp_proof_broken(self, keypair: ElGamalKeyPair,
                               nonce: ElementModQ, seed: ElementModQ):
        # verify two different ways to generate an invalid C-P proof.
        message = get_optional(elgamal_encrypt(0, nonce, keypair.public_key))
        message_bad = get_optional(
            elgamal_encrypt(2, nonce, keypair.public_key))
        proof = make_disjunctive_chaum_pedersen_zero(message, nonce,
                                                     keypair.public_key, seed)
        proof_bad = make_disjunctive_chaum_pedersen_zero(
            message_bad, nonce, keypair.public_key, seed)

        self.assertFalse(proof_bad.is_valid(message_bad, keypair.public_key))
        self.assertFalse(proof.is_valid(message_bad, keypair.public_key))
コード例 #4
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)
コード例 #5
0
    def test_simple_elgamal_encryption_decryption(self):
        nonce = ONE_MOD_Q
        secret_key = TWO_MOD_Q
        keypair = get_optional(elgamal_keypair_from_secret(secret_key))
        public_key = keypair.public_key

        self.assertLess(public_key.to_int(), P)
        elem = g_pow_p(ZERO_MOD_Q)
        self.assertEqual(elem, ONE_MOD_P)  # g^0 == 1

        ciphertext = get_optional(elgamal_encrypt(0, nonce,
                                                  keypair.public_key))
        self.assertEqual(G, ciphertext.alpha.to_int())
        self.assertEqual(
            pow(ciphertext.alpha.to_int(), secret_key.to_int(), P),
            pow(public_key.to_int(), nonce.to_int(), P),
        )
        self.assertEqual(
            ciphertext.beta.to_int(),
            pow(public_key.to_int(), nonce.to_int(), P),
        )

        plaintext = ciphertext.decrypt(keypair.secret_key)

        self.assertEqual(0, plaintext)
コード例 #6
0
    def test_elgamal_encryption_decryption_with_known_nonce_inverses(
            self, message: int, nonce: ElementModQ, keypair: ElGamalKeyPair):
        ciphertext = get_optional(
            elgamal_encrypt(message, nonce, keypair.public_key))
        plaintext = ciphertext.decrypt_known_nonce(keypair.public_key, nonce)

        self.assertEqual(message, plaintext)
コード例 #7
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),
    )
コード例 #8
0
    def test_electionguard_basics(self) -> None:
        plaintexts = range(0, 1000)
        nonces = Nonces(int_to_q(3))
        keypair = elgamal_keypair_random()
        r_public_key = ray.put(keypair.public_key)

        start = timer()
        serial_ciphertexts: List[ElGamalCiphertext] = [
            elgamal_encrypt(p, n, keypair.public_key)
            for p, n in zip(plaintexts, nonces)
        ]
        serial_time = timer()

        # List[ObjectRef[ElGamalCiphertext]
        parallel_ciphertext_objects: List[ObjectRef] = [
            r_encrypt.remote(p, n, r_public_key) for p, n in zip(plaintexts, nonces)
        ]
        parallel_ciphertexts: List[ElGamalCiphertext] = ray.get(
            parallel_ciphertext_objects
        )

        parallel_time = timer()

        self.assertEqual(serial_ciphertexts, parallel_ciphertexts)
        print(
            f"Parallel speedup: {(serial_time - start) / (parallel_time - serial_time):.3f}x"
        )
コード例 #9
0
    def test_ccp_proof(
        self,
        keypair: ElGamalKeyPair,
        nonce: ElementModQ,
        seed: ElementModQ,
        constant: int,
        bad_constant: int,
    ):
        if constant == bad_constant:
            bad_constant = constant + 1

        message = get_optional(elgamal_encrypt(constant, nonce, keypair.public_key))
        decryption = message.partial_decrypt(keypair.secret_key)
        proof = make_chaum_pedersen(
            message, keypair.secret_key, decryption, seed, ONE_MOD_Q
        )
        bad_proof = make_chaum_pedersen(
            message, keypair.secret_key, int_to_p(bad_constant), seed, ONE_MOD_Q
        )
        self.assertTrue(
            proof.is_valid(message, keypair.public_key, decryption, ONE_MOD_Q)
        )
        self.assertFalse(
            bad_proof.is_valid(message, keypair.public_key, decryption, ONE_MOD_Q)
        )
コード例 #10
0
def r_encrypt(
    plaintext: int, nonce: ElementModQ, public_key: ElementModP
) -> ElGamalCiphertext:
    if not isinstance(public_key, ElementModP):
        # paranoid type checking, while still getting used to working with Ray
        print(f"expected ElementModP, got {str(type(public_key))}")

    return elgamal_encrypt(plaintext, nonce, public_key)
コード例 #11
0
 def test_djcp_proof_one(self, keypair: ElGamalKeyPair, nonce: ElementModQ,
                         seed: ElementModQ):
     message = get_optional(elgamal_encrypt(1, nonce, keypair.public_key))
     proof = make_disjunctive_chaum_pedersen_one(message, nonce,
                                                 keypair.public_key, seed)
     proof_bad = make_disjunctive_chaum_pedersen_zero(
         message, nonce, keypair.public_key, seed)
     self.assertTrue(proof.is_valid(message, keypair.public_key))
     self.assertFalse(proof_bad.is_valid(message, keypair.public_key))
コード例 #12
0
 def test_ccp_proofs_simple_encryption_of_one(self):
     keypair = elgamal_keypair_from_secret(TWO_MOD_Q)
     nonce = ONE_MOD_Q
     seed = TWO_MOD_Q
     message = get_optional(elgamal_encrypt(1, nonce, keypair.public_key))
     proof = make_constant_chaum_pedersen(message, 1, nonce,
                                          keypair.public_key, seed)
     bad_proof = make_constant_chaum_pedersen(message, 0, nonce,
                                              keypair.public_key, seed)
     self.assertTrue(proof.is_valid(message, keypair.public_key))
     self.assertFalse(bad_proof.is_valid(message, keypair.public_key))
コード例 #13
0
 def test_reencryption(
     self,
     plaintext: int,
     nonce1: ElementModQ,
     nonce2: ElementModQ,
     keypair: ElGamalKeyPair,
 ) -> None:
     c1 = get_optional(elgamal_encrypt(plaintext, nonce1, keypair.public_key))
     c2 = get_optional(elgamal_reencrypt(keypair.public_key, nonce2, c1))
     self.assertEqual(plaintext, c1.decrypt(keypair.secret_key))
     self.assertNotEqual(c1, c2)
     self.assertEqual(plaintext, c2.decrypt(keypair.secret_key))
コード例 #14
0
ファイル: test_ray_reduce.py プロジェクト: nealmcb/arlo-e2e
def r_encrypt(
    progressbar_actor: Optional[ActorHandle],
    plaintext: int,
    nonce: ElementModQ,
    public_key: ElementModP,
) -> ElGamalCiphertext:
    if not isinstance(public_key, ElementModP):
        # paranoid type checking, while still getting used to working with Ray
        print(f"expected ElementModP, got {str(type(public_key))}")

    if progressbar_actor:
        progressbar_actor.update_completed.remote("Ballots", 1)
    return elgamal_encrypt(plaintext, nonce, public_key)
コード例 #15
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
コード例 #16
0
 def test_cp_proofs_simple(self):
     keypair = elgamal_keypair_from_secret(TWO_MOD_Q)
     nonce = ONE_MOD_Q
     seed = TWO_MOD_Q
     message = get_optional(elgamal_encrypt(0, nonce, keypair.public_key))
     decryption = message.partial_decrypt(keypair.secret_key)
     proof = make_chaum_pedersen(message, keypair.secret_key, decryption,
                                 seed, ONE_MOD_Q)
     bad_proof = make_chaum_pedersen(message, keypair.secret_key, TWO_MOD_Q,
                                     seed, ONE_MOD_Q)
     self.assertTrue(
         proof.is_valid(message, keypair.public_key, decryption, ONE_MOD_Q))
     self.assertFalse(
         bad_proof.is_valid(message, keypair.public_key, decryption,
                            ONE_MOD_Q))
コード例 #17
0
 def test_djcp_proof_invalid_inputs(self):
     # this is here to push up our coverage
     keypair = elgamal_keypair_from_secret(TWO_MOD_Q)
     nonce = ONE_MOD_Q
     seed = TWO_MOD_Q
     message0 = get_optional(elgamal_encrypt(0, nonce, keypair.public_key))
     self.assertRaises(
         Exception,
         make_disjunctive_chaum_pedersen,
         message0,
         nonce,
         keypair.public_key,
         seed,
         3,
     )
コード例 #18
0
def chaum_pedersen_bench(bi: BenchInput) -> Tuple[float, float]:
    """
    Given an input (instance of the BenchInput tuple), constructs and validates
    a disjunctive Chaum-Pedersen proof, returning the time (in seconds) to do each operation.
    """
    (keypair, r, s) = bi
    ciphertext = get_optional(elgamal_encrypt(0, r, keypair.public_key))
    start1 = timer()
    proof = make_disjunctive_chaum_pedersen_zero(ciphertext, r,
                                                 keypair.public_key, s)
    end1 = timer()
    valid = proof.is_valid(ciphertext, keypair.public_key)
    end2 = timer()
    if not valid:
        raise Exception(
            "Wasn't expecting an invalid proof during a benchmark!")
    return end1 - start1, end2 - end1
コード例 #19
0
    def test_accumulation_encryption_decryption(
        self,
        everything: ELECTIONS_AND_BALLOTS_TUPLE_TYPE,
        nonce: ElementModQ,
    ):
        """
        Tests that decryption is the inverse of encryption over arbitrarily generated elections and ballots.

        This test uses an abitrarily generated dataset with a single public-private keypair for the election
        encryption context.  It also manually verifies that homomorphic accumulation works as expected.
        """
        # Arrange
        election_description, metadata, ballots, secret_key, context = everything

        # Tally the plaintext ballots for comparison later
        plaintext_tallies = accumulate_plaintext_ballots(ballots)
        num_ballots = len(ballots)
        num_contests = len(metadata.contests)
        zero_nonce, *nonces = Nonces(nonce)[:num_ballots + 1]
        self.assertEqual(len(nonces), num_ballots)
        self.assertTrue(len(metadata.contests) > 0)

        # Generatea valid encryption of zero
        encrypted_zero = elgamal_encrypt(0, zero_nonce,
                                         context.elgamal_public_key)

        # Act
        encrypted_ballots = []

        # encrypt each ballot
        for i in range(num_ballots):
            encrypted_ballot = encrypt_ballot(ballots[i], metadata, context,
                                              SEED_HASH, nonces[i])
            encrypted_ballots.append(encrypted_ballot)

            # sanity check the encryption
            self.assertIsNotNone(encrypted_ballot)
            self.assertEqual(num_contests, len(encrypted_ballot.contests))

            # decrypt the ballot with secret and verify it matches the plaintext
            decrypted_ballot = decrypt_ballot_with_secret(
                ballot=encrypted_ballot,
                election_metadata=metadata,
                crypto_extended_base_hash=context.crypto_extended_base_hash,
                public_key=context.elgamal_public_key,
                secret_key=secret_key,
                remove_placeholders=True,
            )
            self.assertEqual(ballots[i], decrypted_ballot)

        # homomorphically accumualte the encrypted ballot representations
        encrypted_tallies = _accumulate_encrypted_ballots(
            encrypted_zero, encrypted_ballots)

        decrypted_tallies = {}
        for object_id in encrypted_tallies.keys():
            decrypted_tallies[object_id] = encrypted_tallies[
                object_id].decrypt(secret_key)

        # loop through the contest descriptions and verify
        # the decrypted tallies match the plaintext tallies
        for contest in metadata.contests:
            # Sanity check the generated data
            self.assertTrue(len(contest.ballot_selections) > 0)
            self.assertTrue(len(contest.placeholder_selections) > 0)

            decrypted_selection_tallies = [
                decrypted_tallies[selection.object_id]
                for selection in contest.ballot_selections
            ]
            decrypted_placeholder_tallies = [
                decrypted_tallies[placeholder.object_id]
                for placeholder in contest.placeholder_selections
            ]
            plaintext_tally_values = [
                plaintext_tallies[selection.object_id]
                for selection in contest.ballot_selections
            ]

            # verify the plaintext tallies match the decrypted tallies
            self.assertEqual(decrypted_selection_tallies,
                             plaintext_tally_values)

            # validate the right number of selections including placeholders across all ballots
            self.assertEqual(
                contest.number_elected * num_ballots,
                sum(decrypted_selection_tallies) +
                sum(decrypted_placeholder_tallies),
            )
コード例 #20
0
 def test_elgamal_encrypt_requires_nonzero_nonce(self, message: int,
                                                 keypair: ElGamalKeyPair):
     self.assertEqual(
         None, elgamal_encrypt(message, ZERO_MOD_Q, keypair.public_key))
コード例 #21
0
ファイル: tally.py プロジェクト: nealmcb/arlo-e2e
def fast_tally_everything(
    cvrs: DominionCSV,
    pool: Optional[Pool] = None,
    verbose: bool = True,
    date: Optional[datetime] = None,
    seed_hash: Optional[ElementModQ] = None,
    master_nonce: Optional[ElementModQ] = None,
    secret_key: Optional[ElementModQ] = None,
    use_progressbar: bool = True,
) -> FastTallyEverythingResults:
    """
    This top-level function takes a collection of Dominion CVRs and produces everything that
    we might want for arlo-e2e: a list of encrypted ballots, their encrypted and decrypted tally,
    and proofs of the correctness of the whole thing. The election `secret_key` is an optional
    parameter. If absent, a random keypair is generated and used. Similarly, if a `seed_hash` or
    `master_nonce` is not provided, random ones are generated and used.

    For parallelism, a `multiprocessing.pool.Pool` may be provided, and should result in significant
    speedups on multicore computers. If absent, the computation will proceed sequentially.
    """
    rows, cols = cvrs.data.shape

    if date is None:
        date = datetime.now()

    parse_time = timer()
    log_and_print(f"Rows: {rows}, cols: {cols}", verbose)

    ed, ballots, id_map = cvrs.to_election_description(date=date)
    assert len(ballots) > 0, "can't have zero ballots!"

    keypair = (elgamal_keypair_random() if secret_key is None else
               elgamal_keypair_from_secret(secret_key))
    assert keypair is not None, "unexpected failure with keypair computation"
    secret_key, public_key = keypair

    # This computation exists only to cause side-effects in the DLog engine, so the lame nonce is not an issue.
    assert len(ballots) == get_optional(
        elgamal_encrypt(m=len(ballots),
                        nonce=int_to_q_unchecked(3),
                        public_key=public_key)).decrypt(
                            secret_key), "got wrong ElGamal decryption!"

    dlog_prime_time = timer()
    log_and_print(
        f"DLog prime time (n={len(ballots)}): {dlog_prime_time - parse_time: .3f} sec",
        verbose,
    )

    cec = make_ciphertext_election_context(
        number_of_guardians=1,
        quorum=1,
        elgamal_public_key=public_key,
        description_hash=ed.crypto_hash(),
    )

    ied = InternalElectionDescription(ed)

    # REVIEW THIS: is this cryptographically sound? Is the seed_hash properly a secret? Should
    # it go in the output? The nonces are clearly secret. If you know them, you can decrypt.
    if seed_hash is None:
        seed_hash = rand_q()
    if master_nonce is None:
        master_nonce = rand_q()
    nonces: List[ElementModQ] = Nonces(master_nonce)[0:len(ballots)]

    # even if verbose is false, we still want to see the progress bar for the encryption
    cballots = fast_encrypt_ballots(ballots,
                                    ied,
                                    cec,
                                    seed_hash,
                                    nonces,
                                    pool,
                                    use_progressbar=use_progressbar)
    eg_encrypt_time = timer()

    log_and_print(
        f"Encryption time: {eg_encrypt_time - dlog_prime_time: .3f} sec",
        verbose)
    log_and_print(
        f"Encryption rate: {rows / (eg_encrypt_time - dlog_prime_time): .3f} ballot/sec",
        verbose,
    )

    tally: TALLY_TYPE = fast_tally_ballots(cballots, pool)
    eg_tabulate_time = timer()

    log_and_print(
        f"Tabulation time: {eg_tabulate_time - eg_encrypt_time: .3f} sec",
        verbose)
    log_and_print(
        f"Tabulation rate: {rows / (eg_tabulate_time - eg_encrypt_time): .3f} ballot/sec",
        verbose,
    )
    log_and_print(
        f"Encryption and tabulation: {rows} ballots / {eg_tabulate_time - dlog_prime_time: .3f} sec = {rows / (eg_tabulate_time - dlog_prime_time): .3f} ballot/sec",
        verbose,
    )

    assert tally is not None, "tally failed!"

    if verbose:  # pragma: no cover
        print("Decryption & Proofs: ")
    decrypted_tally: DECRYPT_TALLY_OUTPUT_TYPE = fast_decrypt_tally(
        tally, cec, keypair, seed_hash, pool, verbose)
    eg_decryption_time = timer()
    log_and_print(
        f"Decryption time: {eg_decryption_time - eg_tabulate_time: .3f} sec",
        verbose)
    log_and_print(
        f"Decryption rate: {len(decrypted_tally.keys()) / (eg_decryption_time - eg_tabulate_time): .3f} selection/sec",
        verbose,
    )

    # Sanity-checking logic: make sure we don't have any unexpected keys, and that the decrypted totals
    # match up with the columns in the original plaintext data.
    for obj_id in decrypted_tally.keys():
        assert obj_id in id_map, "object_id in results that we don't know about!"
        cvr_sum = int(cvrs.data[id_map[obj_id]].sum())
        decryption, proof = decrypted_tally[obj_id]
        assert cvr_sum == decryption, f"decryption failed for {obj_id}"

    # Assemble the data structure that we're returning. Having nonces in the ciphertext makes these
    # structures sensitive for writing out to disk, but otherwise they're ready to go.
    reported_tally: Dict[str, SelectionInfo] = {
        k: SelectionInfo(
            object_id=k,
            encrypted_tally=tally[k],
            # we need to forcibly convert mpz to int here to make serialization work properly
            decrypted_tally=int(decrypted_tally[k][0]),
            proof=decrypted_tally[k][1],
        )
        for k in tally.keys()
    }

    # strips the ballots of their nonces, which is important because those could allow for decryption
    accepted_ballots = [ciphertext_ballot_to_accepted(x) for x in cballots]

    return FastTallyEverythingResults(
        metadata=cvrs.metadata,
        cvr_metadata=cvrs.dataframe_without_selections(),
        election_description=ed,
        encrypted_ballot_memos={
            ballot.object_id: make_memo_value(ballot)
            for ballot in accepted_ballots
        },
        tally=SelectionTally(reported_tally),
        context=cec,
    )