|
1 | | -from electionguard.ballot import CiphertextBallot |
| 1 | +from electionguard.ballot import CiphertextBallot, PlaintextBallot |
2 | 2 | from electionguard.ballot_box import accept_ballot, BallotBoxState |
| 3 | +from electionguard.ballot_store import BallotStore |
3 | 4 | from electionguard.election import ( |
4 | 5 | CiphertextElectionContext, |
5 | 6 | ElectionDescription, |
6 | 7 | InternalElectionDescription, |
7 | 8 | ) |
8 | | -from electionguard.ballot_store import BallotStore |
| 9 | +from electionguard.encrypt import encrypt_ballot |
| 10 | +from electionguard.group import ElementModQ |
| 11 | +from electionguard.serializable import read_json_object, write_json_object |
| 12 | +from electionguard.utils import get_optional |
9 | 13 | from fastapi import APIRouter, Body, HTTPException |
10 | | -from typing import Any |
| 14 | +from typing import Any, Optional |
11 | 15 |
|
12 | | -from ..models import AcceptBallotRequest |
13 | | -from ..tags import CAST_AND_SPOIL |
| 16 | +from ..models import AcceptBallotRequest, EncryptBallotsRequest, EncryptBallotsResponse |
| 17 | +from ..tags import CAST_AND_SPOIL, ENCRYPT_BALLOTS |
14 | 18 |
|
15 | 19 | router = APIRouter() |
16 | 20 |
|
@@ -43,6 +47,40 @@ def spoil_ballot(request: AcceptBallotRequest = Body(...)) -> Any: |
43 | 47 | return spoiled_ballot.to_json_object() |
44 | 48 |
|
45 | 49 |
|
| 50 | +@router.post("/encrypt", tags=[ENCRYPT_BALLOTS]) |
| 51 | +def encrypt_ballots(request: EncryptBallotsRequest = Body(...)) -> Any: |
| 52 | + """ |
| 53 | + Encrypt one or more ballots |
| 54 | + """ |
| 55 | + ballots = [PlaintextBallot.from_json_object(ballot) for ballot in request.ballots] |
| 56 | + description = InternalElectionDescription( |
| 57 | + ElectionDescription.from_json_object(request.description) |
| 58 | + ) |
| 59 | + context = CiphertextElectionContext.from_json_object(request.context) |
| 60 | + seed_hash = read_json_object(request.seed_hash, ElementModQ) |
| 61 | + nonce: Optional[ElementModQ] = ( |
| 62 | + read_json_object(request.nonce, ElementModQ) if request.nonce else None |
| 63 | + ) |
| 64 | + |
| 65 | + encrypted_ballots = [] |
| 66 | + current_hash = seed_hash |
| 67 | + |
| 68 | + for ballot in ballots: |
| 69 | + encrypted_ballot = encrypt_ballot( |
| 70 | + ballot, description, context, current_hash, nonce |
| 71 | + ) |
| 72 | + if not encrypted_ballot: |
| 73 | + raise HTTPException(status_code=500, detail="Ballot failed to encrypt") |
| 74 | + encrypted_ballots.append(encrypted_ballot) |
| 75 | + current_hash = get_optional(encrypted_ballot.tracking_hash) |
| 76 | + |
| 77 | + response = EncryptBallotsResponse( |
| 78 | + encrypted_ballots=[ballot.to_json_object() for ballot in encrypted_ballots], |
| 79 | + next_seed_hash=write_json_object(current_hash), |
| 80 | + ) |
| 81 | + return response |
| 82 | + |
| 83 | + |
46 | 84 | def handle_ballot(request: AcceptBallotRequest, state: BallotBoxState) -> Any: |
47 | 85 | ballot = CiphertextBallot.from_json_object(request.ballot) |
48 | 86 | description = ElectionDescription.from_json_object(request.description) |
|
0 commit comments