Skip to content

Commit d4e753e

Browse files
rkorsakkeithrfung
andauthored
🔐 Add an endpoint to encrypt a batch of ballots (#82)
* 🔐 Add an endpoint to encrypt a batch of ballots * Fix linting error * Rename `tracking_hash` to `next_seed_hash` in encryption response The intent is to make it as clear as possible for the caller that this value should be used as the seed_hash of the next request. Co-authored-by: Keith Fung <[email protected]>
1 parent d06b371 commit d4e753e

File tree

3 files changed

+231
-16
lines changed

3 files changed

+231
-16
lines changed

app/api/v1/mediator/ballot.py

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
1-
from electionguard.ballot import CiphertextBallot
1+
from electionguard.ballot import CiphertextBallot, PlaintextBallot
22
from electionguard.ballot_box import accept_ballot, BallotBoxState
3+
from electionguard.ballot_store import BallotStore
34
from electionguard.election import (
45
CiphertextElectionContext,
56
ElectionDescription,
67
InternalElectionDescription,
78
)
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
913
from fastapi import APIRouter, Body, HTTPException
10-
from typing import Any
14+
from typing import Any, Optional
1115

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
1418

1519
router = APIRouter()
1620

@@ -43,6 +47,40 @@ def spoil_ballot(request: AcceptBallotRequest = Body(...)) -> Any:
4347
return spoiled_ballot.to_json_object()
4448

4549

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+
4684
def handle_ballot(request: AcceptBallotRequest, state: BallotBoxState) -> Any:
4785
ballot = CiphertextBallot.from_json_object(request.ballot)
4886
description = ElectionDescription.from_json_object(request.description)

app/api/v1/models/ballot.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,28 @@
1-
from typing import Any
1+
from typing import Any, List, Optional
22

33
from .base import Base
44
from .election import ElectionDecription, CiphertextElectionContext
55

66

77
CiphertextAcceptedBallot = Any
88
CiphertextBallot = Any
9+
PlaintextBallot = Any
910

1011

1112
class AcceptBallotRequest(Base):
1213
ballot: CiphertextBallot
1314
description: ElectionDecription
1415
context: CiphertextElectionContext
16+
17+
18+
class EncryptBallotsRequest(Base):
19+
ballots: List[PlaintextBallot]
20+
seed_hash: str
21+
nonce: Optional[str] = None
22+
description: ElectionDecription
23+
context: CiphertextElectionContext
24+
25+
26+
class EncryptBallotsResponse(Base):
27+
encrypted_ballots: List[PlaintextBallot]
28+
next_seed_hash: str

tests/ElectionGuard Web Api.postman_collection.json

Lines changed: 173 additions & 10 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)