Skip to content

Commit aee0626

Browse files
committed
πŸ›‘ Guardian including Keys, Backups, and Verifiers (#65)
* ✨ Create Guardian Backup - Create Guardian backups - Create models folder - Change generate keys to a guardian call * βœ… Add serialize changes * 🧹 Model Cleanup * ✨ Backups, Challenges and Verifier * βœ…. Update tests for Guardian Test Test * πŸ“ Refactor Models folder
1 parent c4b73db commit aee0626

File tree

15 files changed

+605
-115
lines changed

15 files changed

+605
-115
lines changed

β€Žapp/api/v1/api.pyβ€Ž

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from fastapi import APIRouter
22

3-
from app.api.v1.endpoints import ballot, election, key, ping, tally
3+
from app.api.v1.endpoints import ballot, election, guardian, key, ping, tally
44

55
api_router = APIRouter()
6-
api_router.include_router(ping.router, prefix="/ping", tags=["ping"])
7-
api_router.include_router(election.router, prefix="/election", tags=["election"])
86
api_router.include_router(ballot.router, prefix="/ballot", tags=["ballot"])
7+
api_router.include_router(election.router, prefix="/election", tags=["election"])
8+
api_router.include_router(guardian.router, prefix="/guardian", tags=["guardian"])
99
api_router.include_router(key.router, prefix="/key", tags=["key"])
10+
api_router.include_router(ping.router, prefix="/ping", tags=["ping"])
1011
api_router.include_router(tally.router, prefix="/tally", tags=["tally"])

β€Žapp/api/v1/endpoints/ballot.pyβ€Ž

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,11 @@
77
)
88
from electionguard.ballot_store import BallotStore
99
from fastapi import APIRouter, Body, HTTPException
10-
from pydantic import BaseModel
1110
from typing import Any
1211

13-
router = APIRouter()
14-
12+
from ..models import AcceptBallotRequest
1513

16-
class AcceptBallotRequest(BaseModel):
17-
ballot: Any
18-
description: Any
19-
context: Any
14+
router = APIRouter()
2015

2116

2217
@router.post("/cast")
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
from electionguard.auxiliary import AuxiliaryPublicKey
2+
from electionguard.election_polynomial import ElectionPolynomial
3+
from electionguard.group import int_to_q_unchecked
4+
from electionguard.key_ceremony import (
5+
ElectionPartialKeyBackup,
6+
ElectionPartialKeyChallenge,
7+
generate_election_key_pair,
8+
generate_rsa_auxiliary_key_pair,
9+
generate_election_partial_key_backup,
10+
generate_election_partial_key_challenge,
11+
verify_election_partial_key_backup,
12+
verify_election_partial_key_challenge,
13+
)
14+
from electionguard.rsa import rsa_decrypt, rsa_encrypt
15+
from electionguard.serializable import write_json_object
16+
from fastapi import APIRouter, HTTPException
17+
from typing import Any, List
18+
19+
from ..models import (
20+
AuxiliaryKeyPair,
21+
BackupChallengeRequest,
22+
BackupVerificationRequest,
23+
ChallengeVerificationRequest,
24+
ElectionKeyPair,
25+
Guardian,
26+
GuardianRequest,
27+
GuardianBackup,
28+
GuardianBackupRequest,
29+
)
30+
from app.utils import read_json_object
31+
from ..tags import GUARDIAN_ONLY
32+
33+
router = APIRouter()
34+
35+
identity = lambda message, key: message
36+
37+
38+
@router.post("", response_model=Guardian, tags=[GUARDIAN_ONLY])
39+
def create_guardian(request: GuardianRequest) -> Guardian:
40+
"""
41+
Create a guardian for the election process with the associated keys
42+
"""
43+
election_keys = generate_election_key_pair(
44+
request.quorum,
45+
int_to_q_unchecked(request.nonce) if request.nonce is not None else None,
46+
)
47+
if request.auxiliary_key_pair is None:
48+
auxiliary_keys = generate_rsa_auxiliary_key_pair()
49+
else:
50+
auxiliary_keys = request.auxiliary_key_pair
51+
if not election_keys:
52+
raise HTTPException(
53+
status_code=500,
54+
detail="Election keys failed to be generated",
55+
)
56+
if not auxiliary_keys:
57+
raise HTTPException(
58+
status_code=500, detail="Auxiliary keys failed to be generated"
59+
)
60+
return Guardian(
61+
id=request.id,
62+
sequence_order=request.sequence_order,
63+
number_of_guardians=request.number_of_guardians,
64+
quorum=request.quorum,
65+
election_key_pair=ElectionKeyPair(
66+
public_key=str(election_keys.key_pair.public_key),
67+
secret_key=str(election_keys.key_pair.secret_key),
68+
proof=write_json_object(election_keys.proof),
69+
polynomial=write_json_object(election_keys.polynomial),
70+
),
71+
auxiliary_key_pair=AuxiliaryKeyPair(
72+
public_key=auxiliary_keys.public_key, secret_key=auxiliary_keys.secret_key
73+
),
74+
)
75+
76+
77+
@router.post("/backup", response_model=GuardianBackup, tags=[GUARDIAN_ONLY])
78+
def create_guardian_backup(request: GuardianBackupRequest) -> GuardianBackup:
79+
"""
80+
Generate all election partial key backups based on existing public keys
81+
:param request: Guardian backup request
82+
:return: Guardian backup
83+
"""
84+
encrypt = identity if request.override_rsa else rsa_encrypt
85+
backups: List[Any] = []
86+
for auxiliary_public_key in request.auxiliary_public_keys:
87+
backup = generate_election_partial_key_backup(
88+
request.guardian_id,
89+
read_json_object(request.election_polynomial, ElectionPolynomial),
90+
AuxiliaryPublicKey(
91+
auxiliary_public_key.owner_id,
92+
auxiliary_public_key.sequence_order,
93+
auxiliary_public_key.key,
94+
),
95+
encrypt,
96+
)
97+
if not backup:
98+
raise HTTPException(status_code=500, detail="Backup failed to be generated")
99+
backups.append(write_json_object(backup))
100+
101+
return GuardianBackup(
102+
id=request.guardian_id,
103+
election_partial_key_backups=backups,
104+
)
105+
106+
107+
@router.post("/backup/verify", tags=[GUARDIAN_ONLY])
108+
def verify_backup(request: BackupVerificationRequest) -> Any:
109+
decrypt = identity if request.override_rsa else rsa_decrypt
110+
verification = verify_election_partial_key_backup(
111+
request.verifier_id,
112+
read_json_object(request.election_partial_key_backup, ElectionPartialKeyBackup),
113+
read_json_object(request.auxiliary_key_pair, AuxiliaryKeyPair),
114+
decrypt,
115+
)
116+
if not verification:
117+
raise HTTPException(
118+
status_code=500, detail="Backup verification process failed"
119+
)
120+
return write_json_object(verification)
121+
122+
123+
@router.post("/challenge", tags=[GUARDIAN_ONLY])
124+
def create_backup_challenge(request: BackupChallengeRequest) -> Any:
125+
challenge = generate_election_partial_key_challenge(
126+
read_json_object(request.election_partial_key_backup, ElectionPartialKeyBackup),
127+
read_json_object(request.election_polynomial, ElectionPolynomial),
128+
)
129+
if not challenge:
130+
raise HTTPException(
131+
status_code=500, detail="Backup challenge generation failed"
132+
)
133+
# FIXME Challenge value is ElementModQ converted to int that is too large
134+
challenge._replace(value=str(challenge.value))
135+
return write_json_object(challenge)
136+
137+
138+
@router.post(
139+
"/challenge/verify",
140+
tags=[GUARDIAN_ONLY],
141+
)
142+
def verify_challenge(request: ChallengeVerificationRequest) -> Any:
143+
verification = verify_election_partial_key_challenge(
144+
request.verifier_id,
145+
read_json_object(
146+
request.election_partial_key_challenge, ElectionPartialKeyChallenge
147+
),
148+
)
149+
if not verification:
150+
raise HTTPException(
151+
status_code=500, detail="Challenge verification process failed"
152+
)
153+
return write_json_object(verification)

β€Žapp/api/v1/endpoints/key.pyβ€Ž

Lines changed: 35 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,76 +2,29 @@
22
generate_election_key_pair,
33
generate_rsa_auxiliary_key_pair,
44
)
5+
from electionguard.elgamal import elgamal_combine_public_keys
56
from electionguard.serializable import write_json_object
6-
from electionguard.group import int_to_q_unchecked
7+
from electionguard.group import int_to_q_unchecked, int_to_p_unchecked
78
from fastapi import APIRouter, HTTPException
8-
from pydantic import BaseModel
9-
from typing import Optional, Any
9+
from ..models import (
10+
AuxiliaryKeyPair,
11+
ElectionKeyPair,
12+
ElectionKeyPairRequest,
13+
ElectionJointKey,
14+
CombineElectionKeysRequest,
15+
)
1016

1117
router = APIRouter()
1218

1319

14-
class ElectionKeyPairRequest(BaseModel):
15-
quorum: int
16-
nonce: Optional[str] = None
17-
18-
19-
class ElectionKeyPairResponse(BaseModel):
20-
secret_key: str
21-
public_key: str
22-
proof: Any
23-
polynomial: Any
24-
25-
26-
class AuxiliaryKeyPairResponse(BaseModel):
27-
secret_key: str
28-
public_key: str
29-
30-
31-
GuardianKeysRequest = ElectionKeyPairRequest
32-
33-
34-
class GuardianKeysResponse(BaseModel):
35-
election: ElectionKeyPairResponse
36-
auxiliary: AuxiliaryKeyPairResponse
37-
38-
39-
@router.post("/generate", response_model=GuardianKeysResponse)
40-
def generate_keys(request: GuardianKeysRequest) -> GuardianKeysResponse:
41-
"""
42-
Generate election key pairs for use in election process
43-
"""
44-
election_keys = generate_election_key_pair(
45-
request.quorum,
46-
int_to_q_unchecked(request.nonce) if request.nonce is not None else None,
47-
)
48-
auxiliary_keys = generate_rsa_auxiliary_key_pair()
49-
if not election_keys:
50-
raise HTTPException(
51-
status_code=500,
52-
detail="Election keys failed to be generated",
53-
)
54-
if not auxiliary_keys:
55-
raise HTTPException(
56-
status_code=500, detail="Auxiliary keys failed to be generated"
57-
)
58-
return GuardianKeysResponse(
59-
election=ElectionKeyPairResponse(
60-
public_key=str(election_keys.key_pair.public_key),
61-
secret_key=str(election_keys.key_pair.secret_key),
62-
proof=write_json_object(election_keys.proof),
63-
polynomial=write_json_object(election_keys.polynomial),
64-
),
65-
auxiliary=AuxiliaryKeyPairResponse(
66-
public_key=auxiliary_keys.public_key, secret_key=auxiliary_keys.secret_key
67-
),
68-
)
69-
70-
71-
@router.post("/election/generate", response_model=ElectionKeyPairResponse)
72-
def generate_election_keys(request: ElectionKeyPairRequest) -> ElectionKeyPairResponse:
20+
@router.post(
21+
"/election/generate", response_model=ElectionKeyPair, tags=["Guardian Only"]
22+
)
23+
def generate_election_keys(request: ElectionKeyPairRequest) -> ElectionKeyPair:
7324
"""
7425
Generate election key pairs for use in election process
26+
:param request: Election key pair request
27+
:return: Election key pair
7528
"""
7629
keys = generate_election_key_pair(
7730
request.quorum,
@@ -82,24 +35,38 @@ def generate_election_keys(request: ElectionKeyPairRequest) -> ElectionKeyPairRe
8235
status_code=500,
8336
detail="Election keys failed to be generated",
8437
)
85-
return ElectionKeyPairResponse(
38+
return ElectionKeyPair(
8639
public_key=str(keys.key_pair.public_key),
8740
secret_key=str(keys.key_pair.secret_key),
8841
proof=write_json_object(keys.proof),
8942
polynomial=write_json_object(keys.polynomial),
9043
)
9144

9245

93-
@router.post("/auxiliary/generate", response_model=AuxiliaryKeyPairResponse)
94-
def generate_auxiliary_keys() -> AuxiliaryKeyPairResponse:
46+
@router.post(
47+
"/auxiliary/generate", response_model=AuxiliaryKeyPair, tags=["Guardian Only"]
48+
)
49+
def generate_auxiliary_keys() -> AuxiliaryKeyPair:
9550
"""
9651
Generate auxiliary key pair for auxiliary uses during process
52+
:return: Auxiliary key pair
9753
"""
9854
keys = generate_rsa_auxiliary_key_pair()
9955
if not keys:
10056
raise HTTPException(
10157
status_code=500, detail="Auxiliary keys failed to be generated"
10258
)
103-
return AuxiliaryKeyPairResponse(
104-
public_key=keys.public_key, secret_key=keys.secret_key
105-
)
59+
return AuxiliaryKeyPair(public_key=keys.public_key, secret_key=keys.secret_key)
60+
61+
62+
@router.post("/election/combine", response_model=ElectionJointKey)
63+
def combine_election_keys(request: CombineElectionKeysRequest) -> ElectionJointKey:
64+
"""
65+
Combine public election keys into a final one
66+
:return: Combine Election key
67+
"""
68+
public_keys = []
69+
for key in request.election_public_keys:
70+
public_keys.append(int_to_p_unchecked(key))
71+
joint_key = elgamal_combine_public_keys(public_keys)
72+
return ElectionJointKey(joint_key=write_json_object(joint_key))

β€Žapp/api/v1/endpoints/tally.pyβ€Ž

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,12 @@
1010
PublishedCiphertextTally,
1111
)
1212
from fastapi import APIRouter, Body, HTTPException
13-
from pydantic import BaseModel
1413
from typing import Any, List, Tuple
1514

16-
router = APIRouter()
17-
1815

19-
class StartTallyRequest(BaseModel):
20-
ballots: List[Any]
21-
description: Any
22-
context: Any
16+
from ..models import StartTallyRequest, AppendTallyRequest
2317

24-
25-
class AppendTallyRequest(StartTallyRequest):
26-
encrypted_tally: Any
18+
router = APIRouter()
2719

2820

2921
@router.post("")
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from .ballot import *
2+
from .base import *
3+
from .guardian import *
4+
from .key import *
5+
from .tally import *

β€Žapp/api/v1/models/ballot.pyβ€Ž

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from typing import Any
2+
3+
from .base import Base
4+
5+
6+
class AcceptBallotRequest(Base):
7+
ballot: Any
8+
description: Any
9+
context: Any

β€Žapp/api/v1/models/base.pyβ€Ž

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from pydantic import BaseModel
2+
3+
4+
class Base(BaseModel):
5+
pass

0 commit comments

Comments
Β (0)