Exemplo n.º 1
0
class TestNonces(unittest.TestCase):
    @given(elements_mod_q())
    def test_nonces_iterable(self, seed: ElementModQ):
        n = Nonces(seed)
        i = iter(n)
        q0 = next(i)
        q1 = next(i)
        self.assertTrue(q0 != q1)

    @given(elements_mod_q(), integers(min_value=0, max_value=1000000))
    def test_nonces_deterministic(self, seed: ElementModQ, i: int):
        n1 = Nonces(seed)
        n2 = Nonces(seed)
        self.assertEqual(n1[i], n2[i])

    @given(
        elements_mod_q(),
        elements_mod_q(),
        integers(min_value=0, max_value=1000000),
    )
    def test_nonces_seed_matters(self, seed1: ElementModQ, seed2: ElementModQ,
                                 i: int):
        assume(seed1 != seed2)
        n1 = Nonces(seed1)
        n2 = Nonces(seed2)
        self.assertNotEqual(n1[i], n2[i])

    @given(elements_mod_q())
    def test_nonces_with_slices(self, seed: ElementModQ):
        n = Nonces(seed)
        count: int = 0
        l: List[ElementModQ] = []

        for i in iter(n):
            count += 1
            l.append(i)
            if count == 10:
                break
        self.assertEqual(len(l), 10)

        l2 = Nonces(seed)[0:10]
        self.assertEqual(len(l2), 10)
        self.assertEqual(l, l2)

    def test_nonces_type_errors(self):
        n = Nonces(int_to_q_unchecked(3))
        self.assertRaises(TypeError, len, n)
        self.assertRaises(TypeError, lambda: n[1:])
        self.assertRaises(TypeError, lambda: n.get_with_headers(-1))
Exemplo n.º 2
0
class TestChaumPedersen(TestCase):
    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)
        )

    @settings(
        deadline=timedelta(milliseconds=2000),
        suppress_health_check=[HealthCheck.too_slow],
        max_examples=10,
    )
    @given(
        elgamal_keypairs(),
        elements_mod_q_no_zero(),
        elements_mod_q(),
        integers(0, 100),
        integers(0, 100),
    )
    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)
        )
Exemplo n.º 3
0
class TestEquality(unittest.TestCase):
    @given(elements_mod_q(), elements_mod_q())
    def testPsNotEqualToQs(self, q: ElementModQ, q2: ElementModQ):
        p = int_to_p_unchecked(q.to_int())
        p2 = int_to_p_unchecked(q2.to_int())

        # same value should imply they're equal
        self.assertEqual(p, q)
        self.assertEqual(q, p)

        if q.to_int() != q2.to_int():
            # these are genuinely different numbers
            self.assertNotEqual(q, q2)
            self.assertNotEqual(p, p2)
            self.assertNotEqual(q, p2)
            self.assertNotEqual(p, q2)

        # of course, we're going to make sure that a number is equal to itself
        self.assertEqual(p, p)
        self.assertEqual(q, q)
class TestElections(unittest.TestCase):
    @settings(
        deadline=timedelta(milliseconds=2000),
        suppress_health_check=[HealthCheck.too_slow],
        max_examples=10,
    )
    @given(election_descriptions())
    def test_generators_yield_valid_output(self, ed: ElectionDescription):
        """
        Tests that our Hypothesis election strategies generate "valid" output, also exercises the full stack
        of `is_valid` methods.
        """

        self.assertTrue(ed.is_valid())

    @settings(
        deadline=timedelta(milliseconds=10000),
        suppress_health_check=[HealthCheck.too_slow],
        max_examples=5,
        # disabling the "shrink" phase, because it runs very slowly
        phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],
    )
    @given(
        integers(1, 3).flatmap(lambda n: elections_and_ballots(n)),
        elements_mod_q(),
    )
    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),
            )
Exemplo n.º 5
0
class TestDisjunctiveChaumPedersen(TestCase):
    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, seed
        )
        proof0bad = make_disjunctive_chaum_pedersen_one(
            message0, nonce, keypair.public_key, seed
        )
        self.assertTrue(proof0.is_valid(message0, keypair.public_key))
        self.assertFalse(proof0bad.is_valid(message0, keypair.public_key))

        message1 = get_optional(elgamal_encrypt(1, nonce, keypair.public_key))
        proof1 = make_disjunctive_chaum_pedersen_one(
            message1, nonce, keypair.public_key, seed
        )
        proof1bad = make_disjunctive_chaum_pedersen_zero(
            message1, nonce, keypair.public_key, seed
        )
        self.assertTrue(proof1.is_valid(message1, keypair.public_key))
        self.assertFalse(proof1bad.is_valid(message1, keypair.public_key))

    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,
        )

    @settings(
        deadline=timedelta(milliseconds=2000),
        suppress_health_check=[HealthCheck.too_slow],
        max_examples=10,
    )
    @given(elgamal_keypairs(), elements_mod_q_no_zero(), elements_mod_q())
    def test_djcp_proof_zero(
        self, keypair: ElGamalKeyPair, nonce: ElementModQ, seed: ElementModQ
    ):
        message = get_optional(elgamal_encrypt(0, nonce, keypair.public_key))
        proof = make_disjunctive_chaum_pedersen_zero(
            message, nonce, keypair.public_key, seed
        )
        proof_bad = make_disjunctive_chaum_pedersen_one(
            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))

    @settings(
        deadline=timedelta(milliseconds=2000),
        suppress_health_check=[HealthCheck.too_slow],
        max_examples=10,
    )
    @given(elgamal_keypairs(), elements_mod_q_no_zero(), elements_mod_q())
    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))

    @settings(
        deadline=timedelta(milliseconds=2000),
        suppress_health_check=[HealthCheck.too_slow],
        max_examples=10,
    )
    @given(elgamal_keypairs(), elements_mod_q_no_zero(), elements_mod_q())
    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))
Exemplo n.º 6
0
class TestConstantChaumPedersen(TestCase):
    def test_ccp_proofs_simple_encryption_of_zero(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))
        proof = make_constant_chaum_pedersen(
            message, 0, nonce, keypair.public_key, seed
        )
        bad_proof = make_constant_chaum_pedersen(
            message, 1, nonce, keypair.public_key, seed
        )
        self.assertTrue(proof.is_valid(message, keypair.public_key))
        self.assertFalse(bad_proof.is_valid(message, keypair.public_key))

    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))

    @settings(
        deadline=timedelta(milliseconds=2000),
        suppress_health_check=[HealthCheck.too_slow],
        max_examples=10,
    )
    @given(
        elgamal_keypairs(),
        elements_mod_q_no_zero(),
        elements_mod_q(),
        integers(0, 100),
        integers(0, 100),
    )
    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))
Exemplo n.º 7
0
class TestModularArithmetic(unittest.TestCase):
    @given(elements_mod_q())
    def test_add_q(self, q: ElementModQ):
        as_int = add_q(q, 1)
        as_elem = add_q(q, ElementModQ(1))
        self.assertEqual(as_int, as_elem)

    @given(elements_mod_q())
    def test_a_plus_bc_q(self, q: ElementModQ):
        as_int = a_plus_bc_q(q, 1, 1)
        as_elem = a_plus_bc_q(q, ElementModQ(1), ElementModQ(1))
        self.assertEqual(as_int, as_elem)

    @given(elements_mod_q())
    def test_a_minus_b_q(self, q: ElementModQ):
        as_int = a_minus_b_q(q, 1)
        as_elem = a_minus_b_q(q, ElementModQ(1))
        self.assertEqual(as_int, as_elem)

    @given(elements_mod_q())
    def test_div_q(self, q: ElementModQ):
        as_int = div_q(q, 1)
        as_elem = div_q(q, ElementModQ(1))
        self.assertEqual(as_int, as_elem)

    @given(elements_mod_p())
    def test_div_p(self, p: ElementModQ):
        as_int = div_p(p, 1)
        as_elem = div_p(p, ElementModP(1))
        self.assertEqual(as_int, as_elem)

    def test_no_mult_inv_of_zero(self):
        self.assertRaises(Exception, mult_inv_p, ZERO_MOD_P)

    @given(elements_mod_p_no_zero())
    def test_mult_inverses(self, elem: ElementModP):
        inv = mult_inv_p(elem)
        self.assertEqual(mult_p(elem, inv), ONE_MOD_P)

    @given(elements_mod_p())
    def test_mult_identity(self, elem: ElementModP):
        self.assertEqual(elem, mult_p(elem))

    def test_mult_noargs(self):
        self.assertEqual(ONE_MOD_P, mult_p())

    def test_add_noargs(self):
        self.assertEqual(ZERO_MOD_Q, add_q())

    def test_properties_for_constants(self):
        self.assertNotEqual(G, 1)
        self.assertEqual((R * Q) % P, P - 1)
        self.assertLess(Q, P)
        self.assertLess(G, P)
        self.assertLess(R, P)

    def test_simple_powers(self):
        gp = int_to_p(G)
        self.assertEqual(gp, g_pow_p(ONE_MOD_Q))
        self.assertEqual(ONE_MOD_P, g_pow_p(ZERO_MOD_Q))

    @given(elements_mod_q())
    def test_in_bounds_q(self, q: ElementModQ):
        self.assertTrue(q.is_in_bounds())
        too_big = q.to_int() + Q
        too_small = q.to_int() - Q
        self.assertFalse(int_to_q_unchecked(too_big).is_in_bounds())
        self.assertFalse(int_to_q_unchecked(too_small).is_in_bounds())
        self.assertEqual(None, int_to_q(too_big))
        self.assertEqual(None, int_to_q(too_small))

    @given(elements_mod_p())
    def test_in_bounds_p(self, p: ElementModP):
        self.assertTrue(p.is_in_bounds())
        too_big = p.to_int() + P
        too_small = p.to_int() - P
        self.assertFalse(int_to_p_unchecked(too_big).is_in_bounds())
        self.assertFalse(int_to_p_unchecked(too_small).is_in_bounds())
        self.assertEqual(None, int_to_p(too_big))
        self.assertEqual(None, int_to_p(too_small))

    @given(elements_mod_q_no_zero())
    def test_in_bounds_q_no_zero(self, q: ElementModQ):
        self.assertTrue(q.is_in_bounds_no_zero())
        self.assertFalse(ZERO_MOD_Q.is_in_bounds_no_zero())
        self.assertFalse(int_to_q_unchecked(q.to_int() + Q).is_in_bounds_no_zero())
        self.assertFalse(int_to_q_unchecked(q.to_int() - Q).is_in_bounds_no_zero())

    @given(elements_mod_p_no_zero())
    def test_in_bounds_p_no_zero(self, p: ElementModP):
        self.assertTrue(p.is_in_bounds_no_zero())
        self.assertFalse(ZERO_MOD_P.is_in_bounds_no_zero())
        self.assertFalse(int_to_p_unchecked(p.to_int() + P).is_in_bounds_no_zero())
        self.assertFalse(int_to_p_unchecked(p.to_int() - P).is_in_bounds_no_zero())

    @given(elements_mod_q())
    def test_large_values_rejected_by_int_to_q(self, q: ElementModQ):
        oversize = q.to_int() + Q
        self.assertEqual(None, int_to_q(oversize))
Exemplo n.º 8
0
class TestDominionHypotheses(unittest.TestCase):
    @given(
        integers(1, 3).flatmap(
            lambda i: dominion_cvrs(max_rows=50, max_votes_per_race=i)))
    @settings(
        deadline=timedelta(milliseconds=10000),
        suppress_health_check=[HealthCheck.too_slow],
        max_examples=5,
        # disabling the "shrink" phase, because it runs very slowly
        phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],
    )
    def test_max_votes_per_race_sanity(self, cvrs: str) -> None:
        parsed = read_dominion_csv(StringIO(cvrs))
        self.assertIsNotNone(parsed)

    @given(ballots_and_context(), elements_mod_q())
    @settings(
        deadline=timedelta(milliseconds=50000),
        suppress_health_check=[HealthCheck.too_slow],
        max_examples=5,
        # disabling the "shrink" phase, because it runs very slowly
        phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],
    )
    def test_eg_conversion(self, state: DominionBallotsAndContext,
                           seed: ElementModQ) -> None:
        ied = InternalElectionDescription(state.ed)
        ballot_box = BallotBox(ied, state.cec)

        seed_hash = EncryptionDevice("Location").get_hash()
        nonces = Nonces(seed)[0:len(state.ballots)]

        for b, n in zip(state.ballots, nonces):
            eb = encrypt_ballot(b, ied, state.cec, seed_hash, n)
            self.assertIsNotNone(eb)

            pb = decrypt_ballot_with_secret(
                eb,
                ied,
                state.cec.crypto_extended_base_hash,
                state.cec.elgamal_public_key,
                state.secret_key,
            )
            self.assertEqual(b, pb)

            self.assertGreater(len(eb.contests), 0)
            cast_result = ballot_box.cast(eb)
            self.assertIsNotNone(cast_result)

        tally = tally_ballots(ballot_box._store, ied, state.cec)
        self.assertIsNotNone(tally)
        results = decrypt_tally_with_secret(tally, state.secret_key)

        self.assertEqual(len(results.keys()), len(state.id_map.keys()))
        for obj_id in results.keys():
            self.assertIn(obj_id, state.id_map)
            cvr_sum = int(state.dominion_cvrs.data[state.id_map[obj_id]].sum())
            decryption = results[obj_id]
            self.assertEqual(cvr_sum, decryption)

    @given(dominion_cvrs(max_rows=10))
    @settings(
        deadline=timedelta(milliseconds=50000),
        suppress_health_check=[HealthCheck.too_slow],
        max_examples=5,
        # disabling the "shrink" phase, because it runs very slowly
        phases=[Phase.explicit, Phase.reuse, Phase.generate, Phase.target],
    )
    def test_csv_metadata_roundtrip(self, cvrs: str) -> None:
        parsed = read_dominion_csv(StringIO(cvrs))
        self.assertIsNotNone(parsed)

        original_metadata = parsed.dataframe_without_selections()
        csv_data = original_metadata.to_csv(index=False,
                                            quoting=csv.QUOTE_NONNUMERIC)
        reloaded_metadata = pd.read_csv(StringIO(csv_data))

        self.assertTrue(original_metadata.equals(reloaded_metadata))