Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚧 #355 Guardian Reconstruction #608

Merged
merged 13 commits into from
Apr 26, 2022
17 changes: 10 additions & 7 deletions docs/1_Key_Ceremony.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
# Key Ceremony

The ElectionGuard Key Ceremony is the process used by Election Officials to share encryption keys for an election. Before an election, a fixed number of Guardians are selection to hold the private keys needed to decrypt the election results. A Quorum count of Guardians can also be specified to compensate for guardians who may be missing at the time of Decryption. For instance, 5 Guardians may be selected to hold the keys, but only 3 of them are required to decrypt the election results.
The ElectionGuard Key Ceremony is the process used by Election Officials to share encryption keys for an election. Before an election, a fixed number of Guardians are selection to hold the private keys needed to decrypt the election results. A Quorum count of Guardians can also be specified to compensate for guardians who may be missing at the time of Decryption. For instance, 5 Guardians may be selected to hold the keys, but only 3 of them are required to decrypt the election results.

Guardians are typically Election Officials, Trustees Canvass Board Members, Government Officials or other trusted authorities who are responsible and accountable for conducting the election.

## Summary

The Key Ceremony is broken into several high-level steps. Each Guardian must _announce_ their _attendance_ in the key ceremony, generate their own public-private key pairs, and then _share_ those key pairs with the Quorum. Then the data that is shared is mathematically verified using Non-Interactive Zero Knowledge Proofs, and finally a _joint public key_ is created to encrypt ballots in the election.
The Key Ceremony is broken into several high-level steps. Each Guardian must _announce_ their _attendance_ in the key ceremony, generate their own public-private key pairs, and then _share_ those key pairs with the Quorum. Then the data that is shared is mathematically verified using Non-Interactive Zero Knowledge Proofs, and finally a _joint public key_ is created to encrypt ballots in the election.

### Attendance

Guardians exchange all public keys and ensure each fellow guardian has received an election public key ensuring at all guardians are in attendance.

### Key Sharing
Guardians generate a partial key backup for each guardian and share with that designated key with that guardian. Then each designated guardian sends a verification back to the sender. The sender then publishes to the group when all verifications are received.

Guardians generate a partial key backup for each guardian and share with that designated key with that guardian. Then each designated guardian sends a verification back to the sender. The sender then publishes to the group when all verifications are received.

### Joint Key
The final step is to publish the joint election key after all keys and backups have been shared.

The final step is to publish the joint election key after all keys and backups have been shared.

## Glossary

Expand All @@ -36,7 +39,7 @@ This is a detailed description of the entire Key Ceremony Process
3. Each guardian must generate their `election key pair` _(ElGamal key pair)_. This will generate a corresponding Schnorr `proof` and `polynomial` used for generating `election partial key backups` for sharing.
4. Each guardian must give the other guardians their `election public key` directly or through a mediator.
5. Each guardian must check if all `election public keys` are received.
6. Each guardian must generate `election partial key backup` for each other guardian. The guardian will use their `polynomial` and the designated guardian's `sequence_order` to create the value.
6. Each guardian must generate `election partial key backup` for each other guardian. The guardian will use their `polynomial` and the designated guardian's `sequence_order` to create the value.
7. Each guardian must send each encrypted `election partial key backup` to the designated guardian directly or through a `mediator`.
8. Each guardian checks if all encrypted `election partial key backups` have been received by their recipient guardian directly or through a mediator.
9. Each recipient guardian decrypts each received encrypted `election partial key backup`
Expand Down Expand Up @@ -70,7 +73,7 @@ guardians: List[Guardian]
# Setup Guardians
for i in range(NUMBER_OF_GUARDIANS):
guardians.append(
Guardian(f"some_guardian_id_{str(i)}", i, NUMBER_OF_GUARDIANS, QUORUM)
Guardian.from_nonce(f"some_guardian_id_{str(i)}", i, NUMBER_OF_GUARDIANS, QUORUM)
)

mediator = KeyCeremonyMediator(details)
Expand All @@ -92,4 +95,4 @@ joint_public_key = mediator.publish_joint_key()

## Implementation Considerations

ElectionGuard can be run without the key ceremony. The key ceremony is the recommended process to generate keys for live end-to-end verifiable elections, however this process may not be necessary for other use cases such as privacy preserving risk limiting audits.
ElectionGuard can be run without the key ceremony. The key ceremony is the recommended process to generate keys for live end-to-end verifiable elections, however this process may not be necessary for other use cases such as privacy preserving risk limiting audits.
116 changes: 72 additions & 44 deletions src/electionguard/guardian.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# pylint: disable=too-many-public-methods
# pylint: disable=too-many-instance-attributes

from dataclasses import dataclass
from typing import Dict, List, Optional, TypeVar

Expand Down Expand Up @@ -110,7 +110,6 @@ class PrivateGuardianRecord:
"""Verifications of other guardian's backups"""


# pylint: disable=too-many-instance-attributes
class Guardian:
"""
Guardian of election responsible for safeguarding information and decrypting results.
Expand All @@ -119,11 +118,8 @@ class Guardian:
The second half relates to the decryption process.
"""

id: str
sequence_order: int # Cannot be zero
ceremony_details: CeremonyDetails

_election_keys: ElectionKeyPair
ceremony_details: CeremonyDetails

_backups_to_share: Dict[GuardianId, ElectionPartialKeyBackup]
"""
Expand All @@ -150,45 +146,84 @@ class Guardian:

def __init__(
self,
id: str,
sequence_order: int,
number_of_guardians: int,
quorum: int,
nonce_seed: Optional[ElementModQ] = None,
key_pair: ElectionKeyPair,
ceremony_details: CeremonyDetails,
election_public_keys: Dict[GuardianId, ElectionPublicKey] = None,
partial_key_backups: Dict[GuardianId, ElectionPartialKeyBackup] = None,
backups_to_share: Dict[GuardianId, ElectionPartialKeyBackup] = None,
guardian_election_partial_key_verifications: Dict[
GuardianId, ElectionPartialKeyVerification
] = None,
) -> None:
"""
Initialize a guardian with the specified arguments.

:param id: the unique identifier for the guardian
:param sequence_order: a unique number in [1, 256) that identifies this guardian
:param number_of_guardians: the total number of guardians that will participate in the election
:param quorum: the count of guardians necessary to decrypt
:param nonce_seed: an optional `ElementModQ` value that can be used to generate the `ElectionKeyPair`.
It is recommended to only use this field for testing.
:param key_pair The key pair the guardian generated during a key ceremony
:param ceremony_details The details of the key ceremony
:param election_public_keys the public keys the guardian generated during a key ceremony
:param partial_key_backups the partial key backups the guardian generated during a key ceremony
"""
self.id = id
self.sequence_order = sequence_order
self.set_ceremony_details(number_of_guardians, quorum)
self._backups_to_share = {}
self._guardian_election_public_keys = {}
self._guardian_election_partial_key_backups = {}
self._guardian_election_partial_key_verifications = {}

self.generate_election_key_pair(nonce_seed if nonce_seed is not None else None)
self._election_keys = key_pair
self.ceremony_details = ceremony_details

def reset(self, number_of_guardians: int, quorum: int) -> None:
"""
Reset guardian to initial state.
# Reduce this ⬇️
self._backups_to_share = {} if backups_to_share is None else backups_to_share
self._guardian_election_public_keys = (
{} if election_public_keys is None else election_public_keys
)
self._guardian_election_partial_key_backups = (
{} if partial_key_backups is None else partial_key_backups
)
self._guardian_election_partial_key_verifications = (
{}
if guardian_election_partial_key_verifications is None
else guardian_election_partial_key_verifications
)

:param number_of_guardians: Number of guardians in election
:param quorum: Quorum of guardians required to decrypt
"""
self._backups_to_share.clear()
self._guardian_election_public_keys.clear()
self._guardian_election_partial_key_backups.clear()
self._guardian_election_partial_key_verifications.clear()
self.set_ceremony_details(number_of_guardians, quorum)
self.generate_election_key_pair()
self.save_guardian_key(key_pair.share())

@property
def id(self) -> GuardianId:
return self._election_keys.owner_id

@property
def sequence_order(self) -> int:
return self._election_keys.sequence_order

@classmethod
def from_nonce(
cls,
id: str,
sequence_order: int,
number_of_guardians: int,
quorum: int,
nonce: ElementModQ = None,
) -> "Guardian":
"""Creates a guardian with an `ElementModQ` value that will be used to generate
the `ElectionKeyPair`. If no nonce provided, this will be generated automatically.
This method should generally only be used for testing."""
key_pair = generate_election_key_pair(id, sequence_order, quorum, nonce)
ceremony_details = CeremonyDetails(number_of_guardians, quorum)
return cls(key_pair, ceremony_details)

@classmethod
def from_private_record(
cls,
private_guardian_record: PrivateGuardianRecord,
number_of_guardians: int,
quorum: int,
) -> "Guardian":
guardian = cls(
private_guardian_record.election_keys,
lprichar marked this conversation as resolved.
Show resolved Hide resolved
CeremonyDetails(number_of_guardians, quorum),
private_guardian_record.guardian_election_public_keys,
private_guardian_record.guardian_election_partial_key_backups,
private_guardian_record.backups_to_share,
private_guardian_record.guardian_election_partial_key_verifications,
)

return guardian

def publish(self) -> GuardianRecord:
"""Publish record of guardian with all required information."""
Expand All @@ -215,13 +250,6 @@ def set_ceremony_details(self, number_of_guardians: int, quorum: int) -> None:
self.ceremony_details = CeremonyDetails(number_of_guardians, quorum)

# Public Keys
def generate_election_key_pair(self, nonce: ElementModQ = None) -> None:
"""Generate election key pair for encrypting/decrypting election."""
self._election_keys = generate_election_key_pair(
self.id, self.sequence_order, self.ceremony_details.quorum, nonce
)
self.save_guardian_key(self.share_key())

def share_key(self) -> ElectionPublicKey:
"""
Share election public key with another guardian.
Expand Down
2 changes: 1 addition & 1 deletion src/electionguard_cli/e2e_steps/input_retrieval_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def _get_guardians(number_of_guardians: int, quorum: int) -> List[Guardian]:
guardians: List[Guardian] = []
for i in range(number_of_guardians):
guardians.append(
Guardian(
Guardian.from_nonce(
str(i + 1),
i + 1,
number_of_guardians,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class KeyCeremonyOrchestrator:
@staticmethod
def create_guardians(ceremony_details: CeremonyDetails) -> List[Guardian]:
return [
Guardian(
Guardian.from_nonce(
str(i + 1),
i + 1,
ceremony_details.number_of_guardians,
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/test_end_to_end_election.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ def step_1_key_ceremony(self) -> None:
# Setup Guardians
for i in range(self.NUMBER_OF_GUARDIANS):
self.guardians.append(
Guardian(
Guardian.from_nonce(
str(i + 1),
i + 1,
self.NUMBER_OF_GUARDIANS,
Expand Down
Loading