* Parameterize output path, add election record step

* Starting to gather info to publish an election record

* Expose the encryption device, it's required for election record

* Retrieve all the inputs for election record publishing

* Create election record

* Refactor election record and private data into separate directories

* Refactor to remove export_private_data

* Use output_file parameter for outputting election record

* Fix linting errors

* PR Feedback
This commit is contained in:
Lee Richardson 2022-04-13 11:31:41 -04:00 коммит произвёл GitHub
Родитель 4dfdb1a036
Коммит b7c8782e81
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
13 изменённых файлов: 159 добавлений и 28 удалений

Просмотреть файл

@ -252,3 +252,6 @@ release-notes:
echo -en "\n" >> release_notes.md
echo "## Issues" >> release_notes.md
curl "${GITHUB_API_URL}/${GITHUB_REPOSITORY}/issues?milestone=${MILESTONE_NUM}&state=all" | jq '.[].title' | while read i; do echo "[$i]($MILESTONE_URL)" >> release_notes.md; done
simple-election:
poetry run eg e2e --guardian-count=2 --quorum=2 --manifest=data/election_manifest_simple.json --ballots=data/plaintext_ballots_simple.json --spoil-id=25a7111b-4334-425a-87c1-f7a49f42b3a2 --output-file="./election_record.zip"

Просмотреть файл

@ -1 +0,0 @@
eg e2e --guardian-count=2 --quorum=2 --manifest=data/election_manifest_simple.json --ballots=data/plaintext_ballots_simple.json --spoil-id=25a7111b-4334-425a-87c1-f7a49f42b3a2

Просмотреть файл

@ -7,9 +7,11 @@ from electionguard_cli.cli_models import (
BuildElectionResults,
E2eDecryptResults,
E2eInputs,
E2eSubmitResults,
e2e_build_election_results,
e2e_decrypt_results,
e2e_inputs,
e2e_submit_results,
)
from electionguard_cli.commands import (
e2e,
@ -21,6 +23,7 @@ from electionguard_cli.e2e_steps import (
DecryptStep,
E2eStepBase,
ElectionBuilderStep,
ElectionRecordStep,
InputRetrievalStep,
KeyCeremonyStep,
PrintResultsStep,
@ -28,6 +31,7 @@ from electionguard_cli.e2e_steps import (
decrypt_step,
e2e_step_base,
election_builder_step,
election_record_step,
input_retrieval_step,
key_ceremony_step,
print_results_step,
@ -43,7 +47,9 @@ __all__ = [
"E2eDecryptResults",
"E2eInputs",
"E2eStepBase",
"E2eSubmitResults",
"ElectionBuilderStep",
"ElectionRecordStep",
"InputRetrievalStep",
"KeyCeremonyStep",
"PrintResultsStep",
@ -59,7 +65,9 @@ __all__ = [
"e2e_inputs",
"e2e_step_base",
"e2e_steps",
"e2e_submit_results",
"election_builder_step",
"election_record_step",
"hello",
"hello_command",
"input_retrieval_step",

Просмотреть файл

@ -1,6 +1,7 @@
from electionguard_cli.cli_models import e2e_build_election_results
from electionguard_cli.cli_models import e2e_decrypt_results
from electionguard_cli.cli_models import e2e_inputs
from electionguard_cli.cli_models import e2e_submit_results
from electionguard_cli.cli_models.e2e_build_election_results import (
BuildElectionResults,
@ -11,12 +12,17 @@ from electionguard_cli.cli_models.e2e_decrypt_results import (
from electionguard_cli.cli_models.e2e_inputs import (
E2eInputs,
)
from electionguard_cli.cli_models.e2e_submit_results import (
E2eSubmitResults,
)
__all__ = [
"BuildElectionResults",
"E2eDecryptResults",
"E2eInputs",
"E2eSubmitResults",
"e2e_build_election_results",
"e2e_decrypt_results",
"e2e_inputs",
"e2e_submit_results",
]

Просмотреть файл

@ -1,6 +1,7 @@
from typing import Dict
from electionguard.tally import PlaintextTally
from electionguard.tally import CiphertextTally, PlaintextTally
from electionguard.type import BallotId
from electionguard.election_polynomial import LagrangeCoefficientsRecord
class E2eDecryptResults:
@ -10,9 +11,15 @@ class E2eDecryptResults:
self,
plaintext_tally: PlaintextTally,
plaintext_spoiled_ballots: Dict[BallotId, PlaintextTally],
ciphertext_tally: CiphertextTally,
lagrange_coefficients: LagrangeCoefficientsRecord,
):
self.plaintext_tally = plaintext_tally
self.plaintext_spoiled_ballots = plaintext_spoiled_ballots
self.ciphertext_tally = ciphertext_tally
self.lagrange_coefficients = lagrange_coefficients
plaintext_tally: PlaintextTally
plaintext_spoiled_ballots: Dict[BallotId, PlaintextTally]
ciphertext_tally: CiphertextTally
lagrange_coefficients: LagrangeCoefficientsRecord

Просмотреть файл

@ -15,6 +15,7 @@ class E2eInputs:
manifest: Manifest,
ballots: List[PlaintextBallot],
spoil_id: str,
output_file: str,
):
self.guardian_count = guardian_count
self.quorum = quorum
@ -22,6 +23,7 @@ class E2eInputs:
self.manifest = manifest
self.ballots = ballots
self.spoil_id = spoil_id
self.output_file = output_file
guardian_count: int
quorum: int
@ -29,3 +31,4 @@ class E2eInputs:
manifest: Manifest
ballots: List[PlaintextBallot]
spoil_id: str
output_file: str

Просмотреть файл

@ -0,0 +1,22 @@
from typing import List
from electionguard.ballot import CiphertextBallot
from electionguard.data_store import DataStore
from electionguard.encrypt import EncryptionDevice
class E2eSubmitResults:
"""Responsible for holding the results of submitting votes in an election."""
def __init__(
self,
data_store: DataStore,
device: EncryptionDevice,
ciphertext_ballots: List[CiphertextBallot],
):
self.data_store = data_store
self.device = device
self.ciphertext_ballots = ciphertext_ballots
data_store: DataStore
device: EncryptionDevice
ciphertext_ballots: List[CiphertextBallot]

Просмотреть файл

@ -7,6 +7,7 @@ from ..e2e_steps import (
DecryptStep,
PrintResultsStep,
InputRetrievalStep,
ElectionRecordStep,
)
@ -38,23 +39,31 @@ from ..e2e_steps import (
@click.option(
"--spoil-id",
prompt="Object-id of ballot to spoil",
help="The object-id of a ballot within the ballots file to spoil",
help="The object-id of a ballot within the ballots file to spoil.",
type=click.STRING,
default=None,
prompt_required=False,
)
@click.option(
"--output-file",
help="A file name for saving an output election record (e.g. './election.zip')."
+ " If no value provided then an election record will not be generated.",
type=click.Path(exists=False),
default=None,
)
def e2e(
guardian_count: int,
quorum: int,
manifest: TextIOWrapper,
ballots: TextIOWrapper,
spoil_id: str,
output_file: str,
) -> None:
"""Runs through an end-to-end election."""
# get user inputs
election_inputs = InputRetrievalStep().get_inputs(
guardian_count, quorum, manifest, ballots, spoil_id
guardian_count, quorum, manifest, ballots, spoil_id, output_file
)
# perform election
@ -62,12 +71,17 @@ def e2e(
build_election_results = ElectionBuilderStep().build_election(
election_inputs, joint_key
)
ballot_store = SubmitVotesStep().submit_votes(
submit_results = SubmitVotesStep().submit_votes(
election_inputs, build_election_results
)
decrypt_results = DecryptStep().decrypt_tally(
ballot_store, election_inputs.guardians, build_election_results
submit_results.data_store, election_inputs.guardians, build_election_results
)
# print results
PrintResultsStep().print_election_results(election_inputs, decrypt_results)
# publish election record
ElectionRecordStep().run(
election_inputs, build_election_results, submit_results, decrypt_results
)

Просмотреть файл

@ -1,6 +1,7 @@
from electionguard_cli.e2e_steps import decrypt_step
from electionguard_cli.e2e_steps import e2e_step_base
from electionguard_cli.e2e_steps import election_builder_step
from electionguard_cli.e2e_steps import election_record_step
from electionguard_cli.e2e_steps import input_retrieval_step
from electionguard_cli.e2e_steps import key_ceremony_step
from electionguard_cli.e2e_steps import print_results_step
@ -15,6 +16,9 @@ from electionguard_cli.e2e_steps.e2e_step_base import (
from electionguard_cli.e2e_steps.election_builder_step import (
ElectionBuilderStep,
)
from electionguard_cli.e2e_steps.election_record_step import (
ElectionRecordStep,
)
from electionguard_cli.e2e_steps.input_retrieval_step import (
InputRetrievalStep,
)
@ -32,6 +36,7 @@ __all__ = [
"DecryptStep",
"E2eStepBase",
"ElectionBuilderStep",
"ElectionRecordStep",
"InputRetrievalStep",
"KeyCeremonyStep",
"PrintResultsStep",
@ -39,6 +44,7 @@ __all__ = [
"decrypt_step",
"e2e_step_base",
"election_builder_step",
"election_record_step",
"input_retrieval_step",
"key_ceremony_step",
"print_results_step",

Просмотреть файл

@ -18,14 +18,15 @@ from .e2e_step_base import E2eStepBase
class DecryptStep(E2eStepBase):
"""Responsible for decrypting a tally and/or cast ballots"""
def _print_lagrange_coefficients(
def _get_lagrange_coefficients(
self, decryption_mediator: DecryptionMediator
) -> None:
) -> LagrangeCoefficientsRecord:
lagrange_coefficients = LagrangeCoefficientsRecord(
decryption_mediator.get_lagrange_coefficients()
)
coefficient_count = len(lagrange_coefficients.coefficients)
self.print_value("Lagrange coefficients retrieved", coefficient_count)
return lagrange_coefficients
def decrypt_tally(
self,
@ -35,33 +36,40 @@ class DecryptStep(E2eStepBase):
) -> E2eDecryptResults:
self.print_header("Decrypting tally")
tally = _get_tally(ballot_store, build_election_results)
ciphertext_tally = _get_tally(ballot_store, build_election_results)
spoiled_ballots = _get_spoiled_ballots(ballot_store)
decryption_mediator = _get_decryption_mediator(build_election_results)
context = build_election_results.context
self.print_value("Cast ballots", tally.cast())
self.print_value("Spoiled ballots", tally.spoiled())
self.print_value("Total ballots", len(tally))
self.print_value("Cast ballots", ciphertext_tally.cast())
self.print_value("Spoiled ballots", ciphertext_tally.spoiled())
self.print_value("Total ballots", len(ciphertext_tally))
count = 0
for guardian in guardians:
guardian_key = guardian.share_key()
tally_share = _compute_tally_share(guardian, tally, context)
tally_share = _compute_tally_share(guardian, ciphertext_tally, context)
ballot_shares = guardian.compute_ballot_shares(spoiled_ballots, context)
decryption_mediator.announce(guardian_key, tally_share, ballot_shares)
count += 1
click.echo(f"Guardian Present: {guardian.id}")
self._print_lagrange_coefficients(decryption_mediator)
lagrange_coefficients = self._get_lagrange_coefficients(decryption_mediator)
plaintext_tally = get_optional(decryption_mediator.get_plaintext_tally(tally))
plaintext_tally = get_optional(
decryption_mediator.get_plaintext_tally(ciphertext_tally)
)
plaintext_spoiled_ballots = get_optional(
decryption_mediator.get_plaintext_ballots(spoiled_ballots)
)
return E2eDecryptResults(plaintext_tally, plaintext_spoiled_ballots)
return E2eDecryptResults(
plaintext_tally,
plaintext_spoiled_ballots,
ciphertext_tally,
lagrange_coefficients,
)
def _compute_tally_share(

Просмотреть файл

@ -0,0 +1,50 @@
from shutil import make_archive
from os.path import splitext
from tempfile import TemporaryDirectory
from click import echo
from electionguard_cli.cli_models import BuildElectionResults, E2eSubmitResults
from electionguard_cli.cli_models.e2e_decrypt_results import E2eDecryptResults
from electionguard_cli.cli_models.e2e_inputs import E2eInputs
from electionguard_cli.e2e_steps.e2e_step_base import E2eStepBase
from electionguard.constants import get_constants
from electionguard_tools.helpers.export import export_record
class ElectionRecordStep(E2eStepBase):
"""Responsible for publishing an election record after an election has completed."""
_COMPRESSION_FORMAT = "zip"
def run(
self,
election_inputs: E2eInputs,
build_election_results: BuildElectionResults,
submit_results: E2eSubmitResults,
decrypt_results: E2eDecryptResults,
) -> None:
self.print_header("Election Record")
guardian_records = [
guardian.publish() for guardian in election_inputs.guardians
]
constants = get_constants()
with TemporaryDirectory() as temp_dir:
export_record(
election_inputs.manifest,
build_election_results.context,
constants,
[submit_results.device],
submit_results.data_store.all(),
decrypt_results.plaintext_spoiled_ballots.values(),
decrypt_results.ciphertext_tally.publish(),
decrypt_results.plaintext_tally,
guardian_records,
decrypt_results.lagrange_coefficients,
election_record_directory=temp_dir,
)
file_name = splitext(election_inputs.output_file)[0]
make_archive(file_name, self._COMPRESSION_FORMAT, temp_dir)
echo(
f"Successfully exported election record to '{election_inputs.output_file}'"
)

Просмотреть файл

@ -20,6 +20,7 @@ class InputRetrievalStep(E2eStepBase):
manifest_file: TextIOWrapper,
ballots_file: TextIOWrapper,
spoil_id: str,
output_file: str,
) -> E2eInputs:
self.print_header("Retrieving Inputs")
guardians = InputRetrievalStep._get_guardians(guardian_count, quorum)
@ -27,7 +28,9 @@ class InputRetrievalStep(E2eStepBase):
ballots = InputRetrievalStep._get_ballots(ballots_file)
self.print_value("Guardians", guardian_count)
self.print_value("Quorum", quorum)
return E2eInputs(guardian_count, quorum, guardians, manifest, ballots, spoil_id)
return E2eInputs(
guardian_count, quorum, guardians, manifest, ballots, spoil_id, output_file
)
@staticmethod
def _get_ballots(ballots_file: TextIOWrapper) -> List[PlaintextBallot]:

Просмотреть файл

@ -1,9 +1,9 @@
from typing import List
from typing import List, Tuple
import click
from electionguard.data_store import DataStore
from electionguard.ballot_box import BallotBox
from electionguard.encrypt import EncryptionMediator
from electionguard.encrypt import EncryptionDevice, EncryptionMediator
from electionguard.election import CiphertextElectionContext
from electionguard.manifest import InternalManifest
from electionguard.utils import get_optional
@ -15,7 +15,7 @@ from electionguard_tools.factories.election_factory import (
ElectionFactory,
)
from ..cli_models import E2eInputs, BuildElectionResults
from ..cli_models import E2eInputs, BuildElectionResults, E2eSubmitResults
from .e2e_step_base import E2eStepBase
@ -24,39 +24,41 @@ class SubmitVotesStep(E2eStepBase):
def submit_votes(
self, e2e_inputs: E2eInputs, build_election_results: BuildElectionResults
) -> DataStore:
) -> E2eSubmitResults:
ballots = e2e_inputs.ballots
internal_manifest = build_election_results.internal_manifest
context = build_election_results.context
ballot_store: DataStore = DataStore()
ciphertext_ballots = self._encrypt_votes(ballots, internal_manifest, context)
(ciphertext_ballots, device) = self._encrypt_votes(
ballots, internal_manifest, context
)
SubmitVotesStep._cast_and_spoil(
ballot_store, internal_manifest, context, ciphertext_ballots, e2e_inputs
)
return ballot_store
return E2eSubmitResults(ballot_store, device, ciphertext_ballots)
def _get_encrypter(
self,
internal_manifest: InternalManifest,
context: CiphertextElectionContext,
) -> EncryptionMediator:
) -> Tuple[EncryptionMediator, EncryptionDevice]:
device = ElectionFactory.get_encryption_device()
self.print_value("Device location", device.location)
encrypter = EncryptionMediator(internal_manifest, context, device)
return encrypter
return (encrypter, device)
def _encrypt_votes(
self,
plaintext_ballots: List[PlaintextBallot],
internal_manifest: InternalManifest,
context: CiphertextElectionContext,
) -> List[CiphertextBallot]:
) -> Tuple[List[CiphertextBallot], EncryptionDevice]:
self.print_value("Ballots to encrypt", len(plaintext_ballots))
encrypter = self._get_encrypter(internal_manifest, context)
(encrypter, device) = self._get_encrypter(internal_manifest, context)
encrypted_ballots = SubmitVotesStep._encrypt_ballots(
plaintext_ballots, encrypter
)
return encrypted_ballots
return (encrypted_ballots, device)
@staticmethod
def _encrypt_ballots(