### Initialize Development Environment Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/docs/Build_and_Run.md Sets up the project's development environment. Use either `make environment` or `poetry install --dev`. ```bash make environment ``` ```bash poetry install --dev ``` -------------------------------- ### Run End-to-End Election Example with Make Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/README.md Execute a complete end-to-end election example independently using the make test-example command. This is useful for verifying the full election process. ```bash make test-example ``` -------------------------------- ### Install Make using Chocolatey Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/docs/Tablet Setup.md Installs the 'make' utility using the Chocolatey package manager. Ensure Chocolatey is installed first. ```powershell choco install make ``` -------------------------------- ### Configure gmpy2 for Windows Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/docs/Build_and_Run.md Example of how to specify a local pre-compiled binary for `gmpy2` within the `pyproject.toml` file. This is an alternative to installing `gmpy2` directly when using Poetry on Windows. ```toml gmpy2 = { path = "./packages/gmpy2-2.0.8-cp39-cp39-win_amd64.whl" } ``` -------------------------------- ### Install ElectionGuard Module Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/docs/Build_and_Run.md Installs the `electionguard` module in edit mode, allowing for development. Use either `make install` or `poetry run python -m pip install -e .`. ```bash make install ``` ```bash poetry run python -m pip install -e . ``` -------------------------------- ### Initialize Vue App and Routing Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/src/electionguard_gui/web/index.html Sets up the main Vue application instance, including data properties, computed properties for routing and view rendering, methods for login and route retrieval, and lifecycle hooks for event handling and initial state setup. Mounts the Vue app to the '#app' element. ```javascript import { createApp } from "vue"; // components import Navbar from "./components/shared/navbar-component.js"; import Login from "./components/shared/login-component.js"; import EgFooter from "./components/shared/footer-component.js"; // services import AuthorizationService from "./services/authorization-service.js"; import RouterService from "./services/router-service.js"; createApp({ data() { return { currentPath: window.location.hash, showLogin: false, userId: null, }; }, computed: { currentView() { if (this.showLogin) return null; const route = this.getRoute(this.currentPath); console.log("navigating", route); return route.component; }, currentViewProperties() { const querystringParams = this.currentPath.split("?")[1]; const urlSearchParams = new URLSearchParams(querystringParams); const params = Object.fromEntries(urlSearchParams.entries()); console.log("params", params); return params; }, }, methods: { getRoute(path) { return RouterService.getRoute(path); }, login(username) { console.log("login", username); this.showLogin = false; this.userId = username; }, }, async mounted() { window.addEventListener("hashchange", () => { // setting currentPath will trigger the computed properties to update this.currentPath = window.location.hash; const route = this.getRoute(this.currentPath); this.showLogin = route.secured && !this.userId; }); this.userId = await AuthorizationService.getUserId(); this.showLogin = !this.userId; }, components: { Navbar, Login, EgFooter, }, }).mount("#app"); ``` -------------------------------- ### Configure Election Manifest and Context Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/docs/0_Configure_Election.ipynb Parses an election manifest, configures an ElectionBuilder with guardian details, generates an ElGamal keypair, and builds the internal manifest and ciphertext election context. This is a simplified example for a single keypair; a real election uses a Key Ceremony with multiple guardians. ```python import os from electionguard.election import CiphertextElectionContext from electionguard.election_builder import ElectionBuilder from electionguard.elgamal import ElGamalKeyPair, elgamal_keypair_from_secret from electionguard.manifest import Manifest, InternalManifest # Open an election manifest file with open(os.path.join(some_path, "election-manifest.json"), "r") as manifest: string_representation = manifest.read() election_description = Manifest.from_json(string_representation) # Create an election builder instance, and configure it for a single public-private keypair. # in a real election, you would configure this for a group of guardians. See Key Ceremony for more information. builder = ElectionBuilder( number_of_guardians=1, # since we will generate a single public-private keypair, we set this to 1 quorum=1, # since we will generate a single public-private keypair, we set this to 1 description=election_description, ) # Generate an ElGamal Keypair from a secret. In a real election you would use the Key Ceremony instead. some_secret_value: int = 12345 keypair: ElGamalKeyPair = elgamal_keypair_from_secret(some_secret_value) builder.set_public_key(keypair.public_key) # get an `InternalElectionDescription` and `CiphertextElectionContext` # that are used for the remainder of the election. (internal_manifest, context) = builder.build() ``` -------------------------------- ### Generate Guardians for ElectionGuard Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/docs/1_Key_Ceremony.md Demonstrates the setup and orchestration of guardians for a key ceremony. This includes creating guardians, announcing public key shares, orchestrating private key shares, verifying guardian actions, and publishing the joint public key. ```python NUMBER_OF_GUARDIANS: int QUORUM: int details: CeremonyDetails guardians: List[Guardian] # Setup Guardians for i in range(NUMBER_OF_GUARDIANS): guardians.append( Guardian.from_nonce(f"some_guardian_id_{str(i)}", i, NUMBER_OF_GUARDIANS, QUORUM) ) mediator = KeyCeremonyMediator(details) # Attendance (Public Key Share) for guardian in guardians: mediator.announce(guardian) # Orchestation (Private Key Share) orchestrated = mediator.orchestrate() # Verify (Prove the guardians acted in good faith) verified = mediator.verify() # Publish the Joint Public Key joint_public_key = mediator.publish_joint_key() ``` -------------------------------- ### Validate Module Import Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/docs/Build_and_Run.md Optionally verifies that the `electionguard` module has been successfully installed and can be imported. Use either `make validate` or a Python command. ```bash make validate ``` ```bash poetry run python -c 'import electionguard; print(electionguard.__package__ + " successfully imported")' ``` -------------------------------- ### Guardian Key Announcement and Sharing Source: https://context7.com/election-tech-initiative/electionguard-python/llms.txt Demonstrates the process of guardians announcing and sharing their keys, followed by verification. This is part of the initial setup for a secure election. ```python for g in guardians: mediator.announce(g.share_key()) announced_keys = get_optional(mediator.share_announced()) for g in guardians: for key in announced_keys: if key.owner_id != g.id: g.save_guardian_key(key) assert mediator.all_guardians_announced() ``` -------------------------------- ### Check Python Bit Version Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/docs/Build_and_Run.md Determines if the current Python installation is 64-bit. This is useful for selecting the correct pre-compiled binary for `gmpy2` on Windows. ```python python -c 'from sys import maxsize; print(maxsize > 2**32)' ``` -------------------------------- ### Execute Decryption Process with Missing Guardians Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/docs/4_Decrypt_Tally.md This example demonstrates how to use the DecryptionMediator to decrypt an election tally, compensating for missing guardians by reconstructing their partial decryption shares. Ensure all necessary objects like InternalManifest, CiphertextElectionContext, and CiphertextTally are loaded, and provide lists of available and missing guardians. ```python internal_manifest: InternalManifest # Load the election manifest context: CiphertextElectionContext # Load the election encryption context encrypted_Tally: CiphertextTally # Provide a tally from the previous step available_guardians: List[Guardian] # Provite the list of guardians who will participate missing_guardians: List[str] # Provide a list of guardians who will not participate mediator = DecryptionMediator(internal_manifest, context, encrypted_tally) # Loop through the available guardians and annouce their presence for guardian in available_guardians: if (mediator.announce(guardian) is None): break # loop through the missing guardians and compensate for them for guardian in missing_guardians: if (mediator.compensate(guardian) is None): break # Generate the plaintext tally plaintext_tally = mediator.get_plaintext_tally() # The plaintext tally automatically includes the election tally and the spoiled ballots contest_tallies = plaintext_tally.contests spoiled_ballots = plaintext_tally.spoiled_ballots ``` -------------------------------- ### Initialize Python Environment Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/docs/Tablet Setup.md Runs the 'make environment' command to set up the Python project's environment. An error at the end of this process is expected and normal. ```bash make environment ``` -------------------------------- ### Run All GitHub Actions with Make Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/README.md Execute the entire GitHub Actions workflow using the make command. This simplifies the command-line experience for development and testing. ```bash make ``` -------------------------------- ### Run Unit and Integration Tests with Make Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/README.md Execute all unit and integration tests for the ElectionGuard Python SDK using the make test command. This ensures the code base is functioning correctly. ```bash make test ``` -------------------------------- ### Run ElectionGuard CLI Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/docs/Tablet Setup.md Executes the ElectionGuard command-line interface using Poetry. This command should display the help information for the 'eg' command. ```bash poetry run eg ``` -------------------------------- ### Initialize Key Ceremony Guardians Source: https://context7.com/election-tech-initiative/electionguard-python/llms.txt Set up multiple guardians for a distributed key generation ceremony. This involves creating guardians with unique nonces and configuring a KeyCeremonyMediator. ```python from electionguard.guardian import Guardian from electionguard.key_ceremony import ( generate_election_key_pair, combine_election_public_keys, CeremonyDetails, ) from electionguard.key_ceremony_mediator import KeyCeremonyMediator from electionguard.utils import get_optional NUMBER_OF_GUARDIANS = 3 QUORUM = 2 # Create guardians guardians = [ Guardian.from_nonce(str(i + 1), i + 1, NUMBER_OF_GUARDIANS, QUORUM) for i in range(NUMBER_OF_GUARDIANS) ] # Set up mediator mediator = KeyCeremonyMediator("mediator-1", guardians[0].ceremony_details) ``` -------------------------------- ### Ballot encryption Source: https://context7.com/election-tech-initiative/electionguard-python/llms.txt `encrypt_ballot` homomorphically encrypts a `PlaintextBallot` into a `CiphertextBallot`. Each selection is encrypted with an exponential ElGamal scheme and accompanied by a Disjunctive Chaum-Pedersen zero-knowledge proof ensuring the encrypted vote is 0 or 1. Each contest gets a Constant Chaum-Pedersen proof that the sum of selections equals `number_elected`. `EncryptionMediator` is a stateful wrapper that chains ballot codes across successive encryptions. ```APIDOC ## `EncryptionMediator.encrypt` ### Description Homomorphically encrypts a `PlaintextBallot` into a `CiphertextBallot` using ElGamal encryption and zero-knowledge proofs. ### Method `EncryptionMediator.encrypt(plaintext_ballot: PlaintextBallot) -> CiphertextBallot | None` ### Parameters - `plaintext_ballot` (PlaintextBallot) - Required - The ballot to encrypt. ### Request Example ```python from electionguard.encrypt import ( EncryptionDevice, EncryptionMediator, generate_device_uuid, ) from electionguard.ballot import PlaintextBallot, PlaintextBallotContest, PlaintextBallotSelection # Configure the encryption device device = EncryptionDevice( device_id=generate_device_uuid(), session_id=12345, launch_code=67890, location="Polling Place A, Ward 1", ) # Construct a plaintext ballot plaintext_ballot = PlaintextBallot( object_id="ballot-0001", style_id="ballot-style-1", contests=[ PlaintextBallotContest( object_id="contest-president", ballot_selections=[ PlaintextBallotSelection(object_id="sel-a-for-candidate-a", vote=1), PlaintextBallotSelection(object_id="sel-b-for-candidate-b", vote=0), ], ) ], ) # Stateful mediator encrypter = EncryptionMediator(internal_manifest, context, device) encrypted_ballot = encrypter.encrypt(plaintext_ballot) ``` ### Response #### Success Response (CiphertextBallot) - `object_id` (str) - The unique identifier for the ballot. - `code` (ElementModQ) - The ballot's unique code. ### Response Example ```python assert encrypted_ballot is not None, "Encryption failed" print(f"Ballot ID: {encrypted_ballot.object_id}") print(f"Ballot code: {encrypted_ballot.code.to_hex()[:16]}...") ``` ``` -------------------------------- ### Manifest - Election Description and Validation Source: https://context7.com/election-tech-initiative/electionguard-python/llms.txt Demonstrates how to build and validate a Manifest object, which describes the election's structure, including geopolitical units, parties, candidates, contests, and ballot styles. It also shows how to derive an InternalManifest for runtime use and how to serialize/deserialize Manifest objects. ```APIDOC ## Manifest - Election Description and Validation ### Description `Manifest` is the top-level data class describing an election: its type, geopolitical units, parties, candidates, contests, ballot styles, and schedule. `InternalManifest` is the derived, hashed representation used at runtime for encryption and validation. ### Code Example ```python from electionguard.manifest import ( Manifest, InternalManifest, ElectionType, VoteVariationType, GeopoliticalUnit, ReportingUnitType, Party, Candidate, SelectionDescription, CandidateContestDescription, BallotStyle, ) from electionguard.serialize import from_file, to_file from datetime import datetime # Build a manifest programmatically gpu = GeopoliticalUnit("county-1", "Hamilton County", ReportingUnitType.county) party = Party("party-dem", "Democratic Party") candidate_a = Candidate("candidate-a", is_write_in=False) candidate_b = Candidate("candidate-b", is_write_in=False) sel_a = SelectionDescription("sel-a-for-candidate-a", 0, "candidate-a") sel_b = SelectionDescription("sel-b-for-candidate-b", 1, "candidate-b") contest = CandidateContestDescription( object_id="contest-president", sequence_order=0, electoral_district_id="county-1", vote_variation=VoteVariationType.one_of_m, number_elected=1, name="President", ballot_selections=[sel_a, sel_b], ) ballot_style = BallotStyle("ballot-style-1", ["county-1"]) manifest = Manifest( election_scope_id="hamilton-county-2024", spec_version="1.0", election_type=ElectionType.general, start_date=datetime(2024, 11, 5), end_date=datetime(2024, 11, 5), geopolitical_units=[gpu], parties=[party], candidates=[candidate_a, candidate_b], contests=[contest], ballot_styles=[ballot_style], ) assert manifest.is_valid(), "Manifest failed validation" # Derive the internal (hashed) manifest used for encryption internal_manifest = InternalManifest(manifest) print(f"Manifest hash: {internal_manifest.manifest_hash.to_hex()}") # Serialize / deserialize path = to_file(manifest, "manifest", target_path="/tmp/election") manifest_loaded = from_file(Manifest, path) assert manifest_loaded.is_valid() ``` ``` -------------------------------- ### Clone ElectionGuard Python Repository Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/docs/Tablet Setup.md Clones the ElectionGuard Python repository from GitHub into a local 'code' directory. This command is run from the command prompt. ```bash mkdir code cd code git clone https://github.com/microsoft/electionguard-python ``` -------------------------------- ### Run Code Coverage Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/docs/Build_and_Run.md Executes the test suite and generates a code coverage report. Use either `make coverage` or `poetry run coverage report`. ```bash make coverage ``` ```bash poetry run coverage report ``` -------------------------------- ### Run Tests with Pytest Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/docs/Build_and_Run.md Executes the project's tests using the `pytest` framework. Use either `make test` or `poetry run python -m pytest /tests`. ```bash make test ``` ```bash poetry run python -m pytest /tests ``` -------------------------------- ### Partial Key Backup Sharing and Verification Source: https://context7.com/election-tech-initiative/electionguard-python/llms.txt Illustrates how guardians generate, share, and receive partial key backups, followed by verification. This ensures the integrity and availability of the election keys. ```python for sender in guardians: sender.generate_election_partial_key_backups() backups = [ get_optional(sender.share_election_partial_key_backup(r.id)) for r in guardians if r.id != sender.id ] mediator.receive_backups(backups) for receiver in guardians: backups = get_optional(mediator.share_backups(receiver.id)) for backup in backups: receiver.save_election_partial_key_backup(backup) ``` ```python for receiver in guardians: verifications = [ get_optional(receiver.verify_election_partial_key_backup(owner.id)) for owner in guardians if owner.id != receiver.id ] mediator.receive_backup_verifications(verifications) assert mediator.all_backups_verified() ``` -------------------------------- ### Publish Joint Key Source: https://context7.com/election-tech-initiative/electionguard-python/llms.txt Shows how to publish the joint public key and commitment hash after all backups have been verified. This key is essential for subsequent encryption operations. ```python joint_key = get_optional(mediator.publish_joint_key()) print(f"Joint public key: {joint_key.joint_public_key.to_hex()[:16]}...") print(f"Commitment hash: {joint_key.commitment_hash.to_hex()[:16]}...") ``` -------------------------------- ### Build and Validate Election Manifest Source: https://context7.com/election-tech-initiative/electionguard-python/llms.txt Programmatically construct an election manifest and derive its internal, hashed representation for runtime use. Demonstrates manifest validation and serialization/deserialization. ```python from electionguard.manifest import ( Manifest, InternalManifest, ElectionType, VoteVariationType, GeopoliticalUnit, ReportingUnitType, Party, Candidate, SelectionDescription, CandidateContestDescription, BallotStyle, ) from electionguard.serialize import from_file, to_file from datetime import datetime # Build a manifest programmatically gpu = GeopoliticalUnit("county-1", "Hamilton County", ReportingUnitType.county) party = Party("party-dem", "Democratic Party") candidate_a = Candidate("candidate-a", is_write_in=False) candidate_b = Candidate("candidate-b", is_write_in=False) sel_a = SelectionDescription("sel-a-for-candidate-a", 0, "candidate-a") sel_b = SelectionDescription("sel-b-for-candidate-b", 1, "candidate-b") contest = CandidateContestDescription( object_id="contest-president", sequence_order=0, electoral_district_id="county-1", vote_variation=VoteVariationType.one_of_m, number_elected=1, name="President", ballot_selections=[sel_a, sel_b], ) ballot_style = BallotStyle("ballot-style-1", ["county-1"]) manifest = Manifest( election_scope_id="hamilton-county-2024", spec_version="1.0", election_type=ElectionType.general, start_date=datetime(2024, 11, 5), end_date=datetime(2024, 11, 5), geopolitical_units=[gpu], parties=[party], candidates=[candidate_a, candidate_b], contests=[contest], ballot_styles=[ballot_style], ) assert manifest.is_valid(), "Manifest failed validation" # Derive the internal (hashed) manifest used for encryption internal_manifest = InternalManifest(manifest) print(f"Manifest hash: {internal_manifest.manifest_hash.to_hex()}") # Serialize / deserialize path = to_file(manifest, "manifest", target_path="/tmp/election") manifest_loaded = from_file(Manifest, path) assert manifest_loaded.is_valid() ``` -------------------------------- ### Guardian and Key Ceremony - Distributed Key Generation Source: https://context7.com/election-tech-initiative/electionguard-python/llms.txt Details on using the `Guardian` class for distributed key generation through a multi-round key ceremony. This process involves multiple guardians collectively generating a joint election public key, producing partial-key backups and cross-verification proofs necessary for threshold decryption. ```APIDOC ## Guardian and Key Ceremony — Distributed Key Generation ### Description `Guardian` represents a trustee of the election. A group of guardians collectively generate a joint election public key through a multi-round key ceremony. The ceremony produces partial-key backups and cross-verification proofs so any quorum of guardians can later perform threshold decryption — even if some guardians are absent. ### Code Example ```python from electionguard.guardian import Guardian from electionguard.key_ceremony import ( generate_election_key_pair, combine_election_public_keys, CeremonyDetails, ) from electionguard.key_ceremony_mediator import KeyCeremonyMediator from electionguard.utils import get_optional NUMBER_OF_GUARDIANS = 3 QUORUM = 2 # Create guardians guardians = [ Guardian.from_nonce(str(i + 1), i + 1, NUMBER_OF_GUARDIANS, QUORUM) for i in range(NUMBER_OF_GUARDIANS) ] # Set up mediator mediator = KeyCeremonyMediator("mediator-1", guardians[0].ceremony_details) ``` ``` -------------------------------- ### JSON Serialization and Deserialization Source: https://context7.com/election-tech-initiative/electionguard-python/llms.txt Shows how to serialize ElectionGuard data objects to and from JSON files or raw strings using the `serialize` module. Supports custom types and custom schemas. ```python from electionguard.serialize import ( to_file, from_file, to_raw, from_raw, from_list_in_file, get_schema, construct_path, ) from electionguard.tally import PlaintextTally, CiphertextTally, PublishedCiphertextTally from electionguard.ballot import SubmittedBallot from electionguard.election import CiphertextElectionContext # Assuming context, manifest, and plaintext_tally are defined # context: CiphertextElectionContext # manifest: Manifest # plaintext_tally: PlaintextTally # Serialize to file context_path = to_file(context, "context", target_path="/tmp/election_record") manifest_path = to_file(manifest, "manifest", target_path="/tmp/election_record") tally_path = to_file(plaintext_tally, "tally", target_path="/tmp/election_record") ``` -------------------------------- ### Ballot Submission with BallotBox Source: https://context7.com/election-tech-initiative/electionguard-python/llms.txt Demonstrates casting and spoiling encrypted ballots using the `BallotBox` class. The `BallotBox` manages state transitions and storage. Duplicate submissions are rejected. Use `cast_ballot` and `spoil_ballot` for direct conversion without storage. ```python from electionguard.ballot_box import BallotBox, cast_ballot, spoil_ballot, get_ballots from electionguard.ballot import BallotBoxState from electionguard.data_store import DataStore ballot_store: DataStore = DataStore() ballot_box = BallotBox(internal_manifest, context, ballot_store) # Cast ballot (voter keeps the ballot) submitted = ballot_box.cast(encrypted_ballot) assert submitted is not None assert submitted.state == BallotBoxState.CAST print(f"Cast ballot id: {submitted.object_id}") # Spoil ballot (voter exchanges for a new one — a challenge ballot) # encrypted_ballot_2 is a separate CiphertextBallot submitted_spoiled = ballot_box.spoil(encrypted_ballot_2) assert submitted_spoiled is not None assert submitted_spoiled.state == BallotBoxState.SPOILED # Retrieve only cast ballots from the store cast_ballots = get_ballots(ballot_store, BallotBoxState.CAST) spoiled_ballots = get_ballots(ballot_store, BallotBoxState.SPOILED) print(f"Cast: {len(cast_ballots)}, Spoiled: {len(spoiled_ballots)}") # Direct conversion without a BallotBox (no duplicate checking) submitted_directly = cast_ballot(encrypted_ballot_3) spoiled_directly = spoil_ballot(encrypted_ballot_4) ``` -------------------------------- ### ElGamal Encryption and Homomorphic Addition Source: https://context7.com/election-tech-initiative/electionguard-python/llms.txt Demonstrates core ElGamal encryption primitives, including random keypair generation, encrypting integers, homomorphic addition of ciphertexts, and encrypting arbitrary-length byte payloads. Use `hashed_elgamal_encrypt` for variable-length data. ```python from electionguard.elgamal import ( elgamal_keypair_random, elgamal_keypair_from_secret, elgamal_encrypt, elgamal_add, ElGamalCiphertext, hashed_elgamal_encrypt, elgamal_combine_public_keys, ) from electionguard.group import rand_q, int_to_q, ElementModQ import json # Generate a random keypair keypair = elgamal_keypair_random() # Encrypt votes 1 and 0 nonce1, nonce2 = rand_q(), rand_q() ct1 = elgamal_encrypt(1, nonce1, keypair.public_key) # encrypts "1 vote" ct2 = elgamal_encrypt(0, nonce2, keypair.public_key) # encrypts "0 votes" ct3 = elgamal_encrypt(1, rand_q(), keypair.public_key) assert ct1 is not None and ct2 is not None and ct3 is not None # Homomorphically add: encrypted(1) + encrypted(0) + encrypted(1) = encrypted(2) accumulated = elgamal_add(ct1, ct2, ct3) # Decrypt with secret key → discrete log of g^2 → 2 result = accumulated.decrypt(keypair.secret_key) print(f"Homomorphic sum: {result}") # 2 # Hashed ElGamal for arbitrary-length payloads seed = rand_q() payload = json.dumps({"write_in": "Alice", "error": None}).encode("utf-8") hashed_ct = hashed_elgamal_encrypt(payload, rand_q(), keypair.public_key, seed) decrypted_bytes = hashed_ct.decrypt(keypair.secret_key, seed) print(json.loads(decrypted_bytes)) # {'write_in': 'Alice', 'error': None} # Combine multiple public keys into a joint key kp1 = elgamal_keypair_random() kp2 = elgamal_keypair_random() joint = elgamal_combine_public_keys([kp1.public_key, kp2.public_key]) ``` -------------------------------- ### Encrypt Ballot and Verify Proofs Source: https://context7.com/election-tech-initiative/electionguard-python/llms.txt Encrypts a plaintext ballot and verifies its zero-knowledge proofs. Ensure `should_verify_proofs` is set to `True` for validation. ```python from electionguard.group import rand_q nonce = rand_q() verified_ballot = encrypt_ballot( ballot=plaintext_ballot, internal_manifest=internal_manifest, context=context, encryption_seed=device.get_hash(), nonce=nonce, should_verify_proofs=True, # verify ZK proofs before returning ) assert verified_ballot is not None assert verified_ballot.is_valid_encryption( internal_manifest.manifest_hash, context.elgamal_public_key, context.crypto_extended_base_hash, ) ``` -------------------------------- ### Election Constants Configuration Source: https://context7.com/election-tech-initiative/electionguard-python/llms.txt Manage cryptographic parameters like prime `p`, subgroup order `q`, and generator `g` using `ElectionConstants`. Use `STANDARD_CONSTANTS` for production or smaller sets for testing. ```python from electionguard.constants import ( get_constants, STANDARD_CONSTANTS, SMALL_TEST_CONSTANTS, EXTRA_SMALL_TEST_CONSTANTS, create_constants, PrimeOption, get_large_prime, get_small_prime, get_generator, ) # Inspect the active constants constants = get_constants() # returns whatever is currently configured print(f"Large prime p (first 16 hex): {hex(get_large_prime())[:16]}...") print(f"Small prime q (first 16 hex): {hex(get_small_prime())[:16]}...") print(f"Generator g (first 16 hex): {hex(get_generator())[:16]}...") # Use small constants for fast testing (not production-safe) small = SMALL_TEST_CONSTANTS extra_small = EXTRA_SMALL_TEST_CONSTANTS # Create a custom constant set from a PrimeOption custom = create_constants(PrimeOption.Standard) # or PrimeOption.TestSmall print(f"Standard constants: large_prime bits = {constants.large_prime.bit_length()}") ``` -------------------------------- ### Election context construction Source: https://context7.com/election-tech-initiative/electionguard-python/llms.txt `make_ciphertext_election_context` creates the `CiphertextElectionContext` — the immutable cryptographic context for an election run. It computes the base hash `Q` and extended base hash `Q'` that anchor every subsequent cryptographic operation. Use the `ElectionBuilder` helper (from `electionguard_tools`) to wire together a manifest and a joint key into a context. ```APIDOC ## `make_ciphertext_election_context` ### Description Creates the `CiphertextElectionContext` for an election run, computing base and extended base hashes. ### Parameters - `number_of_guardians` (int) - Required - The total number of guardians for the election. - `quorum` (int) - Required - The minimum number of guardians required for operations. - `elgamal_public_key` (ElGamalPublicKey) - Required - The public key for ElGamal encryption. - `commitment_hash` (Hash) - Required - The commitment hash of the guardians' keys. - `manifest_hash` (Hash) - Required - The hash of the election manifest. - `extended_data` (dict) - Optional - Additional metadata for the context. ### Request Example ```python from electionguard.election import make_ciphertext_election_context from electionguard.manifest import InternalManifest # Assuming `manifest`, `joint_key` obtained from prior steps: context = make_ciphertext_election_context( number_of_guardians=3, quorum=2, elgamal_public_key=joint_key.joint_public_key, commitment_hash=joint_key.commitment_hash, manifest_hash=internal_manifest.manifest_hash, ) # With extended metadata context_with_meta = make_ciphertext_election_context( number_of_guardians=3, quorum=2, elgamal_public_key=joint_key.joint_public_key, commitment_hash=joint_key.commitment_hash, manifest_hash=internal_manifest.manifest_hash, extended_data={"election_id": "hamilton-2024", "jurisdiction": "Ohio"}, ) ``` ### Response #### Success Response (CiphertextElectionContext) - `number_of_guardians` (int) - The total number of guardians. - `quorum` (int) - The quorum number. - `crypto_base_hash` (Hash) - The computed base hash `Q`. - `crypto_extended_base_hash` (Hash) - The computed extended base hash `Q'`. - `elgamal_public_key` (ElGamalPublicKey) - The ElGamal public key. - `commitment_hash` (Hash) - The commitment hash. - `manifest_hash` (Hash) - The manifest hash. - `extended_data` (dict) - Optional extended metadata. ### Response Example ```python print(f"Guardians: {context.number_of_guardians}") print(f"Quorum: {context.quorum}") print(f"Base hash Q: {context.crypto_base_hash.to_hex()[:16]}...") print(f"Extended hash Q': {context.crypto_extended_base_hash.to_hex()[:16]}...") print(context_with_meta.get_extended_data_field("election_id")) # "hamilton-2024" ``` ``` -------------------------------- ### ElGamal Encryption Primitives Source: https://context7.com/election-tech-initiative/electionguard-python/llms.txt Provides core ElGamal encryption functions, including encryption, homomorphic addition, and handling of arbitrary-length payloads. ```APIDOC ## elgamal_encrypt / elgamal_add / ElGamalCiphertext — Core ElGamal primitives Exponential ElGamal encryption with homomorphic addition. `elgamal_encrypt` encrypts an integer `m` such that ciphertexts can be multiplied to add the underlying plaintexts. `elgamal_add` performs ciphertext-level accumulation. `hashed_elgamal_encrypt` handles variable-length byte payloads (used for contest extended data and key backups). ### Functions #### `elgamal_encrypt(message: int, nonce: ElementModQ, public_key: ElGamalPublicKey) -> Optional[ElGamalCiphertext]` Encrypts an integer message using ElGamal encryption. #### `elgamal_add(*ciphertexts: ElGamalCiphertext) -> ElGamalCiphertext` Performs homomorphic addition of multiple ElGamal ciphertexts. #### `hashed_elgamal_encrypt(payload: bytes, nonce: ElementModQ, public_key: ElGamalPublicKey, seed: ElementModQ) -> ElGamalCiphertext` Encrypts a variable-length byte payload using ElGamal encryption with a hash commitment. #### `elgamal_keypair_random() -> ElGamalKeyPair` Generates a random ElGamal key pair. #### `elgamal_keypair_from_secret(secret: ElementModQ) -> ElGamalKeyPair` Generates an ElGamal key pair from a secret key. #### `elgamal_combine_public_keys(public_keys: List[ElGamalPublicKey]) -> ElGamalPublicKey` Combines multiple ElGamal public keys into a single joint public key. ### Example ```python # Generate a random keypair keypair = elgamal_keypair_random() # Encrypt votes 1 and 0 nonce1, nonce2 = rand_q(), rand_q() ct1 = elgamal_encrypt(1, nonce1, keypair.public_key) # encrypts "1 vote" ct2 = elgamal_encrypt(0, nonce2, keypair.public_key) # encrypts "0 votes" ct3 = elgamal_encrypt(1, rand_q(), keypair.public_key) assert ct1 is not None and ct2 is not None and ct3 is not None # Homomorphically add: encrypted(1) + encrypted(0) + encrypted(1) = encrypted(2) accumulated = elgamal_add(ct1, ct2, ct3) # Decrypt with secret key → discrete log of g^2 → 2 result = accumulated.decrypt(keypair.secret_key) print(f"Homomorphic sum: {result}") # 2 # Hashed ElGamal for arbitrary-length payloads import json seed = rand_q() payload = json.dumps({"write_in": "Alice", "error": None}).encode("utf-8") hashed_ct = hashed_elgamal_encrypt(payload, rand_q(), keypair.public_key, seed) decrypted_bytes = hashed_ct.decrypt(keypair.secret_key, seed) print(json.loads(decrypted_bytes)) # {'write_in': 'Alice', 'error': None} # Combine multiple public keys into a joint key kp1 = elgamal_keypair_random() kp2 = elgamal_keypair_random() joint = elgamal_combine_public_keys([kp1.public_key, kp2.public_key]) ``` ``` -------------------------------- ### Accepting Ballots for Casting or Spoiling Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/docs/3_Cast_and_Spoil.md Use the `accept_ballot` function to process ballots, specifying whether they are cast or spoiled. Ensure all necessary manifest, encryption, and store objects are initialized. ```python from electionguard.ballot_box import accept_ballot internal_manifest: InternalManifest encryption: CiphertextElection store: DataStore ballots_to_cast: List[CiphertextBallot] ballots_to_spoil: List[CiphertextBallot] for ballot in ballots_to_cast: submitted_ballot = accept_ballot( ballot, BallotBoxState.CAST, internal_manifest, encryption, store ) for ballot in ballots_to_spoil: submitted_ballot = accept_ballot( ballot, BallotBoxState.SPOILED, internal_manifest, encryption, store ) ``` -------------------------------- ### Create Ciphertext Election Context Source: https://context7.com/election-tech-initiative/electionguard-python/llms.txt Constructs the immutable cryptographic context for an election using `make_ciphertext_election_context`. This function computes essential base hashes and can include extended metadata. ```python from electionguard.election import make_ciphertext_election_context, CiphertextElectionContext from electionguard.manifest import InternalManifest # Assuming `manifest`, `joint_key` obtained from prior steps: context: CiphertextElectionContext = make_ciphertext_election_context( number_of_guardians=3, quorum=2, elgamal_public_key=joint_key.joint_public_key, commitment_hash=joint_key.commitment_hash, manifest_hash=internal_manifest.manifest_hash, ) print(f"Guardians: {context.number_of_guardians}") print(f"Quorum: {context.quorum}") print(f"Base hash Q: {context.crypto_base_hash.to_hex()[:16]}...") print(f"Extended hash Q': {context.crypto_extended_base_hash.to_hex()[:16]}...") ``` ```python # Attach optional extended metadata context_with_meta = make_ciphertext_election_context( number_of_guardians=3, quorum=2, elgamal_public_key=joint_key.joint_public_key, commitment_hash=joint_key.commitment_hash, manifest_hash=internal_manifest.manifest_hash, extended_data={"election_id": "hamilton-2024", "jurisdiction": "Ohio"}, ) print(context_with_meta.get_extended_data_field("election_id")) # "hamilton-2024" ``` -------------------------------- ### Generating Tally with Stateful Class Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/docs/3_Cast_and_Spoil.md Instantiate the `CiphertextTally` class and manually append each submitted ballot. This method is suitable for large ballot collections. ```python internal_manifest: InternalManifest context: CiphertextElectionContext ballots: List[SubmittedBallot] tally = CiphertextTally(internal_manifest, context) for ballot in ballots: assert(tally.append(ballot)) ``` -------------------------------- ### Deserialize Election Data from Files Source: https://context7.com/election-tech-initiative/electionguard-python/llms.txt Load election data from files using various deserialization functions. Supports direct file loading, raw JSON strings, and lists of objects within a JSON file. ```python from electionguard.io import from_file, to_raw, from_raw, from_list_in_file from electionguard.election import CiphertextElectionContext from electionguard.tally import PlaintextTally from electionguard.ballot import SubmittedBallot # Deserialize from file context_back = from_file(CiphertextElectionContext, context_path) tally_back = from_file(PlaintextTally, tally_path) # Serialize/deserialize via raw JSON strings (no I/O) raw_json: str = to_raw(context) context_from_raw: CiphertextElectionContext = from_raw(CiphertextElectionContext, raw_json) # Deserialize a JSON file containing a list of objects ballots: list[SubmittedBallot] = from_list_in_file( SubmittedBallot, "/tmp/election_record/submitted_ballots/ballots.json" ) print(f"Loaded {len(ballots)} submitted ballots") ``` ```python from electionguard.schema import get_schema # Get the JSON Schema definition for a type schema_str = get_schema(CiphertextElectionContext) ``` ```python from electionguard.utils import construct_path # Build a consistent file path path = construct_path("context", "/tmp/election_record") # → "/tmp/election_record/context.json" ``` -------------------------------- ### Export and Restore Guardian Private Data Source: https://context7.com/election-tech-initiative/electionguard-python/llms.txt Use this to export a guardian's private data for backup or to restore a guardian in a new session. Ensure you have the correct NUMBER_OF_GUARDIANS and QUORUM for restoration. ```python private_record = guardians[0].export_private_data() restored_guardian = Guardian.from_private_record(private_record, NUMBER_OF_GUARDIANS, QUORUM) ``` -------------------------------- ### JSON Serialization and Deserialization Source: https://context7.com/election-tech-initiative/electionguard-python/llms.txt Utilities for serializing and deserializing ElectionGuard data objects to and from JSON format. ```APIDOC ## serialize — JSON serialization and deserialization All ElectionGuard data objects can be serialized to and from JSON using the `serialize` module. It uses `pydantic` for encoding and `dacite` for schema-aware deserialization, handling custom types like `ElementModP`, `ElementModQ`, `datetime`, and enums transparently. ### Functions #### `to_file(data: Any, name: str, target_path: str) -> str` Serializes data to a JSON file. #### `from_file(file_path: str, data_class: Type[T]) -> T` Deserializes data from a JSON file. #### `to_raw(data: Any) -> Dict[str, Any]` Serializes data to a raw dictionary. #### `from_raw(data: Dict[str, Any], data_class: Type[T]) -> T` Deserializes data from a raw dictionary. #### `from_list_in_file(file_path: str, data_class: Type[T]) -> List[T]` Deserializes a list of objects from a JSON file. #### `get_schema(data_class: Type[T]) -> Dict[str, Any]` Retrieves the JSON schema for a given data class. #### `construct_path(target_path: str, name: str) -> str` Constructs a file path. ### Example ```python # Serialize to file context_path = to_file(context, "context", target_path="/tmp/election_record") manifest_path = to_file(manifest, "manifest", target_path="/tmp/election_record") tally_path = to_file(plaintext_tally, "tally", target_path="/tmp/election_record") # Deserialize from file loaded_context = from_file(context_path, CiphertextElectionContext) loaded_manifest = from_file(manifest_path, Manifest) loaded_tally = from_file(tally_path, PlaintextTally) # Serialize to raw dictionary raw_ballot = to_raw(submitted_ballot) # Deserialize from raw dictionary loaded_ballot = from_raw(raw_ballot, SubmittedBallot) ``` ``` -------------------------------- ### Generating Tally with Functional Method Source: https://github.com/election-tech-initiative/electionguard-python/blob/main/docs/3_Cast_and_Spoil.md Utilize the `tally_ballots` function for a stateless approach to generating the `CiphertextTally` from a `DataStore`. This is preferred for small ballot collections or custom `DataStore` implementations. ```python internal_manifest: InternalManifest context: CiphertextElectionContext store: DataStore tally = tally_ballots(store, internal_manifest, context) assert(tally is not None) ```