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
52 changes: 38 additions & 14 deletions src/electionguard/guardian.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,20 +95,12 @@ class PrivateGuardianRecord:
election_keys: ElectionKeyPair
"""Private election Key pair of this guardian"""

backups_to_share: Dict[GuardianId, ElectionPartialKeyBackup]
lprichar marked this conversation as resolved.
Show resolved Hide resolved
"""This guardian's partial key backups that will be shared to other guardians"""

guardian_election_public_keys: Dict[GuardianId, ElectionPublicKey]
"""Received election public keys that are shared with this guardian"""

guardian_election_partial_key_backups: Dict[GuardianId, ElectionPartialKeyBackup]
"""Received partial key backups that are shared with this guardian"""

guardian_election_partial_key_verifications: Dict[
GuardianId, ElectionPartialKeyVerification
]
"""Verifications of other guardian's backups"""


# pylint: disable=too-many-instance-attributes
class Guardian:
Expand Down Expand Up @@ -154,6 +146,9 @@ def __init__(
sequence_order: int,
lprichar marked this conversation as resolved.
Show resolved Hide resolved
number_of_guardians: int,
quorum: int,
election_keys: ElectionKeyPair = None,
lprichar marked this conversation as resolved.
Show resolved Hide resolved
election_public_keys: Dict[GuardianId, ElectionPublicKey] = None,
partial_key_backups: Dict[GuardianId, ElectionPartialKeyBackup] = None,
nonce_seed: Optional[ElementModQ] = None,
) -> None:
"""
Expand All @@ -163,18 +158,31 @@ def __init__(
: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 election_keys the private keys the guardian generated during a 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
: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.
This parameter is mutually exclusive with election_keys, election_public_keys, and
partial_key_backups. It is recommended to only use this field for testing.
"""
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_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 = {}

self.generate_election_key_pair(nonce_seed if nonce_seed is not None else None)
if election_keys is None:
self.generate_election_key_pair(
nonce_seed if nonce_seed is not None else None
)
else:
self._election_keys = election_keys

def reset(self, number_of_guardians: int, quorum: int) -> None:
"""
Expand All @@ -199,12 +207,28 @@ def export_private_data(self) -> PrivateGuardianRecord:
return PrivateGuardianRecord(
self.id,
self._election_keys,
self._backups_to_share,
self._guardian_election_public_keys,
self._guardian_election_partial_key_backups,
self._guardian_election_partial_key_verifications,
)

@staticmethod
def from_private_record(
private_guardian_record: PrivateGuardianRecord,
number_of_guardians: int,
quorum: int,
) -> "Guardian":
guardian = Guardian(
private_guardian_record.guardian_id,
private_guardian_record.election_keys.sequence_order,
number_of_guardians,
quorum,
private_guardian_record.election_keys,
private_guardian_record.guardian_election_public_keys,
private_guardian_record.guardian_election_partial_key_backups,
)

return guardian

def set_ceremony_details(self, number_of_guardians: int, quorum: int) -> None:
"""
Set ceremony details for election.
Expand Down
24 changes: 17 additions & 7 deletions tests/unit/test_guardian.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,21 +23,31 @@
class TestGuardian(BaseTestCase):
"""Guardian tests"""

def test_reset(self) -> None:
guardian = Guardian(
def test_import_from_guardian_private_record(self) -> None:
# Arrange
guardian_expected = Guardian(
SENDER_GUARDIAN_ID, SENDER_SEQUENCE_ORDER, NUMBER_OF_GUARDIANS, QUORUM
)
expected_number_of_guardians = 10
expected_quorum = 4
private_guardian_record = guardian_expected.export_private_data()

# Act
guardian.reset(expected_number_of_guardians, expected_quorum)
guardian_actual = Guardian.from_private_record(
private_guardian_record, NUMBER_OF_GUARDIANS, QUORUM
)

# Assert
# pylint: disable=protected-access
self.assertEqual(
expected_number_of_guardians, guardian.ceremony_details.number_of_guardians
guardian_actual._election_keys, guardian_expected._election_keys
)
self.assertEqual(
guardian_actual._guardian_election_public_keys,
guardian_expected._guardian_election_public_keys,
)
self.assertEqual(
guardian_actual._guardian_election_partial_key_backups,
guardian_expected._guardian_election_partial_key_backups,
)
self.assertEqual(expected_quorum, guardian.ceremony_details.quorum)

def test_set_ceremony_details(self) -> None:
# Arrange
Expand Down