Tally Decryption (#161)
* make find functions posts add an allowed route * Add Ballot Inventory * update docstring * refactor election cache queries to their own file * Add Tally/Decrypt Add tally/encrypt functions. rework some of the ballot and election api's to be more consistent. Add sample data. * update the postman collection * clean up imports, docs * more doc cleanup
This commit is contained in:
Родитель
bee0f8d2e4
Коммит
28444eda9b
|
@ -142,3 +142,6 @@ cython_debug/
|
|||
|
||||
# Mac
|
||||
.DS_Store
|
||||
|
||||
# Project
|
||||
storage/
|
||||
|
|
|
@ -13,7 +13,9 @@
|
|||
],
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceRoot}",
|
||||
"API_MODE": "guardian"
|
||||
"API_MODE": "guardian",
|
||||
"QUEUE_MODE": "remote",
|
||||
"STORAGE_MODE": "mongo"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -28,7 +30,9 @@
|
|||
],
|
||||
"env": {
|
||||
"PYTHONPATH": "${workspaceRoot}",
|
||||
"API_MODE": "mediator"
|
||||
"API_MODE": "mediator",
|
||||
"QUEUE_MODE": "remote",
|
||||
"STORAGE_MODE": "mongo"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
4
Makefile
4
Makefile
|
@ -102,8 +102,8 @@ start:
|
|||
|
||||
start-server:
|
||||
docker compose -f docker-compose.support.yml up -d
|
||||
QUEUE_MODE = remote
|
||||
STORAGE_MODE = mongo
|
||||
QUEUE_MODE=remote
|
||||
STORAGE_MODE=mongo
|
||||
poetry run uvicorn app.main:app --reload --port $(PORT)
|
||||
|
||||
stop:
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
from typing import Any
|
||||
from electionguard.ballot import SubmittedBallot
|
||||
from electionguard.decryption import compute_decryption_share_for_ballot
|
||||
from electionguard.election import CiphertextElectionContext
|
||||
|
@ -17,11 +16,13 @@ from ..tags import TALLY
|
|||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/decrypt-shares", tags=[TALLY])
|
||||
@router.post(
|
||||
"/decrypt-shares", response_model=DecryptBallotSharesResponse, tags=[TALLY]
|
||||
)
|
||||
def decrypt_ballot_shares(
|
||||
request: DecryptBallotSharesRequest = Body(...),
|
||||
scheduler: Scheduler = Depends(get_scheduler),
|
||||
) -> Any:
|
||||
) -> DecryptBallotSharesResponse:
|
||||
"""
|
||||
Decrypt this guardian's share of one or more ballots
|
||||
"""
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
from fastapi import APIRouter
|
||||
from . import ballot
|
||||
from . import guardian
|
||||
from . import tally
|
||||
from . import tally_decrypt
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
router.include_router(guardian.router, prefix="/guardian")
|
||||
router.include_router(ballot.router, prefix="/ballot")
|
||||
router.include_router(tally.router, prefix="/tally")
|
||||
router.include_router(tally_decrypt.router, prefix="/tally")
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
from typing import Any
|
||||
from electionguard.decryption import compute_decryption_share
|
||||
from electionguard.election import CiphertextElectionContext
|
||||
from electionguard.key_ceremony import generate_election_key_pair
|
||||
from electionguard.manifest import InternalManifest, Manifest
|
||||
from electionguard.scheduler import Scheduler
|
||||
from electionguard.serializable import write_json_object
|
||||
from fastapi import APIRouter, Body, Depends
|
||||
|
||||
from app.core.scheduler import get_scheduler
|
||||
from ..models import (
|
||||
to_sdk_guardian,
|
||||
convert_tally,
|
||||
DecryptTallyShareRequest,
|
||||
)
|
||||
from ..tags import TALLY
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/decrypt-share", tags=[TALLY])
|
||||
def decrypt_share(
|
||||
request: DecryptTallyShareRequest = Body(...),
|
||||
scheduler: Scheduler = Depends(get_scheduler),
|
||||
) -> Any:
|
||||
"""
|
||||
Decrypt a single guardian's share of a tally
|
||||
"""
|
||||
description = InternalManifest(Manifest.from_json_object(request.description))
|
||||
context = CiphertextElectionContext.from_json_object(request.context)
|
||||
guardian = to_sdk_guardian(request.guardian)
|
||||
tally = convert_tally(request.encrypted_tally, description, context)
|
||||
election_key_pair = generate_election_key_pair(
|
||||
guardian.id, guardian.sequence_order, guardian.ceremony_details.quorum
|
||||
)
|
||||
|
||||
share = compute_decryption_share(election_key_pair, tally, context, scheduler)
|
||||
|
||||
return write_json_object(share)
|
|
@ -0,0 +1,100 @@
|
|||
# pylint: disable=unused-argument
|
||||
from datetime import datetime
|
||||
from typing import Dict
|
||||
from electionguard.election import CiphertextElectionContext
|
||||
from electionguard.scheduler import Scheduler
|
||||
from electionguard.manifest import ElectionType, Manifest, InternalManifest
|
||||
from electionguard.tally import CiphertextTally, CiphertextTallyContest
|
||||
from electionguard.serializable import read_json_object, write_json_object
|
||||
from electionguard.types import CONTEST_ID
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException, Request, status
|
||||
|
||||
from app.core.scheduler import get_scheduler
|
||||
from app.core.guardian import get_guardian
|
||||
from app.core.tally_decrypt import get_decryption_share, set_decryption_share
|
||||
from ..models import (
|
||||
to_sdk_guardian,
|
||||
DecryptTallyShareRequest,
|
||||
CiphertextTallyDecryptionShare,
|
||||
DecryptionShareResponse,
|
||||
)
|
||||
from ..tags import TALLY_DECRYPT
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/decrypt-share", response_model=DecryptionShareResponse, tags=[TALLY_DECRYPT]
|
||||
)
|
||||
def fetch_decrypt_share(
|
||||
request: Request,
|
||||
election_id: str,
|
||||
tally_name: str,
|
||||
) -> DecryptionShareResponse:
|
||||
"""
|
||||
Fetch A decryption share for a given tally
|
||||
"""
|
||||
share = get_decryption_share(election_id, tally_name, request.app.state.settings)
|
||||
return DecryptionShareResponse(shares=[share])
|
||||
|
||||
|
||||
@router.post(
|
||||
"/decrypt-share", response_model=DecryptionShareResponse, tags=[TALLY_DECRYPT]
|
||||
)
|
||||
def decrypt_share(
|
||||
request: Request,
|
||||
data: DecryptTallyShareRequest = Body(...),
|
||||
scheduler: Scheduler = Depends(get_scheduler),
|
||||
) -> DecryptionShareResponse:
|
||||
"""
|
||||
Decrypt a single guardian's share of a tally
|
||||
"""
|
||||
guardian = get_guardian(data.guardian_id, request.app.state.settings)
|
||||
context = CiphertextElectionContext.from_json_object(data.context)
|
||||
|
||||
# TODO: HACK: Remove The Empty Manifest
|
||||
# Note: The CiphertextTally requires an internal manifest passed into its constructor
|
||||
# but it is not actually used when executing `compute_decryption_share` so we create a fake.
|
||||
# see: https://github.com/microsoft/electionguard-python/issues/391
|
||||
internal_manifest = InternalManifest(
|
||||
Manifest(
|
||||
"",
|
||||
"",
|
||||
ElectionType.other,
|
||||
datetime.now(),
|
||||
datetime.now(),
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
)
|
||||
)
|
||||
tally = CiphertextTally(data.encrypted_tally.tally_name, internal_manifest, context)
|
||||
contests: Dict[CONTEST_ID, CiphertextTallyContest] = {
|
||||
contest_id: read_json_object(contest, CiphertextTallyContest)
|
||||
for contest_id, contest in data.encrypted_tally.tally["contests"].items()
|
||||
}
|
||||
tally.contests = contests
|
||||
|
||||
# TODO: modify compute_tally_share to include an optional scheduler param
|
||||
sdk_guardian = to_sdk_guardian(guardian)
|
||||
sdk_tally_share = sdk_guardian.compute_tally_share(tally, context)
|
||||
|
||||
if not sdk_tally_share:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Could not compute tally share",
|
||||
)
|
||||
|
||||
share = CiphertextTallyDecryptionShare(
|
||||
election_id=data.encrypted_tally.election_id,
|
||||
tally_name=data.encrypted_tally.tally_name,
|
||||
guardian_id=guardian.guardian_id,
|
||||
tally_share=write_json_object(sdk_tally_share),
|
||||
# TODO: include spoiled ballots
|
||||
)
|
||||
|
||||
set_decryption_share(share, request.app.state.settings)
|
||||
|
||||
return DecryptionShareResponse(shares=[share])
|
|
@ -1,7 +1,7 @@
|
|||
from typing import Any, List, Optional, Tuple
|
||||
from typing import List, Optional, Tuple
|
||||
import sys
|
||||
|
||||
from fastapi import APIRouter, Body, HTTPException, status
|
||||
from fastapi import APIRouter, Body, HTTPException, Request, status
|
||||
|
||||
from electionguard.ballot import (
|
||||
SubmittedBallot,
|
||||
|
@ -14,17 +14,26 @@ from electionguard.election import CiphertextElectionContext
|
|||
from electionguard.manifest import InternalManifest, Manifest
|
||||
from electionguard.serializable import write_json_object
|
||||
|
||||
from ....core.ballot import (
|
||||
filter_ballots,
|
||||
get_ballot,
|
||||
set_ballots,
|
||||
get_ballot_inventory,
|
||||
upsert_ballot_inventory,
|
||||
)
|
||||
from ....core.election import get_election
|
||||
from ....core.repository import get_repository, DataCollection
|
||||
from ....core.settings import Settings
|
||||
from ....core.queue import get_message_queue, IMessageQueue
|
||||
from ..models import (
|
||||
BaseResponse,
|
||||
BaseQueryRequest,
|
||||
BaseBallotRequest,
|
||||
BallotInventoryResponse,
|
||||
BallotQueryResponse,
|
||||
CastBallotsRequest,
|
||||
SpoilBallotsRequest,
|
||||
SubmitBallotsRequest,
|
||||
SubmitBallotsResponse,
|
||||
ValidateBallotRequest,
|
||||
)
|
||||
from ..tags import BALLOTS
|
||||
|
@ -32,62 +41,77 @@ from ..tags import BALLOTS
|
|||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("", tags=[BALLOTS])
|
||||
def get_ballot(election_id: str, ballot_id: str) -> BallotQueryResponse:
|
||||
@router.get("", response_model=BallotQueryResponse, tags=[BALLOTS])
|
||||
def fetch_ballot(
|
||||
request: Request, election_id: str, ballot_id: str
|
||||
) -> BallotQueryResponse:
|
||||
"""
|
||||
Get A Ballot for a specific election
|
||||
Fetch A Ballot for a specific election.
|
||||
"""
|
||||
with get_repository(election_id, DataCollection.SUBMITTED_BALLOT) as repository:
|
||||
ballot = repository.get({"object_id": ballot_id})
|
||||
if not ballot:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Could not find ballot {ballot_id}",
|
||||
)
|
||||
|
||||
return BallotQueryResponse(
|
||||
election_id=election_id,
|
||||
ballots=[write_json_object(ballot)],
|
||||
)
|
||||
ballot = get_ballot(election_id, ballot_id, request.app.state.settings)
|
||||
return BallotQueryResponse(
|
||||
election_id=election_id,
|
||||
ballots=[ballot.to_json_object()],
|
||||
)
|
||||
|
||||
|
||||
@router.get("/find", tags=[BALLOTS])
|
||||
@router.get("/inventory", response_model=BallotInventoryResponse, tags=[BALLOTS])
|
||||
def fetch_ballot_inventory(
|
||||
request: Request, election_id: str
|
||||
) -> BallotInventoryResponse:
|
||||
"""
|
||||
Fetch the Ballot Inventory for a specific election.
|
||||
"""
|
||||
inventory = get_ballot_inventory(election_id, request.app.state.settings)
|
||||
|
||||
return BallotInventoryResponse(
|
||||
inventory=inventory,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/find", response_model=BallotQueryResponse, tags=[BALLOTS])
|
||||
def find_ballots(
|
||||
request: Request,
|
||||
election_id: str,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
request: BaseQueryRequest = Body(...),
|
||||
data: BaseQueryRequest = Body(...),
|
||||
) -> BallotQueryResponse:
|
||||
"""Find Ballots."""
|
||||
try:
|
||||
filter = write_json_object(request.filter) if request.filter else {}
|
||||
with get_repository(election_id, DataCollection.SUBMITTED_BALLOT) as repository:
|
||||
cursor = repository.find(filter, skip, limit)
|
||||
ballots: List[Any] = []
|
||||
for item in cursor:
|
||||
ballots.append(write_json_object(item))
|
||||
return BallotQueryResponse(ballots=ballots)
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="find guardians failed",
|
||||
) from error
|
||||
"""
|
||||
Find Ballots.
|
||||
|
||||
Search the repository for ballots that match the filter criteria specified in the request body.
|
||||
If no filter criteria is specified the API will iterate all available data.
|
||||
"""
|
||||
filter = write_json_object(data.filter) if data.filter else {}
|
||||
ballots = filter_ballots(
|
||||
election_id, filter, skip, limit, request.app.state.settings
|
||||
)
|
||||
return BallotQueryResponse(
|
||||
election_id=election_id, ballots=[ballot.to_json_object() for ballot in ballots]
|
||||
)
|
||||
|
||||
|
||||
@router.post("/cast", tags=[BALLOTS], status_code=status.HTTP_202_ACCEPTED)
|
||||
def cast_ballot(
|
||||
election_id: Optional[str] = None, request: CastBallotsRequest = Body(...)
|
||||
) -> SubmitBallotsResponse:
|
||||
@router.post(
|
||||
"/cast",
|
||||
response_model=BaseResponse,
|
||||
tags=[BALLOTS],
|
||||
status_code=status.HTTP_202_ACCEPTED,
|
||||
)
|
||||
def cast_ballots(
|
||||
request: Request,
|
||||
election_id: Optional[str] = None,
|
||||
data: CastBallotsRequest = Body(...),
|
||||
) -> BaseResponse:
|
||||
"""
|
||||
Cast ballot
|
||||
"""
|
||||
manifest, context, election_id = _get_election_parameters(election_id, request)
|
||||
manifest, context, election_id = _get_election_parameters(election_id, data)
|
||||
ballots = [
|
||||
from_ciphertext_ballot(
|
||||
CiphertextBallot.from_json_object(ballot), BallotBoxState.CAST
|
||||
)
|
||||
for ballot in request.ballots
|
||||
for ballot in data.ballots
|
||||
]
|
||||
|
||||
for ballot in ballots:
|
||||
|
@ -96,22 +120,29 @@ def cast_ballot(
|
|||
)
|
||||
_validate_ballot(validation_request)
|
||||
|
||||
return _submit_ballots(election_id, ballots)
|
||||
return _submit_ballots(election_id, ballots, request.app.state.settings)
|
||||
|
||||
|
||||
@router.post("/spoil", tags=[BALLOTS], status_code=status.HTTP_202_ACCEPTED)
|
||||
def spoil_ballot(
|
||||
election_id: Optional[str] = None, request: SpoilBallotsRequest = Body(...)
|
||||
@router.post(
|
||||
"/spoil",
|
||||
response_model=BaseResponse,
|
||||
tags=[BALLOTS],
|
||||
status_code=status.HTTP_202_ACCEPTED,
|
||||
)
|
||||
def spoil_ballots(
|
||||
request: Request,
|
||||
election_id: Optional[str] = None,
|
||||
data: SpoilBallotsRequest = Body(...),
|
||||
) -> BaseResponse:
|
||||
"""
|
||||
Spoil ballot
|
||||
"""
|
||||
manifest, context, election_id = _get_election_parameters(election_id, request)
|
||||
manifest, context, election_id = _get_election_parameters(election_id, data)
|
||||
ballots = [
|
||||
from_ciphertext_ballot(
|
||||
CiphertextBallot.from_json_object(ballot), BallotBoxState.SPOILED
|
||||
)
|
||||
for ballot in request.ballots
|
||||
for ballot in data.ballots
|
||||
]
|
||||
|
||||
for ballot in ballots:
|
||||
|
@ -120,14 +151,20 @@ def spoil_ballot(
|
|||
)
|
||||
_validate_ballot(validation_request)
|
||||
|
||||
return _submit_ballots(election_id, ballots)
|
||||
return _submit_ballots(election_id, ballots, request.app.state.settings)
|
||||
|
||||
|
||||
@router.put("/submit", tags=[BALLOTS], status_code=status.HTTP_202_ACCEPTED)
|
||||
@router.put(
|
||||
"/submit",
|
||||
response_model=BaseResponse,
|
||||
tags=[BALLOTS],
|
||||
status_code=status.HTTP_202_ACCEPTED,
|
||||
)
|
||||
def submit_ballots(
|
||||
request: Request,
|
||||
election_id: Optional[str] = None,
|
||||
request: SubmitBallotsRequest = Body(...),
|
||||
) -> SubmitBallotsResponse:
|
||||
data: SubmitBallotsRequest = Body(...),
|
||||
) -> BaseResponse:
|
||||
"""
|
||||
Submit ballots for an election.
|
||||
|
||||
|
@ -135,9 +172,12 @@ def submit_ballots(
|
|||
If both are provied, the query string will override.
|
||||
"""
|
||||
|
||||
manifest, context, election_id = _get_election_parameters(election_id, request)
|
||||
manifest, context, election_id = _get_election_parameters(
|
||||
election_id, data, request.app.state.settings
|
||||
)
|
||||
|
||||
ballots = [SubmittedBallot.from_json_object(ballot) for ballot in request.ballots]
|
||||
# Check each ballot's state and validate
|
||||
ballots = [SubmittedBallot.from_json_object(ballot) for ballot in data.ballots]
|
||||
for ballot in ballots:
|
||||
if ballot.state == BallotBoxState.UNKNOWN:
|
||||
raise HTTPException(
|
||||
|
@ -149,10 +189,10 @@ def submit_ballots(
|
|||
)
|
||||
_validate_ballot(validation_request)
|
||||
|
||||
return _submit_ballots(election_id, ballots)
|
||||
return _submit_ballots(election_id, ballots, request.app.state.settings)
|
||||
|
||||
|
||||
@router.post("/validate", tags=[BALLOTS])
|
||||
@router.post("/validate", response_model=BaseResponse, tags=[BALLOTS])
|
||||
def validate_ballot(
|
||||
request: ValidateBallotRequest = Body(...),
|
||||
) -> BaseResponse:
|
||||
|
@ -164,55 +204,53 @@ def validate_ballot(
|
|||
|
||||
|
||||
def _get_election_parameters(
|
||||
election_id: Optional[str], request: BaseBallotRequest
|
||||
election_id: Optional[str],
|
||||
request_data: BaseBallotRequest,
|
||||
settings: Settings = Settings(),
|
||||
) -> Tuple[Manifest, CiphertextElectionContext, str]:
|
||||
"""Get the election parameters either from the data cache or from the request body."""
|
||||
|
||||
# Check an election is assigned
|
||||
if not election_id:
|
||||
election_id = request.election_id
|
||||
election_id = request_data.election_id
|
||||
|
||||
if not election_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="specify election_id in the query parameter or request body",
|
||||
detail="specify election_id in the query parameter or request body.",
|
||||
)
|
||||
|
||||
# TODO: load validation from repository
|
||||
if not request.manifest:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
||||
detail="Query for Manifest Not Yet Implemented",
|
||||
)
|
||||
election = get_election(election_id, settings)
|
||||
|
||||
if not request.context:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_501_NOT_IMPLEMENTED,
|
||||
detail="Query for Context Not Yet Implemented",
|
||||
)
|
||||
if request_data.manifest:
|
||||
manifest = request_data.manifest
|
||||
else:
|
||||
manifest = Manifest.from_json_object(election.manifest)
|
||||
|
||||
if request_data.context:
|
||||
context = request_data.context
|
||||
else:
|
||||
context = CiphertextElectionContext.from_json_object(election.context)
|
||||
|
||||
manifest = request.manifest
|
||||
context = request.context
|
||||
return manifest, context, election_id
|
||||
|
||||
|
||||
def _submit_ballots(
|
||||
election_id: str,
|
||||
ballots: List[SubmittedBallot],
|
||||
) -> SubmitBallotsResponse:
|
||||
try:
|
||||
with get_repository(election_id, DataCollection.SUBMITTED_BALLOT) as repository:
|
||||
cacheable_ballots = [ballot.to_json_object() for ballot in ballots]
|
||||
keys = repository.set(cacheable_ballots)
|
||||
return SubmitBallotsResponse(
|
||||
message="Ballot Successfully Submitted",
|
||||
cache_keys=keys,
|
||||
election_id=election_id,
|
||||
)
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Submit ballots failed",
|
||||
) from error
|
||||
election_id: str, ballots: List[SubmittedBallot], settings: Settings = Settings()
|
||||
) -> BaseResponse:
|
||||
set_response = set_ballots(election_id, ballots, settings)
|
||||
if set_response.is_success():
|
||||
inventory = get_ballot_inventory(election_id, settings)
|
||||
for ballot in ballots:
|
||||
if ballot.state == BallotBoxState.CAST:
|
||||
inventory.cast_ballot_count += 1
|
||||
inventory.cast_ballots[ballot.code.to_hex()] = ballot.object_id
|
||||
elif ballot.state == BallotBoxState.SPOILED:
|
||||
inventory.spoiled_ballot_count += 1
|
||||
inventory.spoiled_ballots[ballot.code.to_hex()] = ballot.object_id
|
||||
upsert_ballot_inventory(election_id, inventory, settings)
|
||||
|
||||
return set_response
|
||||
|
||||
|
||||
def _validate_ballot(request: ValidateBallotRequest) -> None:
|
||||
|
@ -224,7 +262,7 @@ def _validate_ballot(request: ValidateBallotRequest) -> None:
|
|||
if not ballot_is_valid_for_election(ballot, internal_manifest, context):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_406_NOT_ACCEPTABLE,
|
||||
detail=f"ballot {ballot.object_id} is not valid",
|
||||
detail=f"ballot {ballot.object_id} is not valid.",
|
||||
)
|
||||
|
||||
|
||||
|
@ -240,7 +278,7 @@ def test_submit_ballot(
|
|||
if not request.election_id:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Must submit an election id",
|
||||
detail="Must submit an election id.",
|
||||
)
|
||||
|
||||
try:
|
||||
|
@ -255,9 +293,9 @@ def test_submit_ballot(
|
|||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Ballot failed to be submitted",
|
||||
detail="Ballot failed to be submitted.",
|
||||
) from error
|
||||
return BaseResponse(message="Ballot Successfully Submitted")
|
||||
return BaseResponse(message="Ballot Successfully Submitted.")
|
||||
|
||||
|
||||
def _process_ballots(queue: IMessageQueue, election_id: str) -> None:
|
||||
|
@ -271,5 +309,5 @@ def _process_ballots(queue: IMessageQueue, election_id: str) -> None:
|
|||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Ballot failed to be processed",
|
||||
detail="Ballot failed to be processed.",
|
||||
) from error
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
from typing import Any, List, Optional
|
||||
from typing import Any
|
||||
from uuid import uuid4
|
||||
import sys
|
||||
|
||||
|
||||
from fastapi import APIRouter, Body, HTTPException, status
|
||||
from fastapi import APIRouter, Body, HTTPException, Request, status
|
||||
|
||||
from electionguard.election import (
|
||||
ElectionConstants,
|
||||
|
@ -13,12 +12,20 @@ from electionguard.group import ElementModP, ElementModQ
|
|||
from electionguard.election import CiphertextElectionContext
|
||||
from electionguard.manifest import Manifest
|
||||
from electionguard.serializable import read_json_object, write_json_object
|
||||
from electionguard.utils import get_optional
|
||||
|
||||
from .manifest import get_manifest
|
||||
from ....core.client import get_client_id
|
||||
from ....core.repository import get_repository, DataCollection
|
||||
from ....core.ballot import upsert_ballot_inventory
|
||||
from ....core.key_ceremony import get_key_ceremony
|
||||
from ....core.election import (
|
||||
get_election,
|
||||
set_election,
|
||||
update_election_state,
|
||||
filter_elections,
|
||||
)
|
||||
from ..models import (
|
||||
BaseResponse,
|
||||
BallotInventory,
|
||||
Election,
|
||||
ElectionState,
|
||||
ElectionQueryRequest,
|
||||
|
@ -26,7 +33,6 @@ from ..models import (
|
|||
MakeElectionContextRequest,
|
||||
MakeElectionContextResponse,
|
||||
SubmitElectionRequest,
|
||||
SubmitElectionResponse,
|
||||
)
|
||||
from ..tags import ELECTION
|
||||
|
||||
|
@ -36,96 +42,80 @@ router = APIRouter()
|
|||
@router.get("/constants", tags=[ELECTION])
|
||||
def get_election_constants() -> Any:
|
||||
"""
|
||||
Return the constants defined for an election
|
||||
Get the constants defined for an election.
|
||||
"""
|
||||
constants = ElectionConstants()
|
||||
return constants.to_json_object()
|
||||
|
||||
|
||||
@router.get("", response_model=ElectionQueryResponse, tags=[ELECTION])
|
||||
def get_election(election_id: str) -> ElectionQueryResponse:
|
||||
"""Get an election by election id"""
|
||||
try:
|
||||
with get_repository(get_client_id(), DataCollection.ELECTION) as repository:
|
||||
query_result = repository.get({"election_id": election_id})
|
||||
if not query_result:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Could not find election {election_id}",
|
||||
)
|
||||
election = Election(
|
||||
election_id=query_result["election_id"],
|
||||
state=query_result["state"],
|
||||
context=query_result["context"],
|
||||
manifest=query_result["manifest"],
|
||||
)
|
||||
|
||||
return ElectionQueryResponse(
|
||||
elections=[election],
|
||||
)
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="get election failed",
|
||||
) from error
|
||||
def fetch_election(request: Request, election_id: str) -> ElectionQueryResponse:
|
||||
"""Get an election by election id."""
|
||||
election = get_election(election_id, request.app.state.settings)
|
||||
return ElectionQueryResponse(
|
||||
elections=[election],
|
||||
)
|
||||
|
||||
|
||||
@router.put("", response_model=SubmitElectionResponse, tags=[ELECTION])
|
||||
@router.put("", response_model=BaseResponse, tags=[ELECTION])
|
||||
def create_election(
|
||||
election_id: Optional[str], request: SubmitElectionRequest = Body(...)
|
||||
) -> SubmitElectionResponse:
|
||||
request: Request,
|
||||
data: SubmitElectionRequest = Body(...),
|
||||
) -> BaseResponse:
|
||||
"""
|
||||
Submit an election.
|
||||
|
||||
Method expects a manifest to already be submitted or to optionally be provided
|
||||
as part of the request body. If a manifest is provided as part of the body
|
||||
then it will override any cached value, however the hash must match the hash
|
||||
contained in the CiphertextelectionContext
|
||||
contained in the CiphertextelectionContext.
|
||||
"""
|
||||
if not election_id:
|
||||
election_id = request.election_id
|
||||
|
||||
if not election_id:
|
||||
if data.election_id:
|
||||
election_id = data.election_id
|
||||
else:
|
||||
election_id = str(uuid4())
|
||||
|
||||
context = CiphertextElectionContext.from_json_object(request.context)
|
||||
key_ceremony = get_key_ceremony(data.key_name, request.app.state.settings)
|
||||
context = CiphertextElectionContext.from_json_object(data.context)
|
||||
|
||||
if request.manifest:
|
||||
manifest = Manifest.from_json_object(request.manifest)
|
||||
# if a manifest is provided use it, but don't cache it
|
||||
if data.manifest:
|
||||
sdk_manifest = Manifest.from_json_object(data.manifest)
|
||||
else:
|
||||
manifest_query = get_manifest(context.manifest_hash)
|
||||
manifest = Manifest.from_json_object(manifest_query.manifests[0])
|
||||
api_manifest = get_manifest(context.manifest_hash, request.app.state.settings)
|
||||
sdk_manifest = Manifest.from_json_object(api_manifest.manifest)
|
||||
|
||||
# validate that the context was built against the correct manifest
|
||||
if context.manifest_hash != manifest.crypto_hash():
|
||||
if context.manifest_hash != sdk_manifest.crypto_hash().to_hex():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_412_PRECONDITION_FAILED,
|
||||
detail="manifest hash does not match provided context hash",
|
||||
)
|
||||
|
||||
# validate that the context provided matches a known key ceremony
|
||||
if context.elgamal_public_key != key_ceremony.elgamal_public_key:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_412_PRECONDITION_FAILED,
|
||||
detail="key ceremony public key does not match provided context public key",
|
||||
)
|
||||
|
||||
election = Election(
|
||||
election_id=election_id,
|
||||
key_name=data.key_name,
|
||||
state=ElectionState.CREATED,
|
||||
context=context.to_json_object(),
|
||||
manifest=manifest.to_json_object(),
|
||||
manifest=sdk_manifest.to_json_object(),
|
||||
)
|
||||
|
||||
try:
|
||||
with get_repository(get_client_id(), DataCollection.ELECTION) as repository:
|
||||
_ = repository.set(write_json_object(election.dict()))
|
||||
return SubmitElectionResponse(election_id=election_id)
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Submit election failed",
|
||||
) from error
|
||||
return set_election(election, request.app.state.settings)
|
||||
|
||||
|
||||
@router.get("/find", response_model=ElectionQueryResponse, tags=[ELECTION])
|
||||
@router.post("/find", response_model=ElectionQueryResponse, tags=[ELECTION])
|
||||
def find_elections(
|
||||
skip: int = 0, limit: int = 100, request: ElectionQueryRequest = Body(...)
|
||||
request: Request,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
data: ElectionQueryRequest = Body(...),
|
||||
) -> ElectionQueryResponse:
|
||||
"""
|
||||
Find elections.
|
||||
|
@ -133,114 +123,81 @@ def find_elections(
|
|||
Search the repository for elections that match the filter criteria specified in the request body.
|
||||
If no filter criteria is specified the API will iterate all available data.
|
||||
"""
|
||||
try:
|
||||
|
||||
filter = write_json_object(request.filter) if request.filter else {}
|
||||
with get_repository(get_client_id(), DataCollection.ELECTION) as repository:
|
||||
cursor = repository.find(filter, skip, limit)
|
||||
elections: List[Election] = []
|
||||
for item in cursor:
|
||||
elections.append(
|
||||
Election(
|
||||
election_id=item["election_id"],
|
||||
state=item["state"],
|
||||
context=item["context"],
|
||||
manifest=item["manifest"],
|
||||
)
|
||||
)
|
||||
return ElectionQueryResponse(elections=elections)
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="find elections failed",
|
||||
) from error
|
||||
filter = write_json_object(data.filter) if data.filter else {}
|
||||
elections = filter_elections(filter, skip, limit, request.app.state.settings)
|
||||
return ElectionQueryResponse(elections=elections)
|
||||
|
||||
|
||||
@router.post("/open", response_model=BaseResponse, tags=[ELECTION])
|
||||
def open_election(election_id: str) -> BaseResponse:
|
||||
def open_election(request: Request, election_id: str) -> BaseResponse:
|
||||
"""
|
||||
Open an election.
|
||||
"""
|
||||
return _update_election_state(election_id, ElectionState.OPEN)
|
||||
# create the ballot inventory on election open
|
||||
ballot_inventory = BallotInventory(election_id=election_id)
|
||||
upsert_ballot_inventory(election_id, ballot_inventory, request.app.state.settings)
|
||||
|
||||
return update_election_state(
|
||||
election_id, ElectionState.OPEN, request.app.state.settings
|
||||
)
|
||||
|
||||
|
||||
@router.post("/close", response_model=BaseResponse, tags=[ELECTION])
|
||||
def close_election(election_id: str) -> BaseResponse:
|
||||
def close_election(request: Request, election_id: str) -> BaseResponse:
|
||||
"""
|
||||
Close an election.
|
||||
"""
|
||||
return _update_election_state(election_id, ElectionState.CLOSED)
|
||||
return update_election_state(
|
||||
election_id, ElectionState.CLOSED, request.app.state.settings
|
||||
)
|
||||
|
||||
|
||||
@router.post("/publish", response_model=BaseResponse, tags=[ELECTION])
|
||||
def publish_election(election_id: str) -> BaseResponse:
|
||||
def publish_election(request: Request, election_id: str) -> BaseResponse:
|
||||
"""
|
||||
Publish an election
|
||||
Publish an election.
|
||||
"""
|
||||
return _update_election_state(election_id, ElectionState.PUBLISHED)
|
||||
return update_election_state(
|
||||
election_id, ElectionState.PUBLISHED, request.app.state.settings
|
||||
)
|
||||
|
||||
|
||||
@router.post("/context", response_model=MakeElectionContextResponse, tags=[ELECTION])
|
||||
def build_election_context(
|
||||
manifest_hash: Optional[str] = None, request: MakeElectionContextRequest = Body(...)
|
||||
request: Request,
|
||||
data: MakeElectionContextRequest = Body(...),
|
||||
) -> MakeElectionContextResponse:
|
||||
"""
|
||||
Build a CiphertextElectionContext for a given election and returns it.
|
||||
|
||||
Caller must specify the manifest to build against
|
||||
by either providing the manifest hash in the query parameter or request body;
|
||||
or by providing the manifest directly in the request body
|
||||
by either providing the manifest hash in the request body;
|
||||
or by providing the manifest directly in the request body.
|
||||
"""
|
||||
if not manifest_hash:
|
||||
manifest_hash = request.manifest_hash
|
||||
|
||||
if manifest_hash:
|
||||
print(manifest_hash)
|
||||
manifest_query = get_manifest(manifest_hash)
|
||||
manifest = Manifest.from_json_object(manifest_query.manifests[0])
|
||||
if data.manifest:
|
||||
sdk_manifest = Manifest.from_json_object(data.manifest)
|
||||
else:
|
||||
manifest = Manifest.from_json_object(request.manifest)
|
||||
manifest_hash = read_json_object(get_optional(data.manifest_hash), ElementModQ)
|
||||
api_manifest = get_manifest(
|
||||
manifest_hash,
|
||||
request.app.state.settings,
|
||||
)
|
||||
sdk_manifest = Manifest.from_json_object(api_manifest.manifest)
|
||||
|
||||
elgamal_public_key: ElementModP = read_json_object(
|
||||
request.elgamal_public_key, ElementModP
|
||||
data.elgamal_public_key, ElementModP
|
||||
)
|
||||
commitment_hash = read_json_object(request.commitment_hash, ElementModQ)
|
||||
number_of_guardians = request.number_of_guardians
|
||||
quorum = request.quorum
|
||||
commitment_hash = read_json_object(data.commitment_hash, ElementModQ)
|
||||
number_of_guardians = data.number_of_guardians
|
||||
quorum = data.quorum
|
||||
|
||||
context = make_ciphertext_election_context(
|
||||
number_of_guardians,
|
||||
quorum,
|
||||
elgamal_public_key,
|
||||
commitment_hash,
|
||||
manifest.crypto_hash(),
|
||||
sdk_manifest.crypto_hash(),
|
||||
)
|
||||
|
||||
return MakeElectionContextResponse(context=context.to_json_object())
|
||||
|
||||
|
||||
def _update_election_state(election_id: str, new_state: ElectionState) -> BaseResponse:
|
||||
try:
|
||||
with get_repository(get_client_id(), DataCollection.ELECTION) as repository:
|
||||
query_result = repository.get({"election_id": election_id})
|
||||
if not query_result:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Could not find election {election_id}",
|
||||
)
|
||||
election = Election(
|
||||
election_id=query_result["election_id"],
|
||||
state=new_state,
|
||||
context=query_result["context"],
|
||||
manifest=query_result["manifest"],
|
||||
)
|
||||
|
||||
repository.update({"election_id": election_id}, election.dict())
|
||||
return BaseResponse()
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="update election failed",
|
||||
) from error
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from typing import Optional
|
||||
from fastapi import APIRouter, Body, HTTPException, Request, status
|
||||
|
||||
from electionguard.ballot import PlaintextBallot
|
||||
from electionguard.election import CiphertextElectionContext
|
||||
|
@ -7,8 +7,8 @@ from electionguard.encrypt import encrypt_ballot
|
|||
from electionguard.group import ElementModQ
|
||||
from electionguard.serializable import read_json_object, write_json_object
|
||||
from electionguard.utils import get_optional
|
||||
from fastapi import APIRouter, Body, HTTPException
|
||||
|
||||
from app.core.election import get_election
|
||||
from ..models import (
|
||||
EncryptBallotsRequest,
|
||||
EncryptBallotsResponse,
|
||||
|
@ -20,28 +20,31 @@ router = APIRouter()
|
|||
|
||||
@router.post("/encrypt", tags=[ENCRYPT])
|
||||
def encrypt_ballots(
|
||||
request: EncryptBallotsRequest = Body(...),
|
||||
request: Request,
|
||||
data: EncryptBallotsRequest = Body(...),
|
||||
) -> EncryptBallotsResponse:
|
||||
"""
|
||||
Encrypt one or more ballots
|
||||
Encrypt one or more ballots.
|
||||
|
||||
This function is primarily used for testing and does not modify internal state.
|
||||
"""
|
||||
ballots = [PlaintextBallot.from_json_object(ballot) for ballot in request.ballots]
|
||||
description = InternalManifest(Manifest.from_json_object(request.manifest))
|
||||
context = CiphertextElectionContext.from_json_object(request.context)
|
||||
seed_hash = read_json_object(request.seed_hash, ElementModQ)
|
||||
nonce: Optional[ElementModQ] = (
|
||||
read_json_object(request.nonce, ElementModQ) if request.nonce else None
|
||||
)
|
||||
election = get_election(data.election_id, request.app.state.settings)
|
||||
manifest = InternalManifest(Manifest.from_json_object(election.manifest))
|
||||
context = CiphertextElectionContext.from_json_object(election.context)
|
||||
seed_hash = read_json_object(data.seed_hash, ElementModQ)
|
||||
|
||||
ballots = [PlaintextBallot.from_json_object(ballot) for ballot in data.ballots]
|
||||
|
||||
encrypted_ballots = []
|
||||
current_hash = seed_hash
|
||||
|
||||
for ballot in ballots:
|
||||
encrypted_ballot = encrypt_ballot(
|
||||
ballot, description, context, current_hash, nonce
|
||||
)
|
||||
encrypted_ballot = encrypt_ballot(ballot, manifest, context, current_hash)
|
||||
if not encrypted_ballot:
|
||||
raise HTTPException(status_code=500, detail="Ballot failed to encrypt")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Ballot failed to encrypt",
|
||||
)
|
||||
encrypted_ballots.append(encrypted_ballot)
|
||||
current_hash = get_optional(encrypted_ballot.crypto_hash)
|
||||
|
||||
|
|
|
@ -121,7 +121,7 @@ def fetch_ceremony_state(
|
|||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
@router.post(
|
||||
"/ceremony/find", response_model=KeyCeremonyQueryResponse, tags=[KEY_CEREMONY_ADMIN]
|
||||
)
|
||||
def find_ceremonies(
|
||||
|
|
|
@ -76,7 +76,7 @@ def update_key_ceremony_guardian(
|
|||
)
|
||||
|
||||
|
||||
@router.get("/find", response_model=GuardianQueryResponse, tags=[KEY_GUARDIAN])
|
||||
@router.post("/find", response_model=GuardianQueryResponse, tags=[KEY_GUARDIAN])
|
||||
def find_key_ceremony_guardians(
|
||||
request: Request,
|
||||
skip: int = 0,
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
from typing import Any, List, Optional, Tuple
|
||||
import sys
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException, status
|
||||
from typing import Any, Optional, Tuple
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException, Request, status
|
||||
|
||||
from electionguard.group import hex_to_q
|
||||
from electionguard.manifest import Manifest
|
||||
from electionguard.manifest import Manifest as sdk_manifest
|
||||
from electionguard.schema import validate_json_schema
|
||||
from electionguard.serializable import write_json_object
|
||||
from electionguard.utils import get_optional
|
||||
|
||||
from app.core.schema import get_description_schema
|
||||
|
||||
from ....core.client import get_client_id
|
||||
from ....core.repository import get_repository, DataCollection
|
||||
from ....core.manifest import get_manifest, set_manifest, filter_manifests
|
||||
from ..models import (
|
||||
ManifestQueryRequest,
|
||||
Manifest,
|
||||
BaseQueryRequest,
|
||||
ManifestQueryResponse,
|
||||
ManifestSubmitResponse,
|
||||
ValidateManifestRequest,
|
||||
|
@ -26,31 +26,17 @@ router = APIRouter()
|
|||
|
||||
|
||||
@router.get("", response_model=ManifestQueryResponse, tags=[MANIFEST])
|
||||
def get_manifest(manifest_hash: str) -> ManifestQueryResponse:
|
||||
"""Get an election manifest by hash"""
|
||||
def fetch_manifest(request: Request, manifest_hash: str) -> ManifestQueryResponse:
|
||||
"""Get an election manifest by hash."""
|
||||
crypto_hash = hex_to_q(manifest_hash)
|
||||
if not crypto_hash:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST, detail="manifest hash not valid"
|
||||
)
|
||||
try:
|
||||
with get_repository(get_client_id(), DataCollection.MANIFEST) as repository:
|
||||
query_result = repository.get({"manifest_hash": crypto_hash.to_hex()})
|
||||
if not query_result:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Could not find manifest {manifest_hash}",
|
||||
)
|
||||
|
||||
return ManifestQueryResponse(
|
||||
manifests=[query_result["manifest"]],
|
||||
)
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="get manifest failed",
|
||||
) from error
|
||||
manifest = get_manifest(crypto_hash, request.app.state.settings)
|
||||
return ManifestQueryResponse(
|
||||
manifests=[manifest],
|
||||
)
|
||||
|
||||
|
||||
@router.put(
|
||||
|
@ -60,36 +46,31 @@ def get_manifest(manifest_hash: str) -> ManifestQueryResponse:
|
|||
status_code=status.HTTP_202_ACCEPTED,
|
||||
)
|
||||
def submit_manifest(
|
||||
request: ValidateManifestRequest = Body(...),
|
||||
request: Request,
|
||||
data: ValidateManifestRequest = Body(...),
|
||||
schema: Any = Depends(get_description_schema),
|
||||
) -> ManifestSubmitResponse:
|
||||
"""
|
||||
Submit a manifest for storage
|
||||
Submit a manifest for storage.
|
||||
"""
|
||||
manifest, validation = _validate_manifest(request, schema)
|
||||
manifest, validation = _validate_manifest(data, schema)
|
||||
if not manifest or validation.status == ResponseStatus.FAIL:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST, detail=validation.details
|
||||
)
|
||||
|
||||
try:
|
||||
with get_repository(get_client_id(), DataCollection.MANIFEST) as repository:
|
||||
manifest_hash = manifest.crypto_hash().to_hex()
|
||||
_ = repository.set(
|
||||
{"manifest_hash": manifest_hash, "manifest": manifest.to_json_object()}
|
||||
)
|
||||
return ManifestSubmitResponse(manifest_hash=manifest_hash)
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Submit manifest failed",
|
||||
) from error
|
||||
api_manifest = Manifest(
|
||||
manifest_hash=write_json_object(manifest.crypto_hash()),
|
||||
manifest=manifest.to_json_object(),
|
||||
)
|
||||
return set_manifest(api_manifest, request.app.state.settings)
|
||||
|
||||
|
||||
@router.get("/find", response_model=ManifestQueryResponse, tags=[MANIFEST])
|
||||
@router.post("/find", response_model=ManifestQueryResponse, tags=[MANIFEST])
|
||||
def find_manifests(
|
||||
skip: int = 0, limit: int = 100, request: ManifestQueryRequest = Body(...)
|
||||
request: Request,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
data: BaseQueryRequest = Body(...),
|
||||
) -> ManifestQueryResponse:
|
||||
"""
|
||||
Find manifests.
|
||||
|
@ -97,21 +78,8 @@ def find_manifests(
|
|||
Search the repository for manifests that match the filter criteria specified in the request body.
|
||||
If no filter criteria is specified the API will iterate all available data.
|
||||
"""
|
||||
try:
|
||||
|
||||
filter = write_json_object(request.filter) if request.filter else {}
|
||||
with get_repository(get_client_id(), DataCollection.ELECTION) as repository:
|
||||
cursor = repository.find(filter, skip, limit)
|
||||
manifests: List[Manifest] = []
|
||||
for item in cursor:
|
||||
manifests.append(Manifest.from_json_object(item["manifest"]))
|
||||
return ManifestQueryResponse(manifests=manifests)
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="find manifests failed",
|
||||
) from error
|
||||
filter = write_json_object(data.filter) if data.filter else {}
|
||||
return filter_manifests(filter, skip, limit, request.app.state.settings)
|
||||
|
||||
|
||||
@router.post("/validate", response_model=ValidateManifestResponse, tags=[MANIFEST])
|
||||
|
@ -127,9 +95,9 @@ def validate_manifest(
|
|||
return response
|
||||
|
||||
|
||||
def _deserialize_manifest(data: object) -> Optional[Manifest]:
|
||||
def _deserialize_manifest(data: object) -> Optional[sdk_manifest]:
|
||||
try:
|
||||
return Manifest.from_json_object(data)
|
||||
return sdk_manifest.from_json_object(data)
|
||||
except Exception: # pylint: disable=broad-except
|
||||
# TODO: some sort of information why it failed
|
||||
return None
|
||||
|
@ -137,7 +105,7 @@ def _deserialize_manifest(data: object) -> Optional[Manifest]:
|
|||
|
||||
def _validate_manifest(
|
||||
request: ValidateManifestRequest, schema: Any
|
||||
) -> Tuple[Optional[Manifest], ValidateManifestResponse]:
|
||||
) -> Tuple[Optional[sdk_manifest], ValidateManifestResponse]:
|
||||
# Check schema
|
||||
schema = request.schema_override if request.schema_override else schema
|
||||
(schema_success, schema_details) = validate_json_schema(request.manifest, schema)
|
||||
|
|
|
@ -8,6 +8,7 @@ from . import key_ceremony
|
|||
from . import key_guardian
|
||||
from . import manifest
|
||||
from . import tally
|
||||
from . import tally_decrypt
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
@ -20,3 +21,4 @@ router.include_router(ballot.router, prefix="/ballot")
|
|||
router.include_router(decrypt.router, prefix="/ballot")
|
||||
router.include_router(encrypt.router, prefix="/ballot")
|
||||
router.include_router(tally.router, prefix="/tally")
|
||||
router.include_router(tally_decrypt.router, prefix="/tally/decrypt")
|
||||
|
|
|
@ -1,21 +1,51 @@
|
|||
# pylint: disable=unused-argument
|
||||
from typing import Any, List, Tuple
|
||||
from electionguard.ballot import SubmittedBallot
|
||||
from typing import Dict
|
||||
from datetime import datetime
|
||||
import sys
|
||||
|
||||
from fastapi import (
|
||||
APIRouter,
|
||||
BackgroundTasks,
|
||||
Body,
|
||||
Depends,
|
||||
HTTPException,
|
||||
Request,
|
||||
Response,
|
||||
status,
|
||||
)
|
||||
|
||||
from electionguard.ballot import BallotBoxState
|
||||
from electionguard.decrypt_with_shares import decrypt_tally as decrypt
|
||||
from electionguard.decryption_share import DecryptionShare
|
||||
from electionguard.election import CiphertextElectionContext
|
||||
from electionguard.manifest import InternalManifest, Manifest
|
||||
from electionguard.manifest import ElectionType, InternalManifest, Manifest
|
||||
from electionguard.scheduler import Scheduler
|
||||
from electionguard.serializable import read_json_object
|
||||
from electionguard.tally import CiphertextTally
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException
|
||||
from electionguard.serializable import read_json_object, write_json_object
|
||||
from electionguard.types import CONTEST_ID
|
||||
import electionguard.tally
|
||||
|
||||
|
||||
from app.core.scheduler import get_scheduler
|
||||
from app.core.settings import Settings
|
||||
from app.core.ballot import get_ballot_inventory, filter_ballots
|
||||
from app.core.election import get_election
|
||||
from app.core.tally import (
|
||||
get_ciphertext_tally,
|
||||
set_ciphertext_tally,
|
||||
filter_ciphertext_tallies,
|
||||
set_plaintext_tally,
|
||||
filter_plaintext_tallies,
|
||||
update_plaintext_tally,
|
||||
)
|
||||
from app.core.tally_decrypt import filter_decryption_shares
|
||||
from ..models import (
|
||||
convert_tally,
|
||||
AppendTallyRequest,
|
||||
BaseQueryRequest,
|
||||
CiphertextTallyQueryResponse,
|
||||
DecryptTallyRequest,
|
||||
StartTallyRequest,
|
||||
CiphertextTally,
|
||||
PlaintextTally,
|
||||
PlaintextTallyState,
|
||||
PlaintextTallyQueryResponse,
|
||||
)
|
||||
from ..tags import TALLY
|
||||
|
||||
|
@ -23,80 +53,242 @@ from ..tags import TALLY
|
|||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("", tags=[TALLY])
|
||||
def start_tally(
|
||||
request: StartTallyRequest = Body(...),
|
||||
@router.get("", response_model=CiphertextTally, tags=[TALLY])
|
||||
def fetch_ciphertext_tally(
|
||||
request: Request,
|
||||
election_id: str,
|
||||
tally_name: str,
|
||||
) -> CiphertextTally:
|
||||
"""
|
||||
Fetch a specific ciphertext tally.
|
||||
"""
|
||||
tally = get_ciphertext_tally(election_id, tally_name, request.app.state.settings)
|
||||
return tally
|
||||
|
||||
|
||||
@router.post("", response_model=CiphertextTally, tags=[TALLY])
|
||||
def tally_ballots(
|
||||
request: Request,
|
||||
election_id: str,
|
||||
tally_name: str,
|
||||
scheduler: Scheduler = Depends(get_scheduler),
|
||||
) -> Any:
|
||||
) -> CiphertextTally:
|
||||
"""
|
||||
Start a new tally of a collection of ballots
|
||||
Start a new ciphertext tally of a collection of ballots.
|
||||
|
||||
An election can have more than one tally. Each tally must have a unique name.
|
||||
Each tally correlates to a snapshot of all ballots submitted for a given election.
|
||||
"""
|
||||
election = get_election(election_id, request.app.state.settings)
|
||||
manifest = Manifest.from_json_object(election.manifest)
|
||||
context = CiphertextElectionContext.from_json_object(election.context)
|
||||
|
||||
# get the cast and spoiled ballots by checking the current ballot inventory
|
||||
# and filtering the table for
|
||||
inventory = get_ballot_inventory(election_id, request.app.state.settings)
|
||||
cast_ballots = filter_ballots(
|
||||
election_id,
|
||||
{"state": BallotBoxState.CAST.name},
|
||||
0,
|
||||
inventory.cast_ballot_count,
|
||||
request.app.state.settings,
|
||||
)
|
||||
spoiled_ballots = filter_ballots(
|
||||
election_id,
|
||||
{"state": BallotBoxState.SPOILED.name},
|
||||
0,
|
||||
inventory.spoiled_ballot_count,
|
||||
request.app.state.settings,
|
||||
)
|
||||
# TODO: check inventory list matches find result above and throw if it does not.
|
||||
|
||||
# append the ballots to the eg library tally
|
||||
sdk_tally = electionguard.tally.CiphertextTally(
|
||||
f"{election_id}-{tally_name}", InternalManifest(manifest), context
|
||||
)
|
||||
sdk_tally.batch_append(
|
||||
[(ballot.object_id, ballot) for ballot in cast_ballots], scheduler
|
||||
)
|
||||
sdk_tally.batch_append(
|
||||
[(ballot.object_id, ballot) for ballot in spoiled_ballots], scheduler
|
||||
)
|
||||
|
||||
# create and cache the api tally.
|
||||
api_tally = CiphertextTally(
|
||||
election_id=election_id,
|
||||
tally_name=tally_name,
|
||||
created=datetime.now(),
|
||||
tally=sdk_tally.to_json_object(),
|
||||
)
|
||||
|
||||
set_ciphertext_tally(api_tally, request.app.state.settings)
|
||||
|
||||
return api_tally
|
||||
|
||||
|
||||
@router.post("/find", response_model=CiphertextTallyQueryResponse, tags=[TALLY])
|
||||
def find_ciphertext_tallies(
|
||||
request: Request,
|
||||
election_id: str,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
data: BaseQueryRequest = Body(...),
|
||||
) -> CiphertextTallyQueryResponse:
|
||||
"""
|
||||
Find tallies.
|
||||
|
||||
Search the repository for tallies that match the filter criteria specified in the request body.
|
||||
If no filter criteria is specified the API will iterate all available data.
|
||||
"""
|
||||
filter = write_json_object(data.filter) if data.filter else {}
|
||||
tallies = filter_ciphertext_tallies(
|
||||
election_id, filter, skip, limit, request.app.state.settings
|
||||
)
|
||||
return CiphertextTallyQueryResponse(tallies=tallies)
|
||||
|
||||
|
||||
@router.post("/decrypt", response_model=PlaintextTallyQueryResponse, tags=[TALLY])
|
||||
async def decrypt_tally(
|
||||
request: Request,
|
||||
response: Response,
|
||||
background_tasks: BackgroundTasks,
|
||||
restart: bool = False,
|
||||
data: DecryptTallyRequest = Body(...),
|
||||
) -> PlaintextTallyQueryResponse:
|
||||
"""
|
||||
Decrypt a tally from a collection of decrypted guardian shares.
|
||||
|
||||
Requires that all guardian shares have been submitted.
|
||||
|
||||
The decryption process can take some time,
|
||||
so the method returns immediately and continues processing in the background.
|
||||
"""
|
||||
|
||||
ballots, description, context = _parse_tally_request(request)
|
||||
tally = CiphertextTally("election-results", description, context)
|
||||
# if we already have a value cached, then return it.
|
||||
plaintext_tallies = filter_plaintext_tallies(
|
||||
data.election_id,
|
||||
{"election_id": data.election_id, "tally_name": data.tally_name},
|
||||
0,
|
||||
1,
|
||||
request.app.state.settings,
|
||||
)
|
||||
if not restart and len(plaintext_tallies) > 0:
|
||||
return PlaintextTallyQueryResponse(tallies=plaintext_tallies)
|
||||
|
||||
return _tally_ballots(tally, ballots, scheduler)
|
||||
tally = PlaintextTally(
|
||||
election_id=data.election_id,
|
||||
tally_name=data.tally_name,
|
||||
created=datetime.now(),
|
||||
state=PlaintextTallyState.CREATED,
|
||||
)
|
||||
set_plaintext_tally(tally, request.app.state.settings)
|
||||
|
||||
# queue a background task to execute the tally
|
||||
# TODO: determine whether we wait or continue
|
||||
# asyncio.create_task(_decrypt_tally(tally, request.app.state.settings))
|
||||
await _decrypt_tally(tally, request.app.state.settings)
|
||||
|
||||
response.status_code = status.HTTP_202_ACCEPTED
|
||||
return PlaintextTallyQueryResponse(
|
||||
message="tally computing, check back in a few minutes.", tallies=[tally]
|
||||
)
|
||||
|
||||
|
||||
@router.post("/append", tags=[TALLY])
|
||||
def append_to_tally(
|
||||
request: AppendTallyRequest = Body(...),
|
||||
scheduler: Scheduler = Depends(get_scheduler),
|
||||
) -> Any:
|
||||
"""
|
||||
Append ballots into an existing tally
|
||||
"""
|
||||
# removed tally code since it is changing
|
||||
return {}
|
||||
async def _decrypt_tally(
|
||||
api_plaintext_tally: PlaintextTally, settings: Settings = Settings()
|
||||
) -> None:
|
||||
|
||||
try:
|
||||
# set the tally state to processing
|
||||
api_plaintext_tally.state = PlaintextTallyState.PROCESSING
|
||||
update_plaintext_tally(api_plaintext_tally, settings)
|
||||
|
||||
@router.post("/decrypt", tags=[TALLY])
|
||||
def decrypt_tally(request: DecryptTallyRequest = Body(...)) -> Any:
|
||||
"""
|
||||
Decrypt a tally from a collection of decrypted guardian shares
|
||||
"""
|
||||
description = InternalManifest(Manifest.from_json_object(request.description))
|
||||
context = CiphertextElectionContext.from_json_object(request.context)
|
||||
tally = convert_tally(request.encrypted_tally, description, context)
|
||||
|
||||
shares = {
|
||||
guardian_id: read_json_object(share, DecryptionShare)
|
||||
for guardian_id, share in request.shares.items()
|
||||
}
|
||||
|
||||
full_plaintext_tally = decrypt(tally, shares, context.crypto_extended_base_hash)
|
||||
if not full_plaintext_tally:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Unable to decrypt tally",
|
||||
api_ciphertext_tally = get_ciphertext_tally(
|
||||
api_plaintext_tally.election_id, api_plaintext_tally.tally_name, settings
|
||||
)
|
||||
|
||||
# need to publish the tally
|
||||
return full_plaintext_tally.to_json_object()
|
||||
election = get_election(api_plaintext_tally.election_id, settings)
|
||||
context = CiphertextElectionContext.from_json_object(election.context)
|
||||
|
||||
# filter the guardian shares
|
||||
query_shares = filter_decryption_shares(
|
||||
api_plaintext_tally.tally_name,
|
||||
None,
|
||||
0,
|
||||
context.number_of_guardians,
|
||||
settings,
|
||||
)
|
||||
|
||||
def _parse_tally_request(
|
||||
request: StartTallyRequest,
|
||||
) -> Tuple[List[SubmittedBallot], InternalManifest, CiphertextElectionContext,]:
|
||||
"""
|
||||
Deserialize common tally request values
|
||||
"""
|
||||
ballots = [SubmittedBallot.from_json_object(ballot) for ballot in request.ballots]
|
||||
description = Manifest.from_json_object(request.description)
|
||||
internal_description = InternalManifest(description)
|
||||
context = CiphertextElectionContext.from_json_object(request.context)
|
||||
# validate we have all of the guardian shares
|
||||
# TODO: support thresholding
|
||||
if len(query_shares) != context.number_of_guardians:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_412_PRECONDITION_FAILED,
|
||||
detail="Not all guardians have submitted shares",
|
||||
)
|
||||
|
||||
return (ballots, internal_description, context)
|
||||
# transform the tally shares
|
||||
tally_shares = {
|
||||
share.guardian_id: read_json_object(share.tally_share, DecryptionShare)
|
||||
for share in query_shares
|
||||
}
|
||||
|
||||
# TODO: HACK: Remove The Empty Manifest
|
||||
# Note: The CiphertextTally requires an internal manifest passed into its constructor
|
||||
# but it is not actually used when executing `compute_decryption_share` so we create a fake.
|
||||
# see: https://github.com/microsoft/electionguard-python/issues/391
|
||||
internal_manifest = InternalManifest(
|
||||
Manifest(
|
||||
"",
|
||||
"",
|
||||
ElectionType.other,
|
||||
datetime.now(),
|
||||
datetime.now(),
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
)
|
||||
)
|
||||
sdk_ciphertext_tally = electionguard.tally.CiphertextTally(
|
||||
api_ciphertext_tally.tally_name, internal_manifest, context
|
||||
)
|
||||
contests: Dict[CONTEST_ID, electionguard.tally.CiphertextTallyContest] = {
|
||||
contest_id: read_json_object(
|
||||
contest, electionguard.tally.CiphertextTallyContest
|
||||
)
|
||||
for contest_id, contest in api_ciphertext_tally.tally["contests"].items()
|
||||
}
|
||||
sdk_ciphertext_tally.contests = contests
|
||||
|
||||
def _tally_ballots(
|
||||
tally: CiphertextTally,
|
||||
ballots: List[SubmittedBallot],
|
||||
scheduler: Scheduler,
|
||||
) -> Any:
|
||||
"""
|
||||
Append a series of ballots to a new or existing tally
|
||||
"""
|
||||
# decrypt
|
||||
sdk_plaintext_tally = decrypt(
|
||||
sdk_ciphertext_tally,
|
||||
tally_shares,
|
||||
context.crypto_extended_base_hash,
|
||||
)
|
||||
if not sdk_plaintext_tally:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Unable to decrypt tally",
|
||||
)
|
||||
|
||||
# tally is being changed
|
||||
return {}
|
||||
# cache the tally plaintext tally
|
||||
api_plaintext_tally.tally = write_json_object(sdk_plaintext_tally)
|
||||
api_plaintext_tally.state = PlaintextTallyState.COMPLETE
|
||||
update_plaintext_tally(api_plaintext_tally, settings)
|
||||
|
||||
except HTTPException as error:
|
||||
api_plaintext_tally.state = PlaintextTallyState.ERROR
|
||||
update_plaintext_tally(api_plaintext_tally, settings)
|
||||
print(sys.exc_info())
|
||||
raise
|
||||
except Exception as error:
|
||||
api_plaintext_tally.state = PlaintextTallyState.ERROR
|
||||
update_plaintext_tally(api_plaintext_tally, settings)
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="decrypt plaintext tally failed",
|
||||
) from error
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
from fastapi import APIRouter, Body, HTTPException, Request, status
|
||||
|
||||
from electionguard.key_ceremony import PublicKeySet
|
||||
from electionguard.election import CiphertextElectionContext
|
||||
from electionguard.decryption_share import DecryptionShare
|
||||
from electionguard.serializable import read_json_object
|
||||
from electionguard.tally import CiphertextTallyContest
|
||||
from electionguard.utils import get_optional
|
||||
|
||||
from app.core.election import get_election
|
||||
from app.core.key_guardian import get_key_guardian
|
||||
from app.core.tally import get_ciphertext_tally
|
||||
from app.core.tally_decrypt import (
|
||||
get_decryption_share,
|
||||
set_decryption_share,
|
||||
filter_decryption_shares,
|
||||
)
|
||||
from ..models import (
|
||||
BaseResponse,
|
||||
DecryptionShareResponse,
|
||||
BaseQueryRequest,
|
||||
DecryptionShareRequest,
|
||||
)
|
||||
from ..tags import TALLY_DECRYPT
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("", response_model=DecryptionShareResponse, tags=[TALLY_DECRYPT])
|
||||
def fetch_decryption_share(
|
||||
request: Request, election_id: str, tally_name: str, guardian_id: str
|
||||
) -> DecryptionShareResponse:
|
||||
"""Get a decryption share for a specific tally for a specific guardian."""
|
||||
share = get_decryption_share(
|
||||
election_id, tally_name, guardian_id, request.app.state.settings
|
||||
)
|
||||
return DecryptionShareResponse(
|
||||
shares=[share],
|
||||
)
|
||||
|
||||
|
||||
@router.post("/submit-share", response_model=BaseResponse, tags=[TALLY_DECRYPT])
|
||||
def submit_share(
|
||||
request: Request,
|
||||
data: DecryptionShareRequest = Body(...),
|
||||
) -> BaseResponse:
|
||||
"""
|
||||
Announce a guardian participating in a tally decryption by submitting a decryption share.
|
||||
"""
|
||||
|
||||
election = get_election(data.share.election_id, request.app.state.settings)
|
||||
context = CiphertextElectionContext.from_json_object(election.context)
|
||||
guardian = get_key_guardian(
|
||||
election.key_name, data.share.guardian_id, request.app.state.settings
|
||||
)
|
||||
public_keys = read_json_object(get_optional(guardian.public_keys), PublicKeySet)
|
||||
|
||||
api_tally = get_ciphertext_tally(
|
||||
data.share.election_id, data.share.tally_name, request.app.state.settings
|
||||
)
|
||||
tally_share = read_json_object(data.share.tally_share, DecryptionShare)
|
||||
|
||||
# TODO: spoiled ballot shares
|
||||
# ballot_shares = [
|
||||
# read_json_object(ballot_share, DecryptionShare)
|
||||
# for ballot_share in data.share.ballot_shares
|
||||
# ]
|
||||
|
||||
# validate the decryption share data matches the expectations in the tally
|
||||
# TODO: use the SDK for validation
|
||||
# sdk_tally = read_json_object(api_tally.tally, electionguard.tally.CiphertextTally)
|
||||
for contest_id, contest in api_tally.tally["contests"].items():
|
||||
tally_contest = read_json_object(contest, CiphertextTallyContest)
|
||||
contest_share = tally_share.contests[contest_id]
|
||||
for selection_id, selection in tally_contest.selections.items():
|
||||
selection_share = contest_share.selections[selection_id]
|
||||
if not selection_share.is_valid(
|
||||
selection.ciphertext,
|
||||
public_keys.election.key,
|
||||
context.crypto_extended_base_hash,
|
||||
):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail=f"decryption share failed valitation for contest: {contest_id} selection: {selection_id}",
|
||||
)
|
||||
|
||||
# TODO: validate spoiled ballot shares
|
||||
|
||||
return set_decryption_share(data.share, request.app.state.settings)
|
||||
|
||||
|
||||
@router.post("/find", response_model=DecryptionShareResponse, tags=[TALLY_DECRYPT])
|
||||
def find_decryption_shares(
|
||||
request: Request,
|
||||
tally_name: str,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
data: BaseQueryRequest = Body(...),
|
||||
) -> DecryptionShareResponse:
|
||||
"""Find descryption shares for a specific tally."""
|
||||
shares = filter_decryption_shares(
|
||||
tally_name, data.filter, skip, limit, request.app.state.settings
|
||||
)
|
||||
return DecryptionShareResponse(
|
||||
shares=shares,
|
||||
)
|
|
@ -8,3 +8,4 @@ from .key_ceremony import *
|
|||
from .key_guardian import *
|
||||
from .manifest import *
|
||||
from .tally import *
|
||||
from .tally_decrypt import *
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
from typing import Any, List, Optional
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from .base import BaseRequest, BaseResponse, BaseValidationRequest
|
||||
from .base import Base, BaseRequest, BaseResponse, BaseValidationRequest
|
||||
from .election import CiphertextElectionContext
|
||||
from .manifest import ElectionManifest
|
||||
|
||||
|
||||
__all__ = [
|
||||
"BallotQueryResponse",
|
||||
"BallotInventory",
|
||||
"BallotInventoryResponse",
|
||||
"BaseBallotRequest",
|
||||
"CastBallotsRequest",
|
||||
"SpoilBallotsRequest",
|
||||
"SubmitBallotsRequest",
|
||||
"SubmitBallotsResponse",
|
||||
"ValidateBallotRequest",
|
||||
]
|
||||
|
||||
|
@ -20,10 +21,40 @@ SubmittedBallot = Any
|
|||
CiphertextBallot = Any
|
||||
PlaintextBallot = Any
|
||||
|
||||
BALLOT_CODE = str
|
||||
BALLOT_URL = str
|
||||
|
||||
|
||||
class BallotInventory(Base):
|
||||
"""
|
||||
The Ballot Inventory retains metadata about ballots in an election,
|
||||
including mappings of ballot tracking codes to ballot id's
|
||||
"""
|
||||
|
||||
election_id: str
|
||||
cast_ballot_count: int = 0
|
||||
spoiled_ballot_count: int = 0
|
||||
cast_ballots: Dict[BALLOT_CODE, BALLOT_URL] = {}
|
||||
"""
|
||||
Collection of cast ballot codes mapped to a route that is accessible.
|
||||
|
||||
Note: the BALLOT_URL is storage dependent and is not meant to be shared with the election record
|
||||
"""
|
||||
spoiled_ballots: Dict[BALLOT_CODE, BALLOT_URL] = {}
|
||||
"""
|
||||
Collection of spoiled ballot codes mapped to a route that is accessible.
|
||||
|
||||
Note: the BALLOT_URL is storage dependent and is not meant to be shared with the election record
|
||||
"""
|
||||
|
||||
|
||||
class BallotInventoryResponse(BaseResponse):
|
||||
inventory: BallotInventory
|
||||
|
||||
|
||||
class BallotQueryResponse(BaseResponse):
|
||||
election_id: str
|
||||
ballots: List[CiphertextBallot]
|
||||
ballots: List[CiphertextBallot] = []
|
||||
|
||||
|
||||
class BaseBallotRequest(BaseRequest):
|
||||
|
@ -33,28 +64,25 @@ class BaseBallotRequest(BaseRequest):
|
|||
|
||||
|
||||
class CastBallotsRequest(BaseBallotRequest):
|
||||
"""Cast the enclosed ballots."""
|
||||
|
||||
ballots: List[CiphertextBallot]
|
||||
|
||||
|
||||
class SpoilBallotsRequest(BaseBallotRequest):
|
||||
"""Spoil the enclosed ballots."""
|
||||
|
||||
ballots: List[CiphertextBallot]
|
||||
|
||||
|
||||
class SubmitBallotsRequest(BaseBallotRequest):
|
||||
"""Submit a ballot against a specific election"""
|
||||
"""Submit a ballot against a specific election."""
|
||||
|
||||
ballots: List[SubmittedBallot]
|
||||
|
||||
|
||||
class SubmitBallotsResponse(BaseResponse):
|
||||
"""Submit a ballot against a specific election"""
|
||||
|
||||
cache_keys: List[str]
|
||||
election_id: Optional[str] = None
|
||||
|
||||
|
||||
class ValidateBallotRequest(BaseValidationRequest):
|
||||
"""Submit a ballot against a specific election description and contest to determine if it is accepted"""
|
||||
"""Submit a ballot against a specific election description and contest to determine if it is accepted."""
|
||||
|
||||
ballot: CiphertextBallot
|
||||
manifest: ElectionManifest
|
||||
|
|
|
@ -37,6 +37,9 @@ class BaseResponse(BaseModel):
|
|||
message: Optional[str] = None
|
||||
"""An optional message describing the response"""
|
||||
|
||||
def is_success(self) -> bool:
|
||||
return self.status == ResponseStatus.SUCCESS
|
||||
|
||||
|
||||
class BaseQueryRequest(BaseRequest):
|
||||
"""Find something"""
|
||||
|
|
|
@ -31,4 +31,4 @@ class DecryptBallotSharesRequest(BaseRequest):
|
|||
|
||||
|
||||
class DecryptBallotSharesResponse(BaseRequest):
|
||||
shares: List[DecryptionShare]
|
||||
shares: List[DecryptionShare] = []
|
||||
|
|
|
@ -13,7 +13,6 @@ __all__ = [
|
|||
"MakeElectionContextRequest",
|
||||
"MakeElectionContextResponse",
|
||||
"SubmitElectionRequest",
|
||||
"SubmitElectionResponse",
|
||||
]
|
||||
|
||||
CiphertextElectionContext = Any
|
||||
|
@ -27,20 +26,21 @@ class ElectionState(str, Enum):
|
|||
|
||||
|
||||
class Election(Base):
|
||||
"""An election object"""
|
||||
"""An election object."""
|
||||
|
||||
election_id: str
|
||||
key_name: str
|
||||
state: ElectionState
|
||||
context: CiphertextElectionContext
|
||||
manifest: ElectionManifest
|
||||
|
||||
|
||||
class ElectionQueryRequest(BaseRequest):
|
||||
"""A request for elections using the specified filter"""
|
||||
"""A request for elections using the specified filter."""
|
||||
|
||||
filter: Optional[Any] = None
|
||||
"""
|
||||
a json object filter that will be applied to the search
|
||||
a json object filter that will be applied to the search.
|
||||
"""
|
||||
|
||||
class Config:
|
||||
|
@ -48,28 +48,23 @@ class ElectionQueryRequest(BaseRequest):
|
|||
|
||||
|
||||
class ElectionQueryResponse(BaseResponse):
|
||||
"""A collection of elections"""
|
||||
"""A collection of elections."""
|
||||
|
||||
elections: List[Election]
|
||||
elections: List[Election] = []
|
||||
|
||||
|
||||
class SubmitElectionRequest(BaseRequest):
|
||||
"""Submit an election"""
|
||||
"""Submit an election."""
|
||||
|
||||
election_id: Optional[str] = None
|
||||
election_id: str
|
||||
key_name: str
|
||||
context: CiphertextElectionContext
|
||||
manifest: Optional[ElectionManifest] = None
|
||||
|
||||
|
||||
class SubmitElectionResponse(BaseResponse):
|
||||
"""A submitted election id"""
|
||||
|
||||
election_id: str
|
||||
|
||||
|
||||
class MakeElectionContextRequest(BaseRequest):
|
||||
"""
|
||||
A request to build an Election Context for a given election
|
||||
A request to build an Election Context for a given election.
|
||||
"""
|
||||
|
||||
elgamal_public_key: str
|
||||
|
@ -81,6 +76,6 @@ class MakeElectionContextRequest(BaseRequest):
|
|||
|
||||
|
||||
class MakeElectionContextResponse(BaseResponse):
|
||||
"""A Ciphertext Election Context"""
|
||||
"""A Ciphertext Election Context."""
|
||||
|
||||
context: CiphertextElectionContext
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
from typing import Any, List, Optional
|
||||
from typing import Any, List
|
||||
|
||||
from .base import BaseRequest, BaseResponse
|
||||
from .election import CiphertextElectionContext
|
||||
from .manifest import ElectionManifest
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
@ -13,19 +11,17 @@ __all__ = [
|
|||
CiphertextBallot = Any
|
||||
PlaintextBallot = Any
|
||||
|
||||
# TODO: follow model submit ballot request object model
|
||||
|
||||
|
||||
class EncryptBallotsRequest(BaseRequest):
|
||||
ballots: List[PlaintextBallot]
|
||||
"""A request to encrypt the enclosed ballots."""
|
||||
|
||||
election_id: str
|
||||
seed_hash: str
|
||||
nonce: Optional[str] = None
|
||||
manifest: Optional[ElectionManifest] = None
|
||||
context: Optional[CiphertextElectionContext] = None
|
||||
ballots: List[PlaintextBallot]
|
||||
|
||||
|
||||
class EncryptBallotsResponse(BaseResponse):
|
||||
encrypted_ballots: List[CiphertextBallot]
|
||||
"""The encrypted representations of the plaintext ballots"""
|
||||
"""The encrypted representations of the plaintext ballots."""
|
||||
next_seed_hash: str
|
||||
"""A seed hash which can optionally be used for the next call to encrypt"""
|
||||
"""A seed hash which can optionally be used for the next call to encrypt."""
|
||||
|
|
|
@ -100,7 +100,7 @@ class CreateAuxiliaryKeyPairResponse(BaseResponse):
|
|||
|
||||
|
||||
class GuardianPublicKeysResponse(BaseResponse):
|
||||
"""Returns a set of public auxiliary and election keys"""
|
||||
"""Returns a set of public auxiliary and election keys."""
|
||||
|
||||
public_keys: PublicKeySet
|
||||
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
from typing import Any, List, Optional
|
||||
|
||||
from .base import (
|
||||
BaseRequest,
|
||||
Base,
|
||||
BaseResponse,
|
||||
BaseValidationRequest,
|
||||
BaseValidationResponse,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Manifest",
|
||||
"ManifestSubmitResponse",
|
||||
"ManifestQueryRequest",
|
||||
"ManifestQueryResponse",
|
||||
"ValidateManifestRequest",
|
||||
"ValidateManifestResponse",
|
||||
|
@ -19,27 +19,26 @@ ElectionManifest = Any
|
|||
ElementModQ = Any
|
||||
|
||||
|
||||
class Manifest(Base):
|
||||
manifest_hash: ElementModQ
|
||||
manifest: ElectionManifest
|
||||
|
||||
|
||||
class ManifestSubmitResponse(BaseResponse):
|
||||
manifest_hash: ElementModQ
|
||||
|
||||
|
||||
class ManifestQueryRequest(BaseRequest):
|
||||
"""A request for manifests using the specified filter"""
|
||||
|
||||
filter: Optional[Any] = None
|
||||
|
||||
|
||||
class ManifestQueryResponse(BaseResponse):
|
||||
manifests: List[ElectionManifest]
|
||||
manifests: List[Manifest]
|
||||
|
||||
|
||||
class ValidateManifestRequest(BaseValidationRequest):
|
||||
"""
|
||||
A request to validate an Election Description
|
||||
A request to validate an Election Description.
|
||||
"""
|
||||
|
||||
manifest: ElectionManifest
|
||||
"""The manifest to validate"""
|
||||
"""The manifest to validate."""
|
||||
|
||||
|
||||
class ValidateManifestResponse(BaseValidationResponse):
|
||||
|
|
|
@ -1,66 +1,63 @@
|
|||
from typing import Any, Dict, List
|
||||
import electionguard.election
|
||||
from electionguard.serializable import read_json_object
|
||||
import electionguard.tally
|
||||
from electionguard.manifest import InternalManifest
|
||||
from typing import Any, List, Optional
|
||||
from enum import Enum
|
||||
from datetime import datetime
|
||||
|
||||
from .ballot import SubmittedBallot
|
||||
from .base import BaseRequest
|
||||
from .election import ElectionManifest, CiphertextElectionContext
|
||||
from .guardian import Guardian
|
||||
from .base import BaseResponse, BaseRequest, Base
|
||||
|
||||
__all__ = [
|
||||
"PublishedCiphertextTally",
|
||||
"TallyDecryptionShare",
|
||||
"StartTallyRequest",
|
||||
"AppendTallyRequest",
|
||||
"CiphertextTally",
|
||||
"CiphertextTallyQueryResponse",
|
||||
"DecryptTallyRequest",
|
||||
"DecryptTallyShareRequest",
|
||||
"convert_tally",
|
||||
"PlaintextTally",
|
||||
"PlaintextTallyState",
|
||||
"PlaintextTallyQueryResponse",
|
||||
]
|
||||
|
||||
PublishedCiphertextTally = Any
|
||||
TallyDecryptionShare = Any
|
||||
ElectionGuardCiphertextTally = Any
|
||||
ElectionGuardPlaintextTally = Any
|
||||
|
||||
|
||||
class StartTallyRequest(BaseRequest):
|
||||
ballots: List[SubmittedBallot]
|
||||
description: ElectionManifest
|
||||
context: CiphertextElectionContext
|
||||
class CiphertextTally(Base):
|
||||
"""A Tally for a specific election."""
|
||||
|
||||
election_id: str
|
||||
tally_name: str
|
||||
created: datetime
|
||||
tally: ElectionGuardCiphertextTally
|
||||
"""The full electionguard CiphertextTally that includes the cast and spoiled ballot id's."""
|
||||
|
||||
|
||||
class AppendTallyRequest(StartTallyRequest):
|
||||
encrypted_tally: PublishedCiphertextTally
|
||||
class PlaintextTallyState(str, Enum):
|
||||
CREATED = "CREATED"
|
||||
PROCESSING = "PROCESSING"
|
||||
ERROR = "ERROR"
|
||||
COMPLETE = "COMPLETE"
|
||||
|
||||
|
||||
class PlaintextTally(Base):
|
||||
"""A plaintext tally for a specific election."""
|
||||
|
||||
election_id: str
|
||||
tally_name: str
|
||||
created: datetime
|
||||
state: PlaintextTallyState
|
||||
tally: Optional[ElectionGuardPlaintextTally] = None
|
||||
|
||||
|
||||
class CiphertextTallyQueryResponse(BaseResponse):
|
||||
"""A collection of Ciphertext Tallies."""
|
||||
|
||||
tallies: List[CiphertextTally] = []
|
||||
|
||||
|
||||
class PlaintextTallyQueryResponse(BaseResponse):
|
||||
"""A collection of Plaintext Tallies."""
|
||||
|
||||
tallies: List[PlaintextTally] = []
|
||||
|
||||
|
||||
class DecryptTallyRequest(BaseRequest):
|
||||
encrypted_tally: PublishedCiphertextTally
|
||||
shares: Dict[str, TallyDecryptionShare]
|
||||
description: ElectionManifest
|
||||
context: CiphertextElectionContext
|
||||
"""A request to decrypt a specific tally. Can optionally include the tally to decrypt."""
|
||||
|
||||
|
||||
class DecryptTallyShareRequest(BaseRequest):
|
||||
encrypted_tally: PublishedCiphertextTally
|
||||
guardian: Guardian
|
||||
description: ElectionManifest
|
||||
context: CiphertextElectionContext
|
||||
|
||||
|
||||
def convert_tally(
|
||||
encrypted_tally: PublishedCiphertextTally,
|
||||
description: InternalManifest,
|
||||
context: electionguard.election.CiphertextElectionContext,
|
||||
) -> electionguard.tally.CiphertextTally:
|
||||
"""
|
||||
Convert to an SDK CiphertextTally model
|
||||
"""
|
||||
|
||||
published_tally = read_json_object(
|
||||
encrypted_tally, electionguard.tally.PublishedCiphertextTally
|
||||
)
|
||||
tally = electionguard.tally.CiphertextTally(
|
||||
published_tally.object_id, description, context
|
||||
)
|
||||
|
||||
return tally
|
||||
election_id: str
|
||||
tally_name: str
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
from typing import Any, Dict, List
|
||||
|
||||
from electionguard.types import BALLOT_ID
|
||||
|
||||
from .base import BaseResponse, BaseRequest, Base
|
||||
from .election import CiphertextElectionContext
|
||||
from .tally import CiphertextTally
|
||||
|
||||
__all__ = [
|
||||
"CiphertextTallyDecryptionShare",
|
||||
"DecryptTallyShareRequest",
|
||||
"DecryptionShareRequest",
|
||||
"DecryptionShareResponse",
|
||||
]
|
||||
|
||||
DecryptionShare = Any
|
||||
ElectionGuardCiphertextTally = Any
|
||||
|
||||
|
||||
class CiphertextTallyDecryptionShare(Base):
|
||||
"""
|
||||
A DecryptionShare provided by a guardian for a specific tally.
|
||||
|
||||
Optionally can include ballot_shares for challenge ballots.
|
||||
"""
|
||||
|
||||
election_id: str # TODO: not needed since we have the tally_name?
|
||||
tally_name: str
|
||||
guardian_id: str
|
||||
tally_share: DecryptionShare
|
||||
"""The EG Decryptionshare that includes a share for each contest in the election."""
|
||||
ballot_shares: Dict[BALLOT_ID, DecryptionShare] = {}
|
||||
"""A collection of shares for each challenge ballot."""
|
||||
|
||||
|
||||
class DecryptTallyShareRequest(BaseRequest):
|
||||
"""A request to partially decrypt a tally and generate a DecryptionShare."""
|
||||
|
||||
guardian_id: str
|
||||
encrypted_tally: CiphertextTally
|
||||
context: CiphertextElectionContext
|
||||
|
||||
|
||||
class DecryptionShareRequest(BaseRequest):
|
||||
"""A request to submit a decryption share."""
|
||||
|
||||
share: CiphertextTallyDecryptionShare
|
||||
|
||||
|
||||
class DecryptionShareResponse(BaseResponse):
|
||||
"""A response that includes a collection of decryption shares."""
|
||||
|
||||
shares: List[CiphertextTallyDecryptionShare]
|
|
@ -7,5 +7,6 @@ KEY_GUARDIAN = "Key Ceremony Participant"
|
|||
BALLOTS = "Query Ballots"
|
||||
ENCRYPT = "Encrypt Ballots"
|
||||
TALLY = "Tally Results"
|
||||
TALLY_DECRYPT = "Tally Decrypt"
|
||||
PUBLISH = "Publish Results"
|
||||
UTILITY = "Utility Functions"
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
from typing import Any, List
|
||||
import sys
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
from electionguard.ballot import (
|
||||
SubmittedBallot,
|
||||
)
|
||||
|
||||
from .repository import get_repository, DataCollection
|
||||
from .settings import Settings
|
||||
from ..api.v1.models import BaseResponse, BallotInventory
|
||||
|
||||
|
||||
def get_ballot(
|
||||
election_id: str, ballot_id: str, settings: Settings = Settings()
|
||||
) -> SubmittedBallot:
|
||||
try:
|
||||
with get_repository(
|
||||
election_id, DataCollection.SUBMITTED_BALLOT, settings
|
||||
) as repository:
|
||||
query_result = repository.get({"object_id": ballot_id})
|
||||
if not query_result:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Could not find ballot_id {ballot_id}",
|
||||
)
|
||||
return SubmittedBallot.from_json_object(query_result)
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="get ballot failed",
|
||||
) from error
|
||||
|
||||
|
||||
def set_ballots(
|
||||
election_id: str, ballots: List[SubmittedBallot], settings: Settings = Settings()
|
||||
) -> BaseResponse:
|
||||
try:
|
||||
with get_repository(
|
||||
election_id, DataCollection.SUBMITTED_BALLOT, settings
|
||||
) as repository:
|
||||
cacheable_ballots = [ballot.to_json_object() for ballot in ballots]
|
||||
_ = repository.set(cacheable_ballots)
|
||||
return BaseResponse(
|
||||
message="Ballots Successfully Set",
|
||||
)
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="set ballots failed",
|
||||
) from error
|
||||
|
||||
|
||||
def filter_ballots(
|
||||
election_id: str,
|
||||
filter: Any,
|
||||
skip: int = 0,
|
||||
limit: int = 1000,
|
||||
settings: Settings = Settings(),
|
||||
) -> List[SubmittedBallot]:
|
||||
try:
|
||||
with get_repository(
|
||||
election_id, DataCollection.SUBMITTED_BALLOT, settings
|
||||
) as repository:
|
||||
cursor = repository.find(filter, skip, limit)
|
||||
ballots: List[SubmittedBallot] = []
|
||||
for item in cursor:
|
||||
ballots.append(SubmittedBallot.from_json_object(item))
|
||||
return ballots
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="filter ballots failed",
|
||||
) from error
|
||||
|
||||
|
||||
def get_ballot_inventory(
|
||||
election_id: str, settings: Settings = Settings()
|
||||
) -> BallotInventory:
|
||||
try:
|
||||
with get_repository(
|
||||
election_id, DataCollection.BALLOT_INVENTORY, settings
|
||||
) as repository:
|
||||
query_result = repository.get({"election_id": election_id})
|
||||
if not query_result:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Could not find ballot_id {election_id}",
|
||||
)
|
||||
return BallotInventory(
|
||||
election_id=query_result["election_id"],
|
||||
cast_ballot_count=query_result["cast_ballot_count"],
|
||||
spoiled_ballot_count=query_result["spoiled_ballot_count"],
|
||||
cast_ballots=query_result["cast_ballots"],
|
||||
spoiled_ballots=query_result["spoiled_ballots"],
|
||||
)
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="get ballot inventory failed",
|
||||
) from error
|
||||
|
||||
|
||||
def upsert_ballot_inventory(
|
||||
election_id: str, inventory: BallotInventory, settings: Settings = Settings()
|
||||
) -> BaseResponse:
|
||||
try:
|
||||
with get_repository(
|
||||
election_id, DataCollection.BALLOT_INVENTORY, settings
|
||||
) as repository:
|
||||
query_result = repository.get({"election_id": election_id})
|
||||
if not query_result:
|
||||
repository.set(inventory.dict())
|
||||
else:
|
||||
repository.update({"election_id": election_id}, inventory.dict())
|
||||
return BaseResponse()
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="update ballot inventory failed",
|
||||
) from error
|
|
@ -0,0 +1,105 @@
|
|||
from typing import Any, List
|
||||
|
||||
import sys
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
from electionguard.serializable import write_json_object
|
||||
|
||||
from .client import get_client_id
|
||||
from .repository import get_repository, DataCollection
|
||||
from .settings import Settings
|
||||
from ..api.v1.models import BaseResponse, Election, ElectionState
|
||||
|
||||
|
||||
def from_query(query_result: Any) -> Election:
|
||||
return Election(
|
||||
election_id=query_result["election_id"],
|
||||
key_name=query_result["key_name"],
|
||||
state=query_result["state"],
|
||||
context=query_result["context"],
|
||||
manifest=query_result["manifest"],
|
||||
)
|
||||
|
||||
|
||||
def get_election(election_id: str, settings: Settings = Settings()) -> Election:
|
||||
try:
|
||||
with get_repository(
|
||||
get_client_id(), DataCollection.ELECTION, settings
|
||||
) as repository:
|
||||
query_result = repository.get({"election_id": election_id})
|
||||
if not query_result:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Could not find election {election_id}",
|
||||
)
|
||||
election = from_query(query_result)
|
||||
|
||||
return election
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="get election failed",
|
||||
) from error
|
||||
|
||||
|
||||
def set_election(election: Election, settings: Settings = Settings()) -> BaseResponse:
|
||||
try:
|
||||
with get_repository(
|
||||
get_client_id(), DataCollection.ELECTION, settings
|
||||
) as repository:
|
||||
_ = repository.set(write_json_object(election.dict()))
|
||||
return BaseResponse(
|
||||
message="Election Successfully Set",
|
||||
)
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Submit election failed",
|
||||
) from error
|
||||
|
||||
|
||||
def filter_elections(
|
||||
filter: Any, skip: int = 0, limit: int = 1000, settings: Settings = Settings()
|
||||
) -> List[Election]:
|
||||
try:
|
||||
with get_repository(
|
||||
get_client_id(), DataCollection.ELECTION, settings
|
||||
) as repository:
|
||||
cursor = repository.find(filter, skip, limit)
|
||||
elections: List[Election] = []
|
||||
for item in cursor:
|
||||
elections.append(from_query(item))
|
||||
return elections
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="find elections failed",
|
||||
) from error
|
||||
|
||||
|
||||
def update_election_state(
|
||||
election_id: str, new_state: ElectionState, settings: Settings = Settings()
|
||||
) -> BaseResponse:
|
||||
try:
|
||||
with get_repository(
|
||||
get_client_id(), DataCollection.ELECTION, settings
|
||||
) as repository:
|
||||
query_result = repository.get({"election_id": election_id})
|
||||
if not query_result:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Could not find election {election_id}",
|
||||
)
|
||||
election = from_query(query_result)
|
||||
election.state = new_state
|
||||
repository.update({"election_id": election_id}, election.dict())
|
||||
return BaseResponse()
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="update election failed",
|
||||
) from error
|
|
@ -0,0 +1,89 @@
|
|||
from typing import Any, List
|
||||
import sys
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
from electionguard.group import ElementModQ
|
||||
from electionguard.serializable import read_json_object, write_json_object
|
||||
|
||||
import electionguard.manifest
|
||||
|
||||
from .client import get_client_id
|
||||
from .repository import get_repository, DataCollection
|
||||
from .settings import Settings
|
||||
from ..api.v1.models import Manifest, ManifestSubmitResponse, ManifestQueryResponse
|
||||
|
||||
# TODO: rework the caching mechanism to reduce the amount of object conversions
|
||||
def from_query(query_result: Any) -> Manifest:
|
||||
sdk_manifest = electionguard.manifest.Manifest.from_json_object(
|
||||
query_result["manifest"]
|
||||
)
|
||||
return Manifest(
|
||||
manifest_hash=write_json_object(sdk_manifest.crypto_hash()),
|
||||
manifest=write_json_object(sdk_manifest),
|
||||
)
|
||||
|
||||
|
||||
def get_manifest(
|
||||
manifest_hash: ElementModQ, settings: Settings = Settings()
|
||||
) -> Manifest:
|
||||
try:
|
||||
with get_repository(
|
||||
get_client_id(), DataCollection.MANIFEST, settings
|
||||
) as repository:
|
||||
query_result = repository.get({"manifest_hash": manifest_hash.to_hex()})
|
||||
if not query_result:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Could not find manifest {manifest_hash.to_hex()}",
|
||||
)
|
||||
|
||||
return from_query(query_result)
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="get manifest failed",
|
||||
) from error
|
||||
|
||||
|
||||
def set_manifest(
|
||||
manifest: Manifest, settings: Settings = Settings()
|
||||
) -> ManifestSubmitResponse:
|
||||
try:
|
||||
with get_repository(
|
||||
get_client_id(), DataCollection.MANIFEST, settings
|
||||
) as repository:
|
||||
manifest_hash = read_json_object(
|
||||
manifest.manifest_hash, ElementModQ
|
||||
).to_hex()
|
||||
_ = repository.set(
|
||||
{"manifest_hash": manifest_hash, "manifest": manifest.manifest}
|
||||
)
|
||||
return ManifestSubmitResponse(manifest_hash=manifest_hash)
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Submit manifest failed",
|
||||
) from error
|
||||
|
||||
|
||||
def filter_manifests(
|
||||
filter: Any, skip: int = 0, limit: int = 1000, settings: Settings = Settings()
|
||||
) -> ManifestQueryResponse:
|
||||
try:
|
||||
with get_repository(
|
||||
get_client_id(), DataCollection.MANIFEST, settings
|
||||
) as repository:
|
||||
cursor = repository.find(filter, skip, limit)
|
||||
manifests: List[Manifest] = []
|
||||
for item in cursor:
|
||||
manifests.append(from_query(item))
|
||||
return ManifestQueryResponse(manifests=manifests)
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="find manifests failed",
|
||||
) from error
|
|
@ -13,7 +13,13 @@ from electionguard.hash import hash_elems
|
|||
|
||||
from .settings import Settings, StorageMode
|
||||
|
||||
__all__ = ["IRepository", "MemoryRepository", "MongoRepository", "get_repository"]
|
||||
__all__ = [
|
||||
"IRepository",
|
||||
"LocalRepository",
|
||||
"MemoryRepository",
|
||||
"MongoRepository",
|
||||
"get_repository",
|
||||
]
|
||||
|
||||
|
||||
DOCUMENT_VALUE_TYPE = Union[MutableMapping, List[MutableMapping]]
|
||||
|
@ -48,13 +54,16 @@ class IRepository(Protocol):
|
|||
|
||||
|
||||
class DataCollection:
|
||||
GUARDIAN = "Guardian"
|
||||
KEY_GUARDIAN = "KeyGuardian"
|
||||
KEY_CEREMONY = "KeyCeremony"
|
||||
ELECTION = "Election"
|
||||
MANIFEST = "Manifest"
|
||||
SUBMITTED_BALLOT = "SubmittedBallots"
|
||||
TALLY = "Tally"
|
||||
GUARDIAN = "guardian"
|
||||
KEY_GUARDIAN = "keyGuardian"
|
||||
KEY_CEREMONY = "keyCeremony"
|
||||
ELECTION = "election"
|
||||
MANIFEST = "manifest"
|
||||
BALLOT_INVENTORY = "ballotInventory"
|
||||
SUBMITTED_BALLOT = "submittedBallots"
|
||||
CIPHERTEXT_TALLY = "ciphertextTally"
|
||||
PLAINTEXT_TALLY = "plaintextTally"
|
||||
DECRYPTION_SHARES = "decryptionShares"
|
||||
|
||||
|
||||
class LocalRepository(IRepository):
|
||||
|
@ -113,7 +122,7 @@ class LocalRepository(IRepository):
|
|||
"""A naive set function that hashes the data and writes the file."""
|
||||
# just ignore lists for now
|
||||
if isinstance(value, List):
|
||||
return None
|
||||
raise Exception("Not Implemented")
|
||||
json_string = json.dumps(dict(value))
|
||||
filename = hash_elems(json_string).to_hex()
|
||||
with open(f"{os.path.join(self._storage, filename)}.json", "w") as file:
|
||||
|
|
|
@ -27,7 +27,12 @@ class Settings(BaseSettings):
|
|||
STORAGE_MODE: StorageMode = StorageMode.MEMORY
|
||||
API_V1_STR: str = "/api/v1"
|
||||
BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = Field(
|
||||
default=["http://localhost", "http://localhost:8080", "https://localhost"]
|
||||
default=[
|
||||
"http://localhost",
|
||||
"http://localhost:8080",
|
||||
"http://localhost:6006",
|
||||
"https://localhost",
|
||||
]
|
||||
)
|
||||
PROJECT_NAME: str = "electionguard-api-python"
|
||||
MONGODB_URI: str = "mongodb://root:example@localhost:27017"
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
from typing import Any, List
|
||||
import sys
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
from .repository import get_repository, DataCollection
|
||||
from .settings import Settings
|
||||
from ..api.v1.models import BaseResponse, CiphertextTally, PlaintextTally
|
||||
|
||||
|
||||
def ciphertext_tally_from_query(query_result: Any) -> CiphertextTally:
|
||||
return CiphertextTally(
|
||||
election_id=query_result["election_id"],
|
||||
tally_name=query_result["tally_name"],
|
||||
created=query_result["created"],
|
||||
tally=query_result["tally"],
|
||||
)
|
||||
|
||||
|
||||
def plaintext_tally_from_query(query_result: Any) -> PlaintextTally:
|
||||
return PlaintextTally(
|
||||
election_id=query_result["election_id"],
|
||||
tally_name=query_result["tally_name"],
|
||||
created=query_result["created"],
|
||||
state=query_result["state"],
|
||||
tally=query_result["tally"],
|
||||
)
|
||||
|
||||
|
||||
def get_ciphertext_tally(
|
||||
election_id: str, tally_name: str, settings: Settings = Settings()
|
||||
) -> CiphertextTally:
|
||||
try:
|
||||
with get_repository(
|
||||
election_id, DataCollection.CIPHERTEXT_TALLY, settings
|
||||
) as repository:
|
||||
query_result = repository.get(
|
||||
{"election_id": election_id, "tally_name": tally_name}
|
||||
)
|
||||
if not query_result:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Could not find tally {election_id} {tally_name}",
|
||||
)
|
||||
return ciphertext_tally_from_query(query_result)
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="get ciphertext tally failed",
|
||||
) from error
|
||||
|
||||
|
||||
def set_ciphertext_tally(
|
||||
tally: CiphertextTally, settings: Settings = Settings()
|
||||
) -> BaseResponse:
|
||||
try:
|
||||
with get_repository(
|
||||
tally.election_id, DataCollection.CIPHERTEXT_TALLY, settings
|
||||
) as repository:
|
||||
repository.set(tally.dict())
|
||||
return BaseResponse()
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="set ciphertext tally failed",
|
||||
) from error
|
||||
|
||||
|
||||
def filter_ciphertext_tallies(
|
||||
election_id: str,
|
||||
filter: Any,
|
||||
skip: int = 0,
|
||||
limit: int = 1000,
|
||||
settings: Settings = Settings(),
|
||||
) -> List[CiphertextTally]:
|
||||
try:
|
||||
with get_repository(
|
||||
election_id, DataCollection.CIPHERTEXT_TALLY, settings
|
||||
) as repository:
|
||||
cursor = repository.find(filter, skip, limit)
|
||||
tallies: List[CiphertextTally] = []
|
||||
for item in cursor:
|
||||
tallies.append(ciphertext_tally_from_query(item))
|
||||
return tallies
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="filter ciphertext tallies failed",
|
||||
) from error
|
||||
|
||||
|
||||
def get_plaintext_tally(
|
||||
election_id: str, tally_name: str, settings: Settings = Settings()
|
||||
) -> PlaintextTally:
|
||||
try:
|
||||
with get_repository(
|
||||
election_id, DataCollection.PLAINTEXT_TALLY, settings
|
||||
) as repository:
|
||||
query_result = repository.get(
|
||||
{"election_id": election_id, "tally_name": tally_name}
|
||||
)
|
||||
if not query_result:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Could not find tally {election_id} {tally_name}",
|
||||
)
|
||||
return plaintext_tally_from_query(query_result)
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="get plaintext tally failed",
|
||||
) from error
|
||||
|
||||
|
||||
def set_plaintext_tally(
|
||||
tally: PlaintextTally, settings: Settings = Settings()
|
||||
) -> BaseResponse:
|
||||
try:
|
||||
with get_repository(
|
||||
tally.election_id, DataCollection.PLAINTEXT_TALLY, settings
|
||||
) as repository:
|
||||
repository.set(tally.dict())
|
||||
return BaseResponse()
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="set plaintext tally failed",
|
||||
) from error
|
||||
|
||||
|
||||
def update_plaintext_tally(
|
||||
tally: PlaintextTally, settings: Settings = Settings()
|
||||
) -> BaseResponse:
|
||||
try:
|
||||
with get_repository(
|
||||
tally.election_id, DataCollection.PLAINTEXT_TALLY, settings
|
||||
) as repository:
|
||||
query_result = repository.get(
|
||||
{"election_id": tally.election_id, "tally_name": tally.tally_name}
|
||||
)
|
||||
if not query_result:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Could not find plaintext tally {tally.election_id} {tally.tally_name}",
|
||||
)
|
||||
repository.update(
|
||||
{"election_id": tally.election_id, "tally_name": tally.tally_name},
|
||||
tally.dict(),
|
||||
)
|
||||
return BaseResponse()
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="update plaintext tally failed",
|
||||
) from error
|
||||
|
||||
|
||||
def filter_plaintext_tallies(
|
||||
election_id: str,
|
||||
filter: Any,
|
||||
skip: int = 0,
|
||||
limit: int = 1000,
|
||||
settings: Settings = Settings(),
|
||||
) -> List[PlaintextTally]:
|
||||
try:
|
||||
with get_repository(
|
||||
election_id, DataCollection.PLAINTEXT_TALLY, settings
|
||||
) as repository:
|
||||
cursor = repository.find(filter, skip, limit)
|
||||
tallies: List[PlaintextTally] = []
|
||||
for item in cursor:
|
||||
tallies.append(plaintext_tally_from_query(item))
|
||||
return tallies
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="filter plaintext tallies failed",
|
||||
) from error
|
|
@ -0,0 +1,86 @@
|
|||
from typing import Any, List
|
||||
import sys
|
||||
from fastapi import HTTPException, status
|
||||
|
||||
from .repository import get_repository, DataCollection
|
||||
from .settings import Settings
|
||||
from ..api.v1.models import BaseResponse, CiphertextTallyDecryptionShare
|
||||
|
||||
|
||||
def from_query(query_result: Any) -> CiphertextTallyDecryptionShare:
|
||||
return CiphertextTallyDecryptionShare(
|
||||
election_id=query_result["election_id"],
|
||||
tally_name=query_result["tally_name"],
|
||||
guardian_id=query_result["guardian_id"],
|
||||
tally_share=query_result["tally_share"],
|
||||
ballot_shares=query_result["ballot_shares"],
|
||||
)
|
||||
|
||||
|
||||
def get_decryption_share(
|
||||
election_id: str, tally_name: str, guardian_id: str, settings: Settings = Settings()
|
||||
) -> CiphertextTallyDecryptionShare:
|
||||
try:
|
||||
with get_repository(
|
||||
tally_name, DataCollection.DECRYPTION_SHARES, settings
|
||||
) as repository:
|
||||
query_result = repository.get(
|
||||
{
|
||||
"election_id": election_id,
|
||||
"tally_name": tally_name,
|
||||
"guardian_id": guardian_id,
|
||||
}
|
||||
)
|
||||
if not query_result:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Could not find decryption share {election_id} {tally_name} {guardian_id}",
|
||||
)
|
||||
return from_query(query_result)
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="get decryption share failed",
|
||||
) from error
|
||||
|
||||
|
||||
def set_decryption_share(
|
||||
decryption_share: CiphertextTallyDecryptionShare, settings: Settings = Settings()
|
||||
) -> BaseResponse:
|
||||
try:
|
||||
with get_repository(
|
||||
decryption_share.tally_name, DataCollection.DECRYPTION_SHARES, settings
|
||||
) as repository:
|
||||
repository.set(decryption_share.dict())
|
||||
return BaseResponse()
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="set decryption share failed",
|
||||
) from error
|
||||
|
||||
|
||||
def filter_decryption_shares(
|
||||
tally_name: str,
|
||||
filter: Any,
|
||||
skip: int = 0,
|
||||
limit: int = 1000,
|
||||
settings: Settings = Settings(),
|
||||
) -> List[CiphertextTallyDecryptionShare]:
|
||||
try:
|
||||
with get_repository(
|
||||
tally_name, DataCollection.DECRYPTION_SHARES, settings
|
||||
) as repository:
|
||||
cursor = repository.find(filter, skip, limit)
|
||||
decryption_shares: List[CiphertextTallyDecryptionShare] = []
|
||||
for item in cursor:
|
||||
decryption_shares.append(from_query(item))
|
||||
return decryption_shares
|
||||
except Exception as error:
|
||||
print(sys.exc_info())
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="find decryption shares failed",
|
||||
) from error
|
|
@ -1,403 +0,0 @@
|
|||
{
|
||||
"geopolitical_units": [
|
||||
{
|
||||
"object_id": "jefferson-county",
|
||||
"name": "Jefferson County",
|
||||
"type": "county",
|
||||
"contact_information": {
|
||||
"address_line": [
|
||||
"1234 Samuel Adams Way",
|
||||
"Jefferson, Hamilton 999999"
|
||||
],
|
||||
"name": "Jefferson County Clerk",
|
||||
"email": [
|
||||
{
|
||||
"annotation": "inquiries",
|
||||
"value": "inquiries@jefferson.hamilton.state.gov"
|
||||
}
|
||||
],
|
||||
"phone": [
|
||||
{
|
||||
"annotation": "domestic",
|
||||
"value": "123-456-7890"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"object_id": "harrison-township",
|
||||
"name": "Harrison Township",
|
||||
"type": "township",
|
||||
"contact_information": {
|
||||
"address_line": [
|
||||
"1234 Thorton Drive",
|
||||
"Harrison, Hamilton 999999"
|
||||
],
|
||||
"name": "Harrison Town Hall",
|
||||
"email": [
|
||||
{
|
||||
"annotation": "inquiries",
|
||||
"value": "inquiries@harrison.hamilton.state.gov"
|
||||
}
|
||||
],
|
||||
"phone": [
|
||||
{
|
||||
"annotation": "domestic",
|
||||
"value": "123-456-7890"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"object_id": "harrison-township-precinct-east",
|
||||
"name": "Harrison Township Precinct",
|
||||
"type": "township",
|
||||
"contact_information": {
|
||||
"address_line": [
|
||||
"1234 Thorton Drive",
|
||||
"Harrison, Hamilton 999999"
|
||||
],
|
||||
"name": "Harrison Town Hall",
|
||||
"email": [
|
||||
{
|
||||
"annotation": "inquiries",
|
||||
"value": "inquiries@harrison.hamilton.state.gov"
|
||||
}
|
||||
],
|
||||
"phone": [
|
||||
{
|
||||
"annotation": "domestic",
|
||||
"value": "123-456-7890"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"object_id": "rutledge-elementary",
|
||||
"name": "Rutledge Elementary School district",
|
||||
"type": "school",
|
||||
"contact_information": {
|
||||
"address_line": [
|
||||
"1234 Wolcott Parkway",
|
||||
"Harrison, Hamilton 999999"
|
||||
],
|
||||
"name": "Rutledge Elementary School",
|
||||
"email": [
|
||||
{
|
||||
"annotation": "inquiries",
|
||||
"value": "inquiries@harrison.hamilton.state.gov"
|
||||
}
|
||||
],
|
||||
"phone": [
|
||||
{
|
||||
"annotation": "domestic",
|
||||
"value": "123-456-7890"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"parties": [
|
||||
{
|
||||
"object_id": "whig",
|
||||
"abbreviation": "WHI",
|
||||
"color": "AAAAAA",
|
||||
"logo_uri": "http://some/path/to/whig.svg",
|
||||
"name": {
|
||||
"text": [
|
||||
{
|
||||
"value": "Whig Party",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"object_id": "federalist",
|
||||
"abbreviation": "FED",
|
||||
"color": "CCCCCC",
|
||||
"logo_uri": "http://some/path/to/federalist.svg",
|
||||
"name": {
|
||||
"text": [
|
||||
{
|
||||
"value": "Federalist Party",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"object_id": "democratic-republican",
|
||||
"abbreviation": "DEMREP",
|
||||
"color": "EEEEEE",
|
||||
"logo_uri": "http://some/path/to/democratic-repulbican.svg",
|
||||
"name": {
|
||||
"text": [
|
||||
{
|
||||
"value": "Democratic Republican Party",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"candidates": [
|
||||
{
|
||||
"object_id": "benjamin-franklin",
|
||||
"ballot_name": {
|
||||
"text": [
|
||||
{
|
||||
"value": "Benjamin Franklin",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
},
|
||||
"party_id": "whig"
|
||||
},
|
||||
{
|
||||
"object_id": "john-adams",
|
||||
"ballot_name": {
|
||||
"text": [
|
||||
{
|
||||
"value": "John Adams",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
},
|
||||
"party_id": "federalist"
|
||||
},
|
||||
{
|
||||
"object_id": "john-hancock",
|
||||
"ballot_name": {
|
||||
"text": [
|
||||
{
|
||||
"value": "John Hancock",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
},
|
||||
"party_id": "democratic-republican"
|
||||
},
|
||||
{
|
||||
"object_id": "write-in",
|
||||
"ballot_name": {
|
||||
"text": [
|
||||
{
|
||||
"value": "Write In Candidate",
|
||||
"language": "en"
|
||||
},
|
||||
{
|
||||
"value": "Escribir en la candidata",
|
||||
"language": "es"
|
||||
}
|
||||
]
|
||||
},
|
||||
"is_write_in": true
|
||||
},
|
||||
{
|
||||
"object_id": "referendum-pineapple-affirmative",
|
||||
"ballot_name": {
|
||||
"text": [
|
||||
{
|
||||
"value": "Pineapple should be banned on pizza",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"object_id": "referendum-pineapple-negative",
|
||||
"ballot_name": {
|
||||
"text": [
|
||||
{
|
||||
"value": "Pineapple should not be banned on pizza",
|
||||
"language": "en"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"contests": [
|
||||
{
|
||||
"@type": "CandidateContest",
|
||||
"object_id": "justice-supreme-court",
|
||||
"sequence_order": 0,
|
||||
"ballot_selections": [
|
||||
{
|
||||
"object_id": "john-adams-selection",
|
||||
"sequence_order": 0,
|
||||
"candidate_id": "john-adams"
|
||||
},
|
||||
{
|
||||
"object_id": "benjamin-franklin-selection",
|
||||
"sequence_order": 1,
|
||||
"candidate_id": "benjamin-franklin"
|
||||
},
|
||||
{
|
||||
"object_id": "john-hancock-selection",
|
||||
"sequence_order": 2,
|
||||
"candidate_id": "john-hancock"
|
||||
},
|
||||
{
|
||||
"object_id": "write-in-selection",
|
||||
"sequence_order": 3,
|
||||
"candidate_id": "write-in"
|
||||
}
|
||||
],
|
||||
"ballot_title": {
|
||||
"text": [
|
||||
{
|
||||
"value": "Justice of the Supreme Court",
|
||||
"language": "en"
|
||||
},
|
||||
{
|
||||
"value": "Juez de la corte suprema",
|
||||
"language": "es"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ballot_subtitle": {
|
||||
"text": [
|
||||
{
|
||||
"value": "Please choose up to two candidates",
|
||||
"language": "en"
|
||||
},
|
||||
{
|
||||
"value": "Uno",
|
||||
"language": "es"
|
||||
}
|
||||
]
|
||||
},
|
||||
"vote_variation": "n_of_m",
|
||||
"electoral_district_id": "jefferson-county",
|
||||
"name": "Justice of the Supreme Court",
|
||||
"primary_party_ids": [
|
||||
"whig",
|
||||
"federalist"
|
||||
],
|
||||
"number_elected": 2,
|
||||
"votes_allowed": 2
|
||||
},
|
||||
{
|
||||
"@type": "ReferendumContest",
|
||||
"object_id": "referendum-pineapple",
|
||||
"sequence_order": 1,
|
||||
"ballot_selections": [
|
||||
{
|
||||
"object_id": "referendum-pineapple-affirmative-selection",
|
||||
"sequence_order": 0,
|
||||
"candidate_id": "referendum-pineapple-affirmative"
|
||||
},
|
||||
{
|
||||
"object_id": "referendum-pineapple-negative-selection",
|
||||
"sequence_order": 1,
|
||||
"candidate_id": "referendum-pineapple-negative"
|
||||
}
|
||||
],
|
||||
"ballot_title": {
|
||||
"text": [
|
||||
{
|
||||
"value": "Should pineapple be banned on pizza?",
|
||||
"language": "en"
|
||||
},
|
||||
{
|
||||
"value": "¿Debería prohibirse la piña en la pizza?",
|
||||
"language": "es"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ballot_subtitle": {
|
||||
"text": [
|
||||
{
|
||||
"value": "The township considers this issue to be very important",
|
||||
"language": "en"
|
||||
},
|
||||
{
|
||||
"value": "El municipio considera que esta cuestión es muy importante",
|
||||
"language": "es"
|
||||
}
|
||||
]
|
||||
},
|
||||
"vote_variation": "one_of_m",
|
||||
"electoral_district_id": "harrison-township",
|
||||
"name": "The Pineapple Question",
|
||||
"number_elected": 1,
|
||||
"votes_allowed": 1
|
||||
}
|
||||
],
|
||||
"ballot_styles": [
|
||||
{
|
||||
"object_id": "jefferson-county-ballot-style",
|
||||
"geopolitical_unit_ids": [
|
||||
"jefferson-county"
|
||||
]
|
||||
},
|
||||
{
|
||||
"object_id": "harrison-township-ballot-style",
|
||||
"geopolitical_unit_ids": [
|
||||
"jefferson-county",
|
||||
"harrison-township"
|
||||
]
|
||||
},
|
||||
{
|
||||
"object_id": "harrison-township-precinct-east-ballot-style",
|
||||
"geopolitical_unit_ids": [
|
||||
"jefferson-county",
|
||||
"harrison-township",
|
||||
"harrison-township-precinct-east",
|
||||
"rutledge-elementary"
|
||||
]
|
||||
},
|
||||
{
|
||||
"object_id": "rutledge-elementary-ballot-style",
|
||||
"geopolitical_unit_ids": [
|
||||
"jefferson-county",
|
||||
"harrison-township",
|
||||
"rutledge-elementary"
|
||||
]
|
||||
}
|
||||
],
|
||||
"name": {
|
||||
"text": [
|
||||
{
|
||||
"value": "Jefferson County Spring Primary",
|
||||
"language": "en"
|
||||
},
|
||||
{
|
||||
"value": "Primaria de primavera del condado de Jefferson",
|
||||
"language": "es"
|
||||
}
|
||||
]
|
||||
},
|
||||
"contact_information": {
|
||||
"address_line": [
|
||||
"1234 Paul Revere Run",
|
||||
"Jefferson, Hamilton 999999"
|
||||
],
|
||||
"name": "Hamilton State Election Commission",
|
||||
"email": [
|
||||
{
|
||||
"annotation": "press",
|
||||
"value": "inquiries@hamilton.state.gov"
|
||||
},
|
||||
{
|
||||
"annotation": "federal",
|
||||
"value": "commissioner@hamilton.state.gov"
|
||||
}
|
||||
],
|
||||
"phone": [
|
||||
{
|
||||
"annotation": "domestic",
|
||||
"value": "123-456-7890"
|
||||
},
|
||||
{
|
||||
"annotation": "international",
|
||||
"value": "+1-123-456-7890"
|
||||
}
|
||||
]
|
||||
},
|
||||
"start_date": "2020-03-01T08:00:00-05:00",
|
||||
"end_date": "2020-03-01T20:00:00-05:00",
|
||||
"election_scope_id": "jefferson-county-primary",
|
||||
"type": "primary"
|
||||
}
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1 @@
|
|||
{"contests": [{"ballot_selections": [{"is_placeholder_selection": false, "object_id": "barchi-hallaren-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "cramer-vuocolo-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "court-blumhardt-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "boone-lian-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "patterson-lariviere-selection", "vote": 0}], "object_id": "president-vice-president-contest"}, {"ballot_selections": [{"is_placeholder_selection": false, "object_id": "franz-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "harris-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "bargmann-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "abcock-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "williams-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "alpern-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "sharp-althea-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "alexander-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "lee-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "kennedy-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "jackson-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "brown-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "teller-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "ward-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "chandler-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "write-in-selection-governor", "vote": 0}], "object_id": "ozark-governor"}, {"ballot_selections": [{"is_placeholder_selection": false, "object_id": "bainbridge-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "hennessey-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "tawa-mary-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "write-in-selection-us-congress-district-7", "vote": 0}], "object_id": "congress-district-7-contest"}, {"ballot_selections": [{"is_placeholder_selection": false, "object_id": "moore-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "white-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "smallberries-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "warfin-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "norberg-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "write-in-selection-2-pismo-beach-school-board", "vote": 0}, {"is_placeholder_selection": false, "object_id": "write-in-selection-3-pismo-beach-school-board", "vote": 0}], "object_id": "pismo-beach-school-board-contest"}, {"ballot_selections": [{"is_placeholder_selection": false, "object_id": "ozark-chief-justice-retain-demergue-affirmative-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "ozark-chief-justice-retain-demergue-negative-selection", "vote": 1}], "object_id": "arlington-chief-justice-retain-demergue"}], "object_id": "ballot-1663ab54-e95f-11eb-bd0c-acde48001122", "style_id": "congress-district-7-arlington-pismo-beach"}
|
|
@ -0,0 +1 @@
|
|||
{"contests": [{"ballot_selections": [{"is_placeholder_selection": false, "object_id": "barchi-hallaren-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "cramer-vuocolo-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "hildebrand-garritty-selection", "vote": 0}], "object_id": "president-vice-president-contest"}, {"ballot_selections": [{"is_placeholder_selection": false, "object_id": "harris-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "bargmann-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "abcock-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "walace-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "windbeck-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "sharp-althea-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "greher-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "mitchell-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "lee-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "teller-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "york-selection", "vote": 0}], "object_id": "ozark-governor"}, {"ballot_selections": [{"is_placeholder_selection": false, "object_id": "soliz-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "keller-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "write-in-selection-us-congress-district-5", "vote": 0}], "object_id": "congress-district-5-contest"}, {"ballot_selections": [{"is_placeholder_selection": false, "object_id": "white-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "smallberries-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "warfin-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "norberg-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "parks-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "write-in-selection-1-pismo-beach-school-board", "vote": 0}, {"is_placeholder_selection": false, "object_id": "write-in-selection-2-pismo-beach-school-board", "vote": 0}, {"is_placeholder_selection": false, "object_id": "write-in-selection-3-pismo-beach-school-board", "vote": 0}], "object_id": "pismo-beach-school-board-contest"}, {"ballot_selections": [{"is_placeholder_selection": false, "object_id": "ozark-chief-justice-retain-demergue-affirmative-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "ozark-chief-justice-retain-demergue-negative-selection", "vote": 1}], "object_id": "arlington-chief-justice-retain-demergue"}], "object_id": "ballot-1663bbee-e95f-11eb-bd0c-acde48001122", "style_id": "congress-district-5-arlington-pismo-beach"}
|
|
@ -0,0 +1 @@
|
|||
{"contests": [{"ballot_selections": [{"is_placeholder_selection": false, "object_id": "cramer-vuocolo-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "court-blumhardt-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "boone-lian-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "hildebrand-garritty-selection", "vote": 0}], "object_id": "president-vice-president-contest"}, {"ballot_selections": [{"is_placeholder_selection": false, "object_id": "franz-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "sharp-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "greher-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "lee-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "ash-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "kennedy-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "brown-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "callanann-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "york-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "chandler-selection", "vote": 0}], "object_id": "ozark-governor"}, {"ballot_selections": [{"is_placeholder_selection": false, "object_id": "soliz-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "keller-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "rangel-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "argent-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "write-in-selection-us-congress-district-5", "vote": 0}], "object_id": "congress-district-5-contest"}], "object_id": "ballot-1663cdc8-e95f-11eb-bd0c-acde48001122", "style_id": "congress-district-5-lacroix"}
|
|
@ -0,0 +1 @@
|
|||
{"contests": [{"ballot_selections": [{"is_placeholder_selection": false, "object_id": "cramer-vuocolo-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "court-blumhardt-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "patterson-lariviere-selection", "vote": 0}], "object_id": "president-vice-president-contest"}, {"ballot_selections": [{"is_placeholder_selection": false, "object_id": "franz-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "bargmann-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "greher-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "alexander-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "lee-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "jackson-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "ward-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "murphy-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "york-selection", "vote": 0}], "object_id": "ozark-governor"}, {"ballot_selections": [{"is_placeholder_selection": false, "object_id": "bainbridge-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "hennessey-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "savoy-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "write-in-selection-us-congress-district-7", "vote": 0}], "object_id": "congress-district-7-contest"}, {"ballot_selections": [{"is_placeholder_selection": false, "object_id": "exeter-utility-district-referendum-affirmative-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "exeter-utility-district-referendum-selection", "vote": 0}], "object_id": "exeter-utility-district-referendum-contest"}], "object_id": "ballot-1663d854-e95f-11eb-bd0c-acde48001122", "style_id": "congress-district-7-lacroix-exeter"}
|
|
@ -0,0 +1 @@
|
|||
{"contests": [{"ballot_selections": [{"is_placeholder_selection": false, "object_id": "barchi-hallaren-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "cramer-vuocolo-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "boone-lian-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "hildebrand-garritty-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "patterson-lariviere-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "write-in-selection-president", "vote": 0}], "object_id": "president-vice-president-contest"}, {"ballot_selections": [{"is_placeholder_selection": false, "object_id": "franz-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "harris-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "bargmann-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "abcock-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "steel-loy-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "sharp-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "alexander-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "lee-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "kennedy-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "teller-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "ward-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "murphy-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "newman-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "york-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "chandler-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "write-in-selection-governor", "vote": 0}], "object_id": "ozark-governor"}, {"ballot_selections": [{"is_placeholder_selection": false, "object_id": "soliz-selection", "vote": 1}, {"is_placeholder_selection": false, "object_id": "keller-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "rangel-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "argent-selection", "vote": 0}, {"is_placeholder_selection": false, "object_id": "write-in-selection-us-congress-district-5", "vote": 0}], "object_id": "congress-district-5-contest"}], "object_id": "ballot-1663e3e4-e95f-11eb-bd0c-acde48001122", "style_id": "congress-district-5-lacroix"}
|
|
@ -1,7 +1,10 @@
|
|||
db.createCollection('Guardian');
|
||||
db.createCollection('KeyGuardian');
|
||||
db.createCollection('KeyCeremony')
|
||||
db.createCollection('Election');
|
||||
db.createCollection('Manifest');
|
||||
db.createCollection('SubmittedBallots');
|
||||
db.createCollection('Tally');
|
||||
db.createCollection('guardian');
|
||||
db.createCollection('keyGuardian');
|
||||
db.createCollection('keyCeremony')
|
||||
db.createCollection('election');
|
||||
db.createCollection('manifest');
|
||||
db.createCollection('ballotInventory');
|
||||
db.createCollection('submittedBallots');
|
||||
db.createCollection('ciphertextTally');
|
||||
db.createCollection('plaintextTally');
|
||||
db.createCollection('decryptionShares');
|
|
@ -0,0 +1,564 @@
|
|||
This is a full rundown of example requests to test an end to end election
|
||||
|
||||
## Key Ceremony
|
||||
|
||||
mediator > Create Key Ceremony, Open Key Ceremony
|
||||
|
||||
```
|
||||
{
|
||||
"key_name": "key_ceremony_1",
|
||||
"number_of_guardians": 3,
|
||||
"quorum": 2,
|
||||
"guardian_ids": [
|
||||
"guardian_1",
|
||||
"guardian_2",
|
||||
"guardian_3"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mediator > Create Key Ceremony Guardian (repeat for all)
|
||||
|
||||
```
|
||||
{
|
||||
"key_name": "key_ceremony_1",
|
||||
"guardian_id": "guardian_1",
|
||||
"name": "Benjamin Franklin",
|
||||
"sequence_order": 1,
|
||||
"number_of_guardians": 3,
|
||||
"quorum": 2
|
||||
}
|
||||
|
||||
{
|
||||
"key_name": "key_ceremony_1",
|
||||
"guardian_id": "guardian_2",
|
||||
"name": "Not Benjamin Franklin",
|
||||
"sequence_order": 2,
|
||||
"number_of_guardians": 3,
|
||||
"quorum": 2
|
||||
}
|
||||
|
||||
{
|
||||
"key_name": "key_ceremony_1",
|
||||
"guardian_id": "guardian_3",
|
||||
"name": "Definitely Not Benjamin Franklin",
|
||||
"sequence_order": 3,
|
||||
"number_of_guardians": 3,
|
||||
"quorum": 2
|
||||
}
|
||||
```
|
||||
|
||||
guardian > Create Guardian (repeat for all guardians)
|
||||
|
||||
```
|
||||
{
|
||||
"guardian_id": "guardian_1",
|
||||
"sequence_order": 1,
|
||||
"number_of_guardians": 3,
|
||||
"quorum": 2
|
||||
}
|
||||
{
|
||||
"guardian_id": "guardian_2",
|
||||
"sequence_order": 2,
|
||||
"number_of_guardians": 3,
|
||||
"quorum": 2
|
||||
}
|
||||
{
|
||||
"guardian_id": "guardian_3",
|
||||
"sequence_order": 3,
|
||||
"number_of_guardians": 3,
|
||||
"quorum": 2
|
||||
}
|
||||
```
|
||||
|
||||
guardian > Fetch Public Keys (repeat for all guardians) - make note of them
|
||||
|
||||
```
|
||||
{
|
||||
"status": "success",
|
||||
"message": null,
|
||||
"public_keys": {
|
||||
"election": {
|
||||
"owner_id": "guardian_1",
|
||||
"sequence_order": 1,
|
||||
"key": "6A84EAC91199C7A5E65AA0646F72F2CC01623E6F651C0DA6B5F81E89BA050CA4DFF245D2FE0BF020C096939A5D396C7836D6F95ECE848DAAE5DD7402A66B302C627C7C53469533E1AD5916DE51EE6C91C74C67FE9BEE61E3328FC55D835870E265EA5A524C7C871EF5DFC20550431793D9AA4F68A0A23642785CDA82F69A6663995B1313C7315AE9AAFB5319E93DEF1589C7BFBE56B6BE04E70573C4F94EF710012C187162C1D8DF3A407F4FD9C4559F328DE822F031800D5FAA3CBD4E8BD88083E01C8A8D0E5113963964832B1FB522B0CAB452F53E5E48D8A8725989BD77E1AEA4BED1100F5704C87221DD9057ED866FA8BF3F06D2D048B65785DB753080476E62D251D70BC5975ABC16C5D624779025C6EA9291C89A9823199ECCD2827C3187C23E2935B3E1A16BE00711C14278748B52F276B6634F7B9DD5A47FBDA5D71F7066EAD8440FCC0A6A7374E617C22C1EF100ADDE7B9525AEA317C806409D5B0A9E2C65F70DE80A260519C4E9914802EA1F65AF2C55FC5F526C7FA15B0CDA4BC5EC6277036775B04E9A2539EC150D501E3DE0969E654445E44E8FD1AB50839C594ADCCFC5E3BC73EE7740EDF83ECE8C873D73A48840857513CC848FCA3127EBDF96F4F68F01D1D706912BE5A1D4E36B90B137FD1861CEE2D986A3AA96C0B184D2DED74A5705889DCCFDF7143448C2C3290A6BF90496021C18FEFD7D07A0EE84AE",
|
||||
"coefficient_commitments": [
|
||||
"6A84EAC91199C7A5E65AA0646F72F2CC01623E6F651C0DA6B5F81E89BA050CA4DFF245D2FE0BF020C096939A5D396C7836D6F95ECE848DAAE5DD7402A66B302C627C7C53469533E1AD5916DE51EE6C91C74C67FE9BEE61E3328FC55D835870E265EA5A524C7C871EF5DFC20550431793D9AA4F68A0A23642785CDA82F69A6663995B1313C7315AE9AAFB5319E93DEF1589C7BFBE56B6BE04E70573C4F94EF710012C187162C1D8DF3A407F4FD9C4559F328DE822F031800D5FAA3CBD4E8BD88083E01C8A8D0E5113963964832B1FB522B0CAB452F53E5E48D8A8725989BD77E1AEA4BED1100F5704C87221DD9057ED866FA8BF3F06D2D048B65785DB753080476E62D251D70BC5975ABC16C5D624779025C6EA9291C89A9823199ECCD2827C3187C23E2935B3E1A16BE00711C14278748B52F276B6634F7B9DD5A47FBDA5D71F7066EAD8440FCC0A6A7374E617C22C1EF100ADDE7B9525AEA317C806409D5B0A9E2C65F70DE80A260519C4E9914802EA1F65AF2C55FC5F526C7FA15B0CDA4BC5EC6277036775B04E9A2539EC150D501E3DE0969E654445E44E8FD1AB50839C594ADCCFC5E3BC73EE7740EDF83ECE8C873D73A48840857513CC848FCA3127EBDF96F4F68F01D1D706912BE5A1D4E36B90B137FD1861CEE2D986A3AA96C0B184D2DED74A5705889DCCFDF7143448C2C3290A6BF90496021C18FEFD7D07A0EE84AE",
|
||||
"F87B71E1F7EDD7B91A1320FCCBBD44376E370BCE4B5177C237A944BA45A1151A9604C24AA912E990F21D0CB195E5B5A1DA88E7D2F5F630CD956478454073B5DC445798B22CEE8F54FB6C24676DD5F6AADDB6C005BF30E398396760CE7010366A90AF9EA5058AF7DCBC610BC600939695B947A37786841548A30AD252BD264671EF0BF9906875EE82862633297FEEC1E75C8FEF68E0EF3545A9C16C2F4DD62BB54C4EF84C396180D96B403D4400AFBABEE36925D379213260F00037EFD9CC650A9B4AADBA47869345351409E303E9D976A9692CCD757A12D02852644BA9501A909EFB7484AA43C96D0813110F1AEBDB192E5DD3E032D78F0E0A49D5556F1B33224D9F3D4A20D5C06A9DE2E02454C85691E3BFD88C8BFA132DD79794C9213EA5891FC2F28A4EC3FBCB7535AB91CCBF02D7E96B70BD187B44CCAC4CD0C2720B240EDB301DC32B750428B66B316C747A57CD062EFF6CFE7ED9058A818F36613EF627E57ABC43F3C9A9834B30BE2D07EA0217EEB3458222756ECA296A57B87FEA24B0C3EAEA5D6DED6863EE11D79F4F88AFD2EC2B385465CFB8E557609686A6246A781D952A487DFE2B4E93547AD9C05C08A496BD8171519C799B73E5EB008D2E2A0DA3DC51084E1BBB29F78836A4C0DE13714CBBFB0D448ADFD241841062507F0B803145710EFDF47368FD982141652B848881B20A2C9FE5DFEEA36CE6D15CC9D9E7"
|
||||
],
|
||||
"coefficient_proofs": [
|
||||
{
|
||||
"challenge": "AEEBE8ED2110C5B0D63137A104D52EEAC9131C58A0BB18B80E9FAF0988C51281",
|
||||
"commitment": "A1B78A1DD6595AED57B8A98C5A1B1A12256277BC03ABB06CCBD0A57370309EC58E67995C64131A71214C5ABB6603DC49FB167F1291222106B1F42D80BFCD9C24372E821027520DCABB14B84DE5B605991354BF02091E8B708A3C1A43A1E2CAE538249C4CBAC406A352B1D01020451EA53D0AF5FD12AF7514302A6DF7373D1B9201EF60F41FE6C786DC3D2EDC1548A4E10A26C9B01A47E67DBF3A17CD29BCAAD4AF4315D5076BDBD0B036FA1247854BD3FCE562047194CC0C48A46EF827B86A654BDB6E6DC6A045F6A6A96492B7774E67E27572046D26E58B308AEF1351704DB1D0D87D71EF39A84C1F864430B835E94FACB5600BCBE3C47AB4C96A4857137348D8C45D1F536B86848348FBA09D6281CDC39B1FF608E84B08FD53E0F0C5B6ADDE67BC145B1DA61F79356774DAB76781AC9197498B924FD24E9F48C67E699007CFF675F1E5D7EEB0F15AA147C2ECB0A8F79340141D6C1FFBAFB7F1250E83443E4712440AEB1B150CAEEFD29789799FA3603DBA9F3E297FED0B8934C7EAA3C90D567C35D1DCBABA6E7158F76E9FCDCC7393EED39456C527235D442B7D4DA5AE418E02C572FFA510B22AD83EB4DC4A333B52E47E56E43FD0345187CA7F45DDCB6A6F6E9B3D7842B097752F08261743958866ADB008CAFD5BE9A0E91CB5C7F7B8BCCF7FF6C85AF02C1AA39DF7022F80A1C0AF56C55A86A6D865C4298006553EF11FC5",
|
||||
"name": "Schnorr Proof",
|
||||
"public_key": "6A84EAC91199C7A5E65AA0646F72F2CC01623E6F651C0DA6B5F81E89BA050CA4DFF245D2FE0BF020C096939A5D396C7836D6F95ECE848DAAE5DD7402A66B302C627C7C53469533E1AD5916DE51EE6C91C74C67FE9BEE61E3328FC55D835870E265EA5A524C7C871EF5DFC20550431793D9AA4F68A0A23642785CDA82F69A6663995B1313C7315AE9AAFB5319E93DEF1589C7BFBE56B6BE04E70573C4F94EF710012C187162C1D8DF3A407F4FD9C4559F328DE822F031800D5FAA3CBD4E8BD88083E01C8A8D0E5113963964832B1FB522B0CAB452F53E5E48D8A8725989BD77E1AEA4BED1100F5704C87221DD9057ED866FA8BF3F06D2D048B65785DB753080476E62D251D70BC5975ABC16C5D624779025C6EA9291C89A9823199ECCD2827C3187C23E2935B3E1A16BE00711C14278748B52F276B6634F7B9DD5A47FBDA5D71F7066EAD8440FCC0A6A7374E617C22C1EF100ADDE7B9525AEA317C806409D5B0A9E2C65F70DE80A260519C4E9914802EA1F65AF2C55FC5F526C7FA15B0CDA4BC5EC6277036775B04E9A2539EC150D501E3DE0969E654445E44E8FD1AB50839C594ADCCFC5E3BC73EE7740EDF83ECE8C873D73A48840857513CC848FCA3127EBDF96F4F68F01D1D706912BE5A1D4E36B90B137FD1861CEE2D986A3AA96C0B184D2DED74A5705889DCCFDF7143448C2C3290A6BF90496021C18FEFD7D07A0EE84AE",
|
||||
"response": "FA8357662E152DFDA8FAC336748B56F6FB42AE8E81F8FD74A957E6BFFC2C3D4C",
|
||||
"usage": "SecretValue"
|
||||
},
|
||||
{
|
||||
"challenge": "1CD506E8A8B07E60BFB3F6C285B24648CB18FF9BC0310972D58BC0B7FCF22F75",
|
||||
"commitment": "D7AC3DBF1F398A6E98B8D7420C8C09357BFF2E7611EBD9D9D647EAA538CDFB0C8C2AF3D0864575A3B98D0903DB44612D82C8FFEC854810B369CFAADCEE9986DB0B3574F9567528BCDF15CE9820F68AAF317F407229188A3FE0B9A02B6074C70D69EBBAC2053A7E8821DCB4A76157EC554056F1F86187B0EE873772E4A9A6CC5F4792741FF9749801318101947D7E31BB244949A6FA17CB6580EAAA057CCA466D45473DF34941A2D4A2BF2A1BABAFA7A42823DC833845A05D832C4385E3CC4E35BA28F8568F82D233C02F714EA3AC8E28F335FE2C54B6F1A39316327E2F4A4EA1CF63A388687380D427B21E438E53D6BC9910054CFAA3F98C31DE72955DBC6F93C05242A87BC7EE3D0EDDE7664A1B5E6A123D680DABD6178BFF06FE662F81732F5F849B474D62897A392C61A76EE4BF89BB46A97B8F079822D95F001D0DB6FD558C8932DE73269E2F9A82DF5E054CD9B0C6AF338AA2D0F9EC8C4CB3FE124CB4F0AF23D6C639396587F30E3F5A6A7B42423CD6633421BE26F3C7164F8B298200737DD995B246EDD0029C66AF81A8BCA314FB91F2EC679F24194FAD544C6BD3AA5EDED4204A0B72FBEEAA67CC1CF13D73CDFDC543611B2524BFDF10D0D9F8E9D14EE22299330674914BC42E5FA2E881F574656288854326C900EFF6F43E1311226FBA24C77F4BBF26189E8572AF365DDF56C28906907BDB9F91CB472F4F64262BED",
|
||||
"name": "Schnorr Proof",
|
||||
"public_key": "F87B71E1F7EDD7B91A1320FCCBBD44376E370BCE4B5177C237A944BA45A1151A9604C24AA912E990F21D0CB195E5B5A1DA88E7D2F5F630CD956478454073B5DC445798B22CEE8F54FB6C24676DD5F6AADDB6C005BF30E398396760CE7010366A90AF9EA5058AF7DCBC610BC600939695B947A37786841548A30AD252BD264671EF0BF9906875EE82862633297FEEC1E75C8FEF68E0EF3545A9C16C2F4DD62BB54C4EF84C396180D96B403D4400AFBABEE36925D379213260F00037EFD9CC650A9B4AADBA47869345351409E303E9D976A9692CCD757A12D02852644BA9501A909EFB7484AA43C96D0813110F1AEBDB192E5DD3E032D78F0E0A49D5556F1B33224D9F3D4A20D5C06A9DE2E02454C85691E3BFD88C8BFA132DD79794C9213EA5891FC2F28A4EC3FBCB7535AB91CCBF02D7E96B70BD187B44CCAC4CD0C2720B240EDB301DC32B750428B66B316C747A57CD062EFF6CFE7ED9058A818F36613EF627E57ABC43F3C9A9834B30BE2D07EA0217EEB3458222756ECA296A57B87FEA24B0C3EAEA5D6DED6863EE11D79F4F88AFD2EC2B385465CFB8E557609686A6246A781D952A487DFE2B4E93547AD9C05C08A496BD8171519C799B73E5EB008D2E2A0DA3DC51084E1BBB29F78836A4C0DE13714CBBFB0D448ADFD241841062507F0B803145710EFDF47368FD982141652B848881B20A2C9FE5DFEEA36CE6D15CC9D9E7",
|
||||
"response": "A1828C9C5EFC9D6D96DE0A708CAF5692D5BD9ED037D002C4749FA64E17B2A015",
|
||||
"usage": "SecretValue"
|
||||
}
|
||||
]
|
||||
},
|
||||
"auxiliary": {
|
||||
"owner_id": "guardian_1",
|
||||
"sequence_order": 1,
|
||||
"key": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsVI84AFUjQya+wlfTAOh\nPjzBO+LbOKEeqNWK5Jv84z1haFDZ4jm/2adCGlTx9xBUd1aRsUeYUBKA8uSR1BBB\ngRFPtlHwTbLjCZ0A2ZwcVDFMHlvTCATfXqSQanuHOY/RBMndc8T+YqBLsa2w9zT5\naqCx98GBzzuKW03/+t8RvjjjZ0D3DGI/buGhm9KgwKQmnSHzQNXcSvOCeib/hgtt\n3ovVmchhbxZz/mRdXCfVx2EoUJKiHQiPzBvGNKtsLPzRHgmI5314hfU3f+EUMh8A\n37XsBVmgG7ASUp4DS7DE4fLwI/L2JZKKyw7RbsbuI4YVO2+FtA/xkxmyU51b/xXl\nJrp5GYLiQtFMaesWeiCWeUr11rsY8dbcbFXF2S5w4ADUFN0A9llGnxmPfmlGRjYk\nhtWk7Mx3JwjlTzmEtac6yXm4X20gj6L3jhnzW0hFlawzDQsF3tGJo9nRKgp4ID2T\n7Ptf88X4RotmJHLSYr18EVbHsM7kti47pCJGAZJqWSm/w5TN1LW6nX7F9dhZ1xM/\nh0QabkO3ySvAE/gzHQebyGTvKaYPJXwhWD+lDcMNx8D/pCEOYWtYlbuHyX5+eIgM\nDkF5CUJ4HqNvHxRSlhQ9dT+uc9aalv4OyzLeE4ytuUicIoEQkpVw5/PjIMu69OGD\n61Oe8WITkbWQaijxsQLc99UCAwEAAQ==\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"status": "success",
|
||||
"message": null,
|
||||
"public_keys": {
|
||||
"election": {
|
||||
"owner_id": "guardian_2",
|
||||
"sequence_order": 2,
|
||||
"key": "95772093EEEFC25F34ADAE7669805CC1546393EAF6C3150B9B19150B0584F76F83E50EB80E935DC41A5B5F9B20644D8832F1DB0A7F1C3989516B94F819B33A3FA4A9E71B985DCA42AB7FAFFADBBF088CDECB1E2716CD5A7B075D40E13A8287175756245CAF8E72F72493426B85D42968C3AF4886A09DE6F987DE59AC7C78EFF48503576BCC90C62ABF41BB5DC739DF7232426A4033EAEB1011FA65B2ED6C4F4E1F4CD326BA1201D75618E163C0C63301A6765757FF982AC568FA3586000BF8A2CE461A45A4A1585A4CA2123182CBF61F2D538FB495794EDA3A1661B80D2D3F06F531730A8D3032E80F5671C4F403B05F2EADB2DB887468029D0C3AE6D5A816F00D433453E146EA3E01142132DE3AEBDFF3EC94CF344F61632FA3D9A0251647815F8176DD90BEF6A731C76AD9CBD940DD4D2AD274045C0F3E561E526562FC906459CEE93182BC62D8CD1CB38548C146E816D0293562CC5B1EC37363B2F8E1069DE915C32076A2D190B3959C32AC9BE5B0C4193FF3868E62DA7EF4828187A9EBCDC64AFD10A496BBD91EE5BB3B9223CB6C7C9062F5D18FD76DF450D0717AB5AAF0A3AA6E1C5EFFC7DA1203EB9BF2B920302FD8DFC393C0E444452602F1C888EE785AE0DF7FB25F82AA7E25B28F304966BE92CC6B81CE7C61F5086DA154A02A81082133EAE92DDC144E67FF58E701DFB2CAA70AAE12381C03B211FA91F7DA64F13F",
|
||||
"coefficient_commitments": [
|
||||
"95772093EEEFC25F34ADAE7669805CC1546393EAF6C3150B9B19150B0584F76F83E50EB80E935DC41A5B5F9B20644D8832F1DB0A7F1C3989516B94F819B33A3FA4A9E71B985DCA42AB7FAFFADBBF088CDECB1E2716CD5A7B075D40E13A8287175756245CAF8E72F72493426B85D42968C3AF4886A09DE6F987DE59AC7C78EFF48503576BCC90C62ABF41BB5DC739DF7232426A4033EAEB1011FA65B2ED6C4F4E1F4CD326BA1201D75618E163C0C63301A6765757FF982AC568FA3586000BF8A2CE461A45A4A1585A4CA2123182CBF61F2D538FB495794EDA3A1661B80D2D3F06F531730A8D3032E80F5671C4F403B05F2EADB2DB887468029D0C3AE6D5A816F00D433453E146EA3E01142132DE3AEBDFF3EC94CF344F61632FA3D9A0251647815F8176DD90BEF6A731C76AD9CBD940DD4D2AD274045C0F3E561E526562FC906459CEE93182BC62D8CD1CB38548C146E816D0293562CC5B1EC37363B2F8E1069DE915C32076A2D190B3959C32AC9BE5B0C4193FF3868E62DA7EF4828187A9EBCDC64AFD10A496BBD91EE5BB3B9223CB6C7C9062F5D18FD76DF450D0717AB5AAF0A3AA6E1C5EFFC7DA1203EB9BF2B920302FD8DFC393C0E444452602F1C888EE785AE0DF7FB25F82AA7E25B28F304966BE92CC6B81CE7C61F5086DA154A02A81082133EAE92DDC144E67FF58E701DFB2CAA70AAE12381C03B211FA91F7DA64F13F",
|
||||
"0C38BB43D919999789C91D26712EE6BD86ADDF39FF6693CF3B69CE4714ADF7913AE0EF4E8F6702C0B25A585335B3F54F2B2EB244209F7911FFEE5D331F475F85B920E08D2D2AE8DC2E9C5A99683A8222436C7B4B4E8F322CB3DA972971D15C8DF768BB76574FE6A04127470C526A67C378C57A15BA470B491BBF4A2C7BC9B610C7FF3ECAC7A2935083C067BE9F637FAD141078A12B1564F5609DF4ADA9513BC67FFCCE66D44A7A87752E371ED104BCE9AA9E80AF5DB237491099675C1DE6D094E9854699A9C8EE01D21724F8178F3D8F015CF9E288D7A8C56302B02FF71FB9792A4124CD1D5124E16937A8C08AFC3D35AB383918870E42FE3427CF3D2D4EAAF72DCC831529EB7F6E17A6995EEE18512ABA160B47A3B2BB3D6D132EFB518CBC504794C82BB3143E9D76640791CF8FF52810830ECCF9D8FC101015C694165A9B81A81CA30C75394AA2C2FD80ECDE40D9148C502C3E7EBC08DB7A41D7A8B31980F1055B58A2D7BDA18817BFA69A65FF5EAAE145DC8CD125BF5811E94D1A9F00CE1203ED961C4F7B2567546692B95D55D2D98D89C7C64949CA084BF9D57CDB400889D7D4A8F2550707F9F85F2F215AD13CAED3D92F89C27568E7B20635A36CFABC47DA134AFD56F954CD5E6E071A746EF006795EC6CB494F398F08B7D0039AC241C03857DB128CA033A5A82A4424F66A0B371A2997A0334168BEA3D28733F391F873"
|
||||
],
|
||||
"coefficient_proofs": [
|
||||
{
|
||||
"challenge": "5AA7F750F94ED4B2E24EBBE44651B2B4AB7B241B8E5EC6C1C5B92D61BC30AA21",
|
||||
"commitment": "1F56E5B4FFF78B031949223E18B862912CBD8AEF996B94B269A34177CA71F9944982E96708793EF78A91B098944D192645D3A73EEE884392C8658825C6D7825AD96B5A2AB5D5F5753CB30FE4C2BA6AD7B20AEF936AC12CE403BEB9D2424737BB93604E9B34DC3E45A6190DE2F128937522469C32FED56F43FAD189DBDF6B6406D52FFC85840C51E7128C5F1D4EAE73E28BDA1538EAD0D70A03F02F428B2180106D9E00C16862269C56EE59A00B6BE482E6751927DB84C960A2C0082558E631F80B7F45963E56355A11F34FD45D54B313FD3A708192290E4C8FA80453C36A4D8BBF3513F7494106E4660DCBC094B5977FE58BA00F013E1E35913443AC5D27035A96A2CDDCE3E5F15EEEBC82E48CD85CE277D8FC7BAEFE4AD4E67570C947CDC5441B5A79E1C51968BD2C274C817A93553CABB99735422BF9912D4C9EC5F80A1B9832975CF015A53833437C926C60496FA92538A0F7914E74121E818C1FA382C31364CB6E498E1EFA0BF4249D6B116119C96FB6EFCD7AD9DDADC4A7A57C2B0BB04D2397A4310DE2B8F13C0A522E64C032505E43AA3AB5B74C1C4AB5FC1DA882CD8CCB1C725F042FE097327E8B259D3B71C8AB134E1D66CB618586DFE514CC608AB0B37BCCA78ECEC919AF0B0B0BA87381FAF03A3A0CFAEF6C2B038343EA2DA5C9BB457B58ECB9B37EC5B1A17CD529C4CD120D718CA4BE6F8438DB419A09688410B2",
|
||||
"name": "Schnorr Proof",
|
||||
"public_key": "95772093EEEFC25F34ADAE7669805CC1546393EAF6C3150B9B19150B0584F76F83E50EB80E935DC41A5B5F9B20644D8832F1DB0A7F1C3989516B94F819B33A3FA4A9E71B985DCA42AB7FAFFADBBF088CDECB1E2716CD5A7B075D40E13A8287175756245CAF8E72F72493426B85D42968C3AF4886A09DE6F987DE59AC7C78EFF48503576BCC90C62ABF41BB5DC739DF7232426A4033EAEB1011FA65B2ED6C4F4E1F4CD326BA1201D75618E163C0C63301A6765757FF982AC568FA3586000BF8A2CE461A45A4A1585A4CA2123182CBF61F2D538FB495794EDA3A1661B80D2D3F06F531730A8D3032E80F5671C4F403B05F2EADB2DB887468029D0C3AE6D5A816F00D433453E146EA3E01142132DE3AEBDFF3EC94CF344F61632FA3D9A0251647815F8176DD90BEF6A731C76AD9CBD940DD4D2AD274045C0F3E561E526562FC906459CEE93182BC62D8CD1CB38548C146E816D0293562CC5B1EC37363B2F8E1069DE915C32076A2D190B3959C32AC9BE5B0C4193FF3868E62DA7EF4828187A9EBCDC64AFD10A496BBD91EE5BB3B9223CB6C7C9062F5D18FD76DF450D0717AB5AAF0A3AA6E1C5EFFC7DA1203EB9BF2B920302FD8DFC393C0E444452602F1C888EE785AE0DF7FB25F82AA7E25B28F304966BE92CC6B81CE7C61F5086DA154A02A81082133EAE92DDC144E67FF58E701DFB2CAA70AAE12381C03B211FA91F7DA64F13F",
|
||||
"response": "1245641FBA8C41AB7C9AC682950A5289D3A4FC57E445F4830C0F5BF8C0E91CED",
|
||||
"usage": "SecretValue"
|
||||
},
|
||||
{
|
||||
"challenge": "6B42F99DC8E40EE3AA4C663F57923DA705711E45E48C72A6B773B5697365A1BD",
|
||||
"commitment": "51D8A50F872475679481A207BFD2042572D4BCDBBCE97F9288ED61485EE944BD7163043BC5EB691C811A0B4571446E5E838491D2ADB37D44BBF2A0252F303C08B9570F379E8F5ACCCD97CD762A9FC9D401E07134F4E5337600741524C49D59629C6C389D0B6E8EE0E1DECD41E86B4DAE7A7FD1E06A9C2CA1DC8086F1C7A4E68A367B5FFD3EEB46046CCC6767EED648C6D678664BF15DAD95E3333D266864F83457A2306726CB8CE7C10CD4E2EB6D73D0938BB8BA049940C4CC234EB082858DF1007B5FBE45ACF1A94261114D309E7B9A59FDD258D37A1A901E55CBB342105B119690A105AB856DC1B2BD53E4C1382CF9FA5BED7C2AABBC7FA0EC6BD95FB5AB7A1304F60135B0A5F0FE836351DFFB8EC68186001ED27A519E388F9D895235AE391600EA6C3C7F1CF4B99C43F2ACB2589CC04D29CAEA98AAA4A9CAC523BED78FBC69828156D8EEF9AD633359BD86AF4F0541576629558E4C43616C04D86DDA47A8681B21278057BA94F3A25039C54018927E353F93B2B9717F7564DE4328ECF48CD9CDB502DFC78FD82FBC859AEB3C607F9F82B5CEC16CB6126217975626A6B91104009D4A8FDD8AE80C1E17DFDCA8B6B59F07C28EA8C8B40647E59E3E896DDD8A7AA636DE7C03AA7C94002DC687FD5B4FF7B92DDD81904C86A533EE2FE0CFD528AF468DEA88D72A863B13384771729B3B9E26538E98D0D5431FA293E37A57569A",
|
||||
"name": "Schnorr Proof",
|
||||
"public_key": "0C38BB43D919999789C91D26712EE6BD86ADDF39FF6693CF3B69CE4714ADF7913AE0EF4E8F6702C0B25A585335B3F54F2B2EB244209F7911FFEE5D331F475F85B920E08D2D2AE8DC2E9C5A99683A8222436C7B4B4E8F322CB3DA972971D15C8DF768BB76574FE6A04127470C526A67C378C57A15BA470B491BBF4A2C7BC9B610C7FF3ECAC7A2935083C067BE9F637FAD141078A12B1564F5609DF4ADA9513BC67FFCCE66D44A7A87752E371ED104BCE9AA9E80AF5DB237491099675C1DE6D094E9854699A9C8EE01D21724F8178F3D8F015CF9E288D7A8C56302B02FF71FB9792A4124CD1D5124E16937A8C08AFC3D35AB383918870E42FE3427CF3D2D4EAAF72DCC831529EB7F6E17A6995EEE18512ABA160B47A3B2BB3D6D132EFB518CBC504794C82BB3143E9D76640791CF8FF52810830ECCF9D8FC101015C694165A9B81A81CA30C75394AA2C2FD80ECDE40D9148C502C3E7EBC08DB7A41D7A8B31980F1055B58A2D7BDA18817BFA69A65FF5EAAE145DC8CD125BF5811E94D1A9F00CE1203ED961C4F7B2567546692B95D55D2D98D89C7C64949CA084BF9D57CDB400889D7D4A8F2550707F9F85F2F215AD13CAED3D92F89C27568E7B20635A36CFABC47DA134AFD56F954CD5E6E071A746EF006795EC6CB494F398F08B7D0039AC241C03857DB128CA033A5A82A4424F66A0B371A2997A0334168BEA3D28733F391F873",
|
||||
"response": "7F9F037C773259DF340CE3B1116A6074103D12AD06087E59757911A716375DF7",
|
||||
"usage": "SecretValue"
|
||||
}
|
||||
]
|
||||
},
|
||||
"auxiliary": {
|
||||
"owner_id": "guardian_2",
|
||||
"sequence_order": 2,
|
||||
"key": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApYyLSfBMCdZkXx8477by\nMD07wyfMNBS+ZM1fJhGiPYaEFIHQae7gDpLTa2+RpW/KWGYrC7ZP5TWq2HZ6bp+3\nUr9blfj58MQvIobnWZDd+bVygfPXaOwxoxxaGQnhEJ2VRd9H+ZshblGGBaB2Qbes\n5g8geBoE1a7iKT/1OiFlDn8TCZVbZq0Ncwz49AlDg4hk/c8wGjrbx5z7uT5vV21O\n9C8QujGU2ZS2QM3AjPs7itxsN3XhabQMjBawRXuSpZ503DqpLA0F/9EsyJb6HOL1\nnPTXnG4bcovy4q6BJeuuvRJRBYwweuZ1ApDQP8nEq0YKRWEphscTtxAJqaIP+5LB\ngC+hznSLIhR+lRPqpn+MY33hF8SG16syr4d25zk/4vHpPfnOgNGLHdYoJ3wL6HfR\ns1PoubU8x38GCib/WXWOshMLqGRkWdElLnmSt/O5FMdUfBygxa1HJ+HAWfiUdBCP\naTPZHa8fEEZbBeH8zalPm/knNXAtCWh4kI496pJWsfUblOr2P0db4hzO3Nt09hPG\nZ1hHOLCD1ftQSURIcqNjWraxV+IEU9v/NHc/b5ATetJV9vfKrawtzBU742tzFyNA\n+mrGArLrq280tZiYQnsmMHYJ9rns08NnXxQ8zVYSDcw7Nd5O/fAFG3V+nSgF/oDr\nZdTiEFX67mC8DSRNVpy5V4MCAwEAAQ==\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
"status": "success",
|
||||
"message": null,
|
||||
"public_keys": {
|
||||
"election": {
|
||||
"owner_id": "guardian_3",
|
||||
"sequence_order": 3,
|
||||
"key": "B72BE7179D27721490A35A7C660DC6AAE92FB34030B3579A982E77FC7244B7512D70F8531978F76C16C20F31D566328460D1FB7C3994FC9E08F8D1D150155E4FDF8BD89D8F7A33129A37D4EA1EBD48146EC20E6A051EB066DB4EA8A4B16596B37FA35CF64B8272B14D33DCC726205B2AFA0DE5FAB4C757CFEF08B7662043646C311B6A4A469A17A7E9FBF95D6C4DD6EA62A6899208DC5A5A71F2357B3A02BBB91B62ABAFC10959D6337118C8C144636EC76ECD07D45BCED0D640A210D6E941DE67077473A0D15E4178F7D0592E2D015D9503A0609EC42F566BA4982860B27C96B0E9E4163FAF75F9AE2F1396BF52717D5FD363F86421BF1D93F0ECDFC7C742FB96F323E2B357267492EAEA6886991AE927B7CAA9269F70E63BE03F46B42E22EAB81F5590DC996E06A286F1E9EEB32C2AF2DDA233CD4DB2FF35B2D6EF3831CF7CD0CF4FA4AA8F233DB3B20FB2969A7D4F00C96E5F2C3590297BEAB380CACBC52DBA4410680C627ED9CF7D8922CEF42410A1FEF356C07641EEC7C5BA5A228AE5BBB1A59C30DE2631C27C33A63DB732B0ADC39CFFC67858AA3D1889FDD56F968649CC69B21C53D90B03F9B7D326341E1442E0BC9CCA2297653FA7DA2074888A70E13A6C46479DE2EA80653F5446D2853E452FDAE2FEC366AB20A75A36B6C23279CAF995938359FB4E82831B5AE1CCC15B59BF44BBBCF9CEA9006AD126E8E5DBE5A8",
|
||||
"coefficient_commitments": [
|
||||
"B72BE7179D27721490A35A7C660DC6AAE92FB34030B3579A982E77FC7244B7512D70F8531978F76C16C20F31D566328460D1FB7C3994FC9E08F8D1D150155E4FDF8BD89D8F7A33129A37D4EA1EBD48146EC20E6A051EB066DB4EA8A4B16596B37FA35CF64B8272B14D33DCC726205B2AFA0DE5FAB4C757CFEF08B7662043646C311B6A4A469A17A7E9FBF95D6C4DD6EA62A6899208DC5A5A71F2357B3A02BBB91B62ABAFC10959D6337118C8C144636EC76ECD07D45BCED0D640A210D6E941DE67077473A0D15E4178F7D0592E2D015D9503A0609EC42F566BA4982860B27C96B0E9E4163FAF75F9AE2F1396BF52717D5FD363F86421BF1D93F0ECDFC7C742FB96F323E2B357267492EAEA6886991AE927B7CAA9269F70E63BE03F46B42E22EAB81F5590DC996E06A286F1E9EEB32C2AF2DDA233CD4DB2FF35B2D6EF3831CF7CD0CF4FA4AA8F233DB3B20FB2969A7D4F00C96E5F2C3590297BEAB380CACBC52DBA4410680C627ED9CF7D8922CEF42410A1FEF356C07641EEC7C5BA5A228AE5BBB1A59C30DE2631C27C33A63DB732B0ADC39CFFC67858AA3D1889FDD56F968649CC69B21C53D90B03F9B7D326341E1442E0BC9CCA2297653FA7DA2074888A70E13A6C46479DE2EA80653F5446D2853E452FDAE2FEC366AB20A75A36B6C23279CAF995938359FB4E82831B5AE1CCC15B59BF44BBBCF9CEA9006AD126E8E5DBE5A8",
|
||||
"4E9E538E4C0A2687165B9C57CADF5C4B775B780C6E8275D8802171C34844DD9763B92D0DE633D29C0B524B57801985A8EAB64DE495CCDBC084D311A23F423D5F82B41C57B730E2F0F3080FC5C9274C6B40235515C5C5E9211DDFF01857A233B303EA2190C592D9AE43129F08FC052B874FC03D37503A4B0C301DE25B1AA3B66342149343A7557F0CF81356AB262DC100CB8E3C07AB3C9FA4793A059477DFD993AD0FC33A64EE5EA730E19382AECD6BA2CD6F6FA541D526E8CC69777DEA70FD0DFDBBB1052BB49F2D3A9AD04A5FB9C87ACD6CA004A73A7F0FE9434998E7E8EC7AF083A52624C945BEF7FA7343ED264D6982280A3976247A7B202EC98800E13D54E0D00A7AA989F9AB0097D888D040A9949C64C5E9070D3D148CC8FF7B77D8E76AC5D1E7D2D3933E0232A837EABD80E386968E3966ABDF4E431A61259E70FA14FE6A43FD9C63C3A09AB7CF2EF6B8F9E5B651A06BCDDCDF41BEB5EF4B4CB38046EB8E58BC2C2CAE2F5201FF5EFFFEA17A39D9264116D1FD2C5A83FF4F086A938F53900B517E17A751A0A045928AAB63A784F39005054FC5C53D97F31C22799B4115FED2CDFB1F7149478C533A5EF2069BE22FA2013EDCA915810EA2B75CD9664DCD982A2E891F42C785915A308E761321D52544CDF62F7FFDE600C2DE4E0836853498331FFA1A65D6EC07F7C5F66E73F2C4F74AEAFBDD1E8B0B9725DD1E9A552E94"
|
||||
],
|
||||
"coefficient_proofs": [
|
||||
{
|
||||
"challenge": "959FA8B65AD061BBB409D034F705DF753C11831C72AA004B0D6E69C056FBFF7D",
|
||||
"commitment": "04C2FFA57E3DC683002C78558DD1ED18E7CB5D3BDAA31A91D1A8487CE27DBA6B96C7A300E88DF5584A42E5D2021D6028A2FFECF5B98F9C1FE88137FA8D23755ADCA82BEDFAB49742B973E354827169EEFA78BB073DD0BA970F82E1D89DEDF6ACC849224EFB898D44FBD2F1DADCAEB779ED08561C0BB5DFB03B6D73D64651599F4C845686EC9002EBC39E84802F39DFF6E58D085F2468B1C62C684FF44EC7FCB361EB7979FAFCD76187229C59AFBB721E2C48FC4B88BFD14E43F864A5C31DFDADE63DE70F355972200B7027C09FB9A7B208E1E8E5C3D3BABE23DB53A990E702F3B876DF7C854EA582522BE70486B0352617DEE3EAE1E7586F4F8E743726B1FE29C1818B253043D295D956D20DC4EF1B4CC31B82627D3C7A28CEBC74390DE98C221B346481CBE2D3C12BF63164008283898C1BC2F66409573870B1CA80AB309261B3FF0F589F336C7BD8659AC7D0205043A4A5C3BEF2091623703368B0D1605C11C64AF322E758128F92E76C9C2006DA6FA79D58703FC1F71E3D77C792AE7F1654091DDF901DC405049753FBAD063CF09F05FFAA2B12911705588CCA512819DD998A65A8DDF4E4B2300386F5124A7A7C318A954A4C31736DF03506BBAEE1F860B80B39741B154FDAC229EC73CE0ABADA76062BA3B4A0B7EBDA9DA5D88F85D3561D00D6FFDFA703DFE4BE85487FE316F72A2C1FE21C9DEC52B1446EA3FBE767BC2A",
|
||||
"name": "Schnorr Proof",
|
||||
"public_key": "B72BE7179D27721490A35A7C660DC6AAE92FB34030B3579A982E77FC7244B7512D70F8531978F76C16C20F31D566328460D1FB7C3994FC9E08F8D1D150155E4FDF8BD89D8F7A33129A37D4EA1EBD48146EC20E6A051EB066DB4EA8A4B16596B37FA35CF64B8272B14D33DCC726205B2AFA0DE5FAB4C757CFEF08B7662043646C311B6A4A469A17A7E9FBF95D6C4DD6EA62A6899208DC5A5A71F2357B3A02BBB91B62ABAFC10959D6337118C8C144636EC76ECD07D45BCED0D640A210D6E941DE67077473A0D15E4178F7D0592E2D015D9503A0609EC42F566BA4982860B27C96B0E9E4163FAF75F9AE2F1396BF52717D5FD363F86421BF1D93F0ECDFC7C742FB96F323E2B357267492EAEA6886991AE927B7CAA9269F70E63BE03F46B42E22EAB81F5590DC996E06A286F1E9EEB32C2AF2DDA233CD4DB2FF35B2D6EF3831CF7CD0CF4FA4AA8F233DB3B20FB2969A7D4F00C96E5F2C3590297BEAB380CACBC52DBA4410680C627ED9CF7D8922CEF42410A1FEF356C07641EEC7C5BA5A228AE5BBB1A59C30DE2631C27C33A63DB732B0ADC39CFFC67858AA3D1889FDD56F968649CC69B21C53D90B03F9B7D326341E1442E0BC9CCA2297653FA7DA2074888A70E13A6C46479DE2EA80653F5446D2853E452FDAE2FEC366AB20A75A36B6C23279CAF995938359FB4E82831B5AE1CCC15B59BF44BBBCF9CEA9006AD126E8E5DBE5A8",
|
||||
"response": "6B3AF4570CCB04B243BE67F1644C3211B8C66E34F65CD6C02370D3B9AC5F4370",
|
||||
"usage": "SecretValue"
|
||||
},
|
||||
{
|
||||
"challenge": "36EAEEED22EDDF59160CCBBDA43AA26F9CBF732EE35B4091A3BE5309C3E96898",
|
||||
"commitment": "C33855856F08AFB1790B2E3D34E29C6DF8D77AE195F13F01C27F2BF8391E32D6EB5F255D9152A3E9177509C90640524420425684D36E9E613D171D98DBD499D8CD345C895B5EC38955A27D1109671E91AE519504EC6C9AF0C5CB845D0D4802D6E6826745C9F556AA5204D210434AB89FEF908BD0A49025D435B8B875C62A3B73659BC698F68DA376148A3ADB73254A8841649538A3F77954B71C25BD7145A4631D331C236CA306BCD642BBEFC921091A9803C6F29F5B7723BC9C04EC621C91D2CA0CB3167F4F8293068494EC2C121DA7FE2143DE01CBCA15AE58FDA2BF470C476EABC0746091C40F15E3F14B3731B10511D2F270E0DE50EED0B3A889BEA924837BB47CE3661CEF3FE5A010A546BF99A8928E6A87C063F8419E6169B9B7E65ACBFD529FB9E20ADA0D8F3C03FEF54F3A399065994B812E2DC2287C015A592B57C139D0036D0555479F9A8A88232D5CB42E800C829E5B289DA1ABFDD4B2620EBEAFAA925787CF7D2B4B620539905E1FBA6805A251B7D7364AC39B54FF9AEBE53878823C7A6C8B83C43A5269287B1B21FD4E9EF2956BAA782371320566B98A7E5804E4FA398368DBF62CF46E64470D8BC23EA0F9605D12C5A9D70FD0BBDD18455C547B411CEA60DDFF53AE9F22E3FC0D972D5B5B5904F6EB351BF55EC46C872132F65C9FF9E08BCEE6F36399D93282069FF0CEF73C9E02CACCACA52ED56C52B1D003",
|
||||
"name": "Schnorr Proof",
|
||||
"public_key": "4E9E538E4C0A2687165B9C57CADF5C4B775B780C6E8275D8802171C34844DD9763B92D0DE633D29C0B524B57801985A8EAB64DE495CCDBC084D311A23F423D5F82B41C57B730E2F0F3080FC5C9274C6B40235515C5C5E9211DDFF01857A233B303EA2190C592D9AE43129F08FC052B874FC03D37503A4B0C301DE25B1AA3B66342149343A7557F0CF81356AB262DC100CB8E3C07AB3C9FA4793A059477DFD993AD0FC33A64EE5EA730E19382AECD6BA2CD6F6FA541D526E8CC69777DEA70FD0DFDBBB1052BB49F2D3A9AD04A5FB9C87ACD6CA004A73A7F0FE9434998E7E8EC7AF083A52624C945BEF7FA7343ED264D6982280A3976247A7B202EC98800E13D54E0D00A7AA989F9AB0097D888D040A9949C64C5E9070D3D148CC8FF7B77D8E76AC5D1E7D2D3933E0232A837EABD80E386968E3966ABDF4E431A61259E70FA14FE6A43FD9C63C3A09AB7CF2EF6B8F9E5B651A06BCDDCDF41BEB5EF4B4CB38046EB8E58BC2C2CAE2F5201FF5EFFFEA17A39D9264116D1FD2C5A83FF4F086A938F53900B517E17A751A0A045928AAB63A784F39005054FC5C53D97F31C22799B4115FED2CDFB1F7149478C533A5EF2069BE22FA2013EDCA915810EA2B75CD9664DCD982A2E891F42C785915A308E761321D52544CDF62F7FFDE600C2DE4E0836853498331FFA1A65D6EC07F7C5F66E73F2C4F74AEAFBDD1E8B0B9725DD1E9A552E94",
|
||||
"response": "A16793CFDB6A03E32399642D2E431FA382998D297F7914B188AA0BD0B0509EAC",
|
||||
"usage": "SecretValue"
|
||||
}
|
||||
]
|
||||
},
|
||||
"auxiliary": {
|
||||
"owner_id": "guardian_3",
|
||||
"sequence_order": 3,
|
||||
"key": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyD6tXHsk/dfJTBx218Qo\n55l5wzeLgtzKChpkpw82c4NUQty1FU/HAqQt+XZvE2EDZMFo0PBR3pWXjEvlLYYm\n5SPCtBjj1pAbwlEJ3WZ0cnbcyAm1yvOo3IDZBanOx7gVhyvnY53GDEg/c825gVKM\nnllTlF90C0k187jfVQrtsWYtCjZBGHy0er8B3IQHO6uZrZcZreNEJu25AVo0jUKl\nDHPY0WoxePJeOvDn6ztx6n4BZGHZHWQ+fXgUlkc7PiPvZNuwLJN3HwSyD5tWtsXR\ntaGnk75gAtbcmNxWQtrgwt1TIkdu0vFOKwWFffEoLLBsfnaA/qQpOqATU0iHpKsh\nFHxMhvEYeytG0tAbpflIPFjsV8a36CArqFDAiI3g0RqPPdcq/YgKHvqPSJ9NL9UI\nmHh9sldFD3LtrjuRTeyAWNXAVbknhrgiLDgWSAF3dnL0iNdPjzLkT/I0B/91vBsz\np1UzODQlR/tdtPYncHyeIsllaOwdeAX0NymeHwUN1faDHwLOHcIKxoZdC+vseBy7\nGz0AcKKzWXW/fxzn0bnygQPVDeoenm/ku9g8zWezzCUjj/pQX59llBg0/t2RuTYh\ns17zp0ZoK3UFJtqnvZpgrErVjsvFe08zn5MNmeCgV9r8V1G3FqCMD6CyJ3d9Kr+T\n2FOb3u90fy5VpXmWNafE4vUCAwEAAQ==\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
mediator > Announce Guardian (repeat for all)
|
||||
|
||||
it's the public keys above
|
||||
|
||||
guardian > Create Guardian Backup (repeat for all) - make note of them
|
||||
|
||||
Requests:
|
||||
|
||||
```
|
||||
|
||||
missing 1 and 2, i forgot to copy them
|
||||
|
||||
{
|
||||
"guardian_id": "guardian_3",
|
||||
"quorum": 2,
|
||||
"public_keys": [
|
||||
{
|
||||
"election": {
|
||||
"owner_id": "guardian_1",
|
||||
"sequence_order": 1,
|
||||
"key": "6A84EAC91199C7A5E65AA0646F72F2CC01623E6F651C0DA6B5F81E89BA050CA4DFF245D2FE0BF020C096939A5D396C7836D6F95ECE848DAAE5DD7402A66B302C627C7C53469533E1AD5916DE51EE6C91C74C67FE9BEE61E3328FC55D835870E265EA5A524C7C871EF5DFC20550431793D9AA4F68A0A23642785CDA82F69A6663995B1313C7315AE9AAFB5319E93DEF1589C7BFBE56B6BE04E70573C4F94EF710012C187162C1D8DF3A407F4FD9C4559F328DE822F031800D5FAA3CBD4E8BD88083E01C8A8D0E5113963964832B1FB522B0CAB452F53E5E48D8A8725989BD77E1AEA4BED1100F5704C87221DD9057ED866FA8BF3F06D2D048B65785DB753080476E62D251D70BC5975ABC16C5D624779025C6EA9291C89A9823199ECCD2827C3187C23E2935B3E1A16BE00711C14278748B52F276B6634F7B9DD5A47FBDA5D71F7066EAD8440FCC0A6A7374E617C22C1EF100ADDE7B9525AEA317C806409D5B0A9E2C65F70DE80A260519C4E9914802EA1F65AF2C55FC5F526C7FA15B0CDA4BC5EC6277036775B04E9A2539EC150D501E3DE0969E654445E44E8FD1AB50839C594ADCCFC5E3BC73EE7740EDF83ECE8C873D73A48840857513CC848FCA3127EBDF96F4F68F01D1D706912BE5A1D4E36B90B137FD1861CEE2D986A3AA96C0B184D2DED74A5705889DCCFDF7143448C2C3290A6BF90496021C18FEFD7D07A0EE84AE",
|
||||
"coefficient_commitments": [
|
||||
"6A84EAC91199C7A5E65AA0646F72F2CC01623E6F651C0DA6B5F81E89BA050CA4DFF245D2FE0BF020C096939A5D396C7836D6F95ECE848DAAE5DD7402A66B302C627C7C53469533E1AD5916DE51EE6C91C74C67FE9BEE61E3328FC55D835870E265EA5A524C7C871EF5DFC20550431793D9AA4F68A0A23642785CDA82F69A6663995B1313C7315AE9AAFB5319E93DEF1589C7BFBE56B6BE04E70573C4F94EF710012C187162C1D8DF3A407F4FD9C4559F328DE822F031800D5FAA3CBD4E8BD88083E01C8A8D0E5113963964832B1FB522B0CAB452F53E5E48D8A8725989BD77E1AEA4BED1100F5704C87221DD9057ED866FA8BF3F06D2D048B65785DB753080476E62D251D70BC5975ABC16C5D624779025C6EA9291C89A9823199ECCD2827C3187C23E2935B3E1A16BE00711C14278748B52F276B6634F7B9DD5A47FBDA5D71F7066EAD8440FCC0A6A7374E617C22C1EF100ADDE7B9525AEA317C806409D5B0A9E2C65F70DE80A260519C4E9914802EA1F65AF2C55FC5F526C7FA15B0CDA4BC5EC6277036775B04E9A2539EC150D501E3DE0969E654445E44E8FD1AB50839C594ADCCFC5E3BC73EE7740EDF83ECE8C873D73A48840857513CC848FCA3127EBDF96F4F68F01D1D706912BE5A1D4E36B90B137FD1861CEE2D986A3AA96C0B184D2DED74A5705889DCCFDF7143448C2C3290A6BF90496021C18FEFD7D07A0EE84AE",
|
||||
"F87B71E1F7EDD7B91A1320FCCBBD44376E370BCE4B5177C237A944BA45A1151A9604C24AA912E990F21D0CB195E5B5A1DA88E7D2F5F630CD956478454073B5DC445798B22CEE8F54FB6C24676DD5F6AADDB6C005BF30E398396760CE7010366A90AF9EA5058AF7DCBC610BC600939695B947A37786841548A30AD252BD264671EF0BF9906875EE82862633297FEEC1E75C8FEF68E0EF3545A9C16C2F4DD62BB54C4EF84C396180D96B403D4400AFBABEE36925D379213260F00037EFD9CC650A9B4AADBA47869345351409E303E9D976A9692CCD757A12D02852644BA9501A909EFB7484AA43C96D0813110F1AEBDB192E5DD3E032D78F0E0A49D5556F1B33224D9F3D4A20D5C06A9DE2E02454C85691E3BFD88C8BFA132DD79794C9213EA5891FC2F28A4EC3FBCB7535AB91CCBF02D7E96B70BD187B44CCAC4CD0C2720B240EDB301DC32B750428B66B316C747A57CD062EFF6CFE7ED9058A818F36613EF627E57ABC43F3C9A9834B30BE2D07EA0217EEB3458222756ECA296A57B87FEA24B0C3EAEA5D6DED6863EE11D79F4F88AFD2EC2B385465CFB8E557609686A6246A781D952A487DFE2B4E93547AD9C05C08A496BD8171519C799B73E5EB008D2E2A0DA3DC51084E1BBB29F78836A4C0DE13714CBBFB0D448ADFD241841062507F0B803145710EFDF47368FD982141652B848881B20A2C9FE5DFEEA36CE6D15CC9D9E7"
|
||||
],
|
||||
"coefficient_proofs": [
|
||||
{
|
||||
"challenge": "AEEBE8ED2110C5B0D63137A104D52EEAC9131C58A0BB18B80E9FAF0988C51281",
|
||||
"commitment": "A1B78A1DD6595AED57B8A98C5A1B1A12256277BC03ABB06CCBD0A57370309EC58E67995C64131A71214C5ABB6603DC49FB167F1291222106B1F42D80BFCD9C24372E821027520DCABB14B84DE5B605991354BF02091E8B708A3C1A43A1E2CAE538249C4CBAC406A352B1D01020451EA53D0AF5FD12AF7514302A6DF7373D1B9201EF60F41FE6C786DC3D2EDC1548A4E10A26C9B01A47E67DBF3A17CD29BCAAD4AF4315D5076BDBD0B036FA1247854BD3FCE562047194CC0C48A46EF827B86A654BDB6E6DC6A045F6A6A96492B7774E67E27572046D26E58B308AEF1351704DB1D0D87D71EF39A84C1F864430B835E94FACB5600BCBE3C47AB4C96A4857137348D8C45D1F536B86848348FBA09D6281CDC39B1FF608E84B08FD53E0F0C5B6ADDE67BC145B1DA61F79356774DAB76781AC9197498B924FD24E9F48C67E699007CFF675F1E5D7EEB0F15AA147C2ECB0A8F79340141D6C1FFBAFB7F1250E83443E4712440AEB1B150CAEEFD29789799FA3603DBA9F3E297FED0B8934C7EAA3C90D567C35D1DCBABA6E7158F76E9FCDCC7393EED39456C527235D442B7D4DA5AE418E02C572FFA510B22AD83EB4DC4A333B52E47E56E43FD0345187CA7F45DDCB6A6F6E9B3D7842B097752F08261743958866ADB008CAFD5BE9A0E91CB5C7F7B8BCCF7FF6C85AF02C1AA39DF7022F80A1C0AF56C55A86A6D865C4298006553EF11FC5",
|
||||
"name": "Schnorr Proof",
|
||||
"public_key": "6A84EAC91199C7A5E65AA0646F72F2CC01623E6F651C0DA6B5F81E89BA050CA4DFF245D2FE0BF020C096939A5D396C7836D6F95ECE848DAAE5DD7402A66B302C627C7C53469533E1AD5916DE51EE6C91C74C67FE9BEE61E3328FC55D835870E265EA5A524C7C871EF5DFC20550431793D9AA4F68A0A23642785CDA82F69A6663995B1313C7315AE9AAFB5319E93DEF1589C7BFBE56B6BE04E70573C4F94EF710012C187162C1D8DF3A407F4FD9C4559F328DE822F031800D5FAA3CBD4E8BD88083E01C8A8D0E5113963964832B1FB522B0CAB452F53E5E48D8A8725989BD77E1AEA4BED1100F5704C87221DD9057ED866FA8BF3F06D2D048B65785DB753080476E62D251D70BC5975ABC16C5D624779025C6EA9291C89A9823199ECCD2827C3187C23E2935B3E1A16BE00711C14278748B52F276B6634F7B9DD5A47FBDA5D71F7066EAD8440FCC0A6A7374E617C22C1EF100ADDE7B9525AEA317C806409D5B0A9E2C65F70DE80A260519C4E9914802EA1F65AF2C55FC5F526C7FA15B0CDA4BC5EC6277036775B04E9A2539EC150D501E3DE0969E654445E44E8FD1AB50839C594ADCCFC5E3BC73EE7740EDF83ECE8C873D73A48840857513CC848FCA3127EBDF96F4F68F01D1D706912BE5A1D4E36B90B137FD1861CEE2D986A3AA96C0B184D2DED74A5705889DCCFDF7143448C2C3290A6BF90496021C18FEFD7D07A0EE84AE",
|
||||
"response": "FA8357662E152DFDA8FAC336748B56F6FB42AE8E81F8FD74A957E6BFFC2C3D4C",
|
||||
"usage": "SecretValue"
|
||||
},
|
||||
{
|
||||
"challenge": "1CD506E8A8B07E60BFB3F6C285B24648CB18FF9BC0310972D58BC0B7FCF22F75",
|
||||
"commitment": "D7AC3DBF1F398A6E98B8D7420C8C09357BFF2E7611EBD9D9D647EAA538CDFB0C8C2AF3D0864575A3B98D0903DB44612D82C8FFEC854810B369CFAADCEE9986DB0B3574F9567528BCDF15CE9820F68AAF317F407229188A3FE0B9A02B6074C70D69EBBAC2053A7E8821DCB4A76157EC554056F1F86187B0EE873772E4A9A6CC5F4792741FF9749801318101947D7E31BB244949A6FA17CB6580EAAA057CCA466D45473DF34941A2D4A2BF2A1BABAFA7A42823DC833845A05D832C4385E3CC4E35BA28F8568F82D233C02F714EA3AC8E28F335FE2C54B6F1A39316327E2F4A4EA1CF63A388687380D427B21E438E53D6BC9910054CFAA3F98C31DE72955DBC6F93C05242A87BC7EE3D0EDDE7664A1B5E6A123D680DABD6178BFF06FE662F81732F5F849B474D62897A392C61A76EE4BF89BB46A97B8F079822D95F001D0DB6FD558C8932DE73269E2F9A82DF5E054CD9B0C6AF338AA2D0F9EC8C4CB3FE124CB4F0AF23D6C639396587F30E3F5A6A7B42423CD6633421BE26F3C7164F8B298200737DD995B246EDD0029C66AF81A8BCA314FB91F2EC679F24194FAD544C6BD3AA5EDED4204A0B72FBEEAA67CC1CF13D73CDFDC543611B2524BFDF10D0D9F8E9D14EE22299330674914BC42E5FA2E881F574656288854326C900EFF6F43E1311226FBA24C77F4BBF26189E8572AF365DDF56C28906907BDB9F91CB472F4F64262BED",
|
||||
"name": "Schnorr Proof",
|
||||
"public_key": "F87B71E1F7EDD7B91A1320FCCBBD44376E370BCE4B5177C237A944BA45A1151A9604C24AA912E990F21D0CB195E5B5A1DA88E7D2F5F630CD956478454073B5DC445798B22CEE8F54FB6C24676DD5F6AADDB6C005BF30E398396760CE7010366A90AF9EA5058AF7DCBC610BC600939695B947A37786841548A30AD252BD264671EF0BF9906875EE82862633297FEEC1E75C8FEF68E0EF3545A9C16C2F4DD62BB54C4EF84C396180D96B403D4400AFBABEE36925D379213260F00037EFD9CC650A9B4AADBA47869345351409E303E9D976A9692CCD757A12D02852644BA9501A909EFB7484AA43C96D0813110F1AEBDB192E5DD3E032D78F0E0A49D5556F1B33224D9F3D4A20D5C06A9DE2E02454C85691E3BFD88C8BFA132DD79794C9213EA5891FC2F28A4EC3FBCB7535AB91CCBF02D7E96B70BD187B44CCAC4CD0C2720B240EDB301DC32B750428B66B316C747A57CD062EFF6CFE7ED9058A818F36613EF627E57ABC43F3C9A9834B30BE2D07EA0217EEB3458222756ECA296A57B87FEA24B0C3EAEA5D6DED6863EE11D79F4F88AFD2EC2B385465CFB8E557609686A6246A781D952A487DFE2B4E93547AD9C05C08A496BD8171519C799B73E5EB008D2E2A0DA3DC51084E1BBB29F78836A4C0DE13714CBBFB0D448ADFD241841062507F0B803145710EFDF47368FD982141652B848881B20A2C9FE5DFEEA36CE6D15CC9D9E7",
|
||||
"response": "A1828C9C5EFC9D6D96DE0A708CAF5692D5BD9ED037D002C4749FA64E17B2A015",
|
||||
"usage": "SecretValue"
|
||||
}
|
||||
]
|
||||
},
|
||||
"auxiliary": {
|
||||
"owner_id": "guardian_1",
|
||||
"sequence_order": 1,
|
||||
"key": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAsVI84AFUjQya+wlfTAOh\nPjzBO+LbOKEeqNWK5Jv84z1haFDZ4jm/2adCGlTx9xBUd1aRsUeYUBKA8uSR1BBB\ngRFPtlHwTbLjCZ0A2ZwcVDFMHlvTCATfXqSQanuHOY/RBMndc8T+YqBLsa2w9zT5\naqCx98GBzzuKW03/+t8RvjjjZ0D3DGI/buGhm9KgwKQmnSHzQNXcSvOCeib/hgtt\n3ovVmchhbxZz/mRdXCfVx2EoUJKiHQiPzBvGNKtsLPzRHgmI5314hfU3f+EUMh8A\n37XsBVmgG7ASUp4DS7DE4fLwI/L2JZKKyw7RbsbuI4YVO2+FtA/xkxmyU51b/xXl\nJrp5GYLiQtFMaesWeiCWeUr11rsY8dbcbFXF2S5w4ADUFN0A9llGnxmPfmlGRjYk\nhtWk7Mx3JwjlTzmEtac6yXm4X20gj6L3jhnzW0hFlawzDQsF3tGJo9nRKgp4ID2T\n7Ptf88X4RotmJHLSYr18EVbHsM7kti47pCJGAZJqWSm/w5TN1LW6nX7F9dhZ1xM/\nh0QabkO3ySvAE/gzHQebyGTvKaYPJXwhWD+lDcMNx8D/pCEOYWtYlbuHyX5+eIgM\nDkF5CUJ4HqNvHxRSlhQ9dT+uc9aalv4OyzLeE4ytuUicIoEQkpVw5/PjIMu69OGD\n61Oe8WITkbWQaijxsQLc99UCAwEAAQ==\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
},
|
||||
{
|
||||
"election": {
|
||||
"owner_id": "guardian_2",
|
||||
"sequence_order": 2,
|
||||
"key": "95772093EEEFC25F34ADAE7669805CC1546393EAF6C3150B9B19150B0584F76F83E50EB80E935DC41A5B5F9B20644D8832F1DB0A7F1C3989516B94F819B33A3FA4A9E71B985DCA42AB7FAFFADBBF088CDECB1E2716CD5A7B075D40E13A8287175756245CAF8E72F72493426B85D42968C3AF4886A09DE6F987DE59AC7C78EFF48503576BCC90C62ABF41BB5DC739DF7232426A4033EAEB1011FA65B2ED6C4F4E1F4CD326BA1201D75618E163C0C63301A6765757FF982AC568FA3586000BF8A2CE461A45A4A1585A4CA2123182CBF61F2D538FB495794EDA3A1661B80D2D3F06F531730A8D3032E80F5671C4F403B05F2EADB2DB887468029D0C3AE6D5A816F00D433453E146EA3E01142132DE3AEBDFF3EC94CF344F61632FA3D9A0251647815F8176DD90BEF6A731C76AD9CBD940DD4D2AD274045C0F3E561E526562FC906459CEE93182BC62D8CD1CB38548C146E816D0293562CC5B1EC37363B2F8E1069DE915C32076A2D190B3959C32AC9BE5B0C4193FF3868E62DA7EF4828187A9EBCDC64AFD10A496BBD91EE5BB3B9223CB6C7C9062F5D18FD76DF450D0717AB5AAF0A3AA6E1C5EFFC7DA1203EB9BF2B920302FD8DFC393C0E444452602F1C888EE785AE0DF7FB25F82AA7E25B28F304966BE92CC6B81CE7C61F5086DA154A02A81082133EAE92DDC144E67FF58E701DFB2CAA70AAE12381C03B211FA91F7DA64F13F",
|
||||
"coefficient_commitments": [
|
||||
"95772093EEEFC25F34ADAE7669805CC1546393EAF6C3150B9B19150B0584F76F83E50EB80E935DC41A5B5F9B20644D8832F1DB0A7F1C3989516B94F819B33A3FA4A9E71B985DCA42AB7FAFFADBBF088CDECB1E2716CD5A7B075D40E13A8287175756245CAF8E72F72493426B85D42968C3AF4886A09DE6F987DE59AC7C78EFF48503576BCC90C62ABF41BB5DC739DF7232426A4033EAEB1011FA65B2ED6C4F4E1F4CD326BA1201D75618E163C0C63301A6765757FF982AC568FA3586000BF8A2CE461A45A4A1585A4CA2123182CBF61F2D538FB495794EDA3A1661B80D2D3F06F531730A8D3032E80F5671C4F403B05F2EADB2DB887468029D0C3AE6D5A816F00D433453E146EA3E01142132DE3AEBDFF3EC94CF344F61632FA3D9A0251647815F8176DD90BEF6A731C76AD9CBD940DD4D2AD274045C0F3E561E526562FC906459CEE93182BC62D8CD1CB38548C146E816D0293562CC5B1EC37363B2F8E1069DE915C32076A2D190B3959C32AC9BE5B0C4193FF3868E62DA7EF4828187A9EBCDC64AFD10A496BBD91EE5BB3B9223CB6C7C9062F5D18FD76DF450D0717AB5AAF0A3AA6E1C5EFFC7DA1203EB9BF2B920302FD8DFC393C0E444452602F1C888EE785AE0DF7FB25F82AA7E25B28F304966BE92CC6B81CE7C61F5086DA154A02A81082133EAE92DDC144E67FF58E701DFB2CAA70AAE12381C03B211FA91F7DA64F13F",
|
||||
"0C38BB43D919999789C91D26712EE6BD86ADDF39FF6693CF3B69CE4714ADF7913AE0EF4E8F6702C0B25A585335B3F54F2B2EB244209F7911FFEE5D331F475F85B920E08D2D2AE8DC2E9C5A99683A8222436C7B4B4E8F322CB3DA972971D15C8DF768BB76574FE6A04127470C526A67C378C57A15BA470B491BBF4A2C7BC9B610C7FF3ECAC7A2935083C067BE9F637FAD141078A12B1564F5609DF4ADA9513BC67FFCCE66D44A7A87752E371ED104BCE9AA9E80AF5DB237491099675C1DE6D094E9854699A9C8EE01D21724F8178F3D8F015CF9E288D7A8C56302B02FF71FB9792A4124CD1D5124E16937A8C08AFC3D35AB383918870E42FE3427CF3D2D4EAAF72DCC831529EB7F6E17A6995EEE18512ABA160B47A3B2BB3D6D132EFB518CBC504794C82BB3143E9D76640791CF8FF52810830ECCF9D8FC101015C694165A9B81A81CA30C75394AA2C2FD80ECDE40D9148C502C3E7EBC08DB7A41D7A8B31980F1055B58A2D7BDA18817BFA69A65FF5EAAE145DC8CD125BF5811E94D1A9F00CE1203ED961C4F7B2567546692B95D55D2D98D89C7C64949CA084BF9D57CDB400889D7D4A8F2550707F9F85F2F215AD13CAED3D92F89C27568E7B20635A36CFABC47DA134AFD56F954CD5E6E071A746EF006795EC6CB494F398F08B7D0039AC241C03857DB128CA033A5A82A4424F66A0B371A2997A0334168BEA3D28733F391F873"
|
||||
],
|
||||
"coefficient_proofs": [
|
||||
{
|
||||
"challenge": "5AA7F750F94ED4B2E24EBBE44651B2B4AB7B241B8E5EC6C1C5B92D61BC30AA21",
|
||||
"commitment": "1F56E5B4FFF78B031949223E18B862912CBD8AEF996B94B269A34177CA71F9944982E96708793EF78A91B098944D192645D3A73EEE884392C8658825C6D7825AD96B5A2AB5D5F5753CB30FE4C2BA6AD7B20AEF936AC12CE403BEB9D2424737BB93604E9B34DC3E45A6190DE2F128937522469C32FED56F43FAD189DBDF6B6406D52FFC85840C51E7128C5F1D4EAE73E28BDA1538EAD0D70A03F02F428B2180106D9E00C16862269C56EE59A00B6BE482E6751927DB84C960A2C0082558E631F80B7F45963E56355A11F34FD45D54B313FD3A708192290E4C8FA80453C36A4D8BBF3513F7494106E4660DCBC094B5977FE58BA00F013E1E35913443AC5D27035A96A2CDDCE3E5F15EEEBC82E48CD85CE277D8FC7BAEFE4AD4E67570C947CDC5441B5A79E1C51968BD2C274C817A93553CABB99735422BF9912D4C9EC5F80A1B9832975CF015A53833437C926C60496FA92538A0F7914E74121E818C1FA382C31364CB6E498E1EFA0BF4249D6B116119C96FB6EFCD7AD9DDADC4A7A57C2B0BB04D2397A4310DE2B8F13C0A522E64C032505E43AA3AB5B74C1C4AB5FC1DA882CD8CCB1C725F042FE097327E8B259D3B71C8AB134E1D66CB618586DFE514CC608AB0B37BCCA78ECEC919AF0B0B0BA87381FAF03A3A0CFAEF6C2B038343EA2DA5C9BB457B58ECB9B37EC5B1A17CD529C4CD120D718CA4BE6F8438DB419A09688410B2",
|
||||
"name": "Schnorr Proof",
|
||||
"public_key": "95772093EEEFC25F34ADAE7669805CC1546393EAF6C3150B9B19150B0584F76F83E50EB80E935DC41A5B5F9B20644D8832F1DB0A7F1C3989516B94F819B33A3FA4A9E71B985DCA42AB7FAFFADBBF088CDECB1E2716CD5A7B075D40E13A8287175756245CAF8E72F72493426B85D42968C3AF4886A09DE6F987DE59AC7C78EFF48503576BCC90C62ABF41BB5DC739DF7232426A4033EAEB1011FA65B2ED6C4F4E1F4CD326BA1201D75618E163C0C63301A6765757FF982AC568FA3586000BF8A2CE461A45A4A1585A4CA2123182CBF61F2D538FB495794EDA3A1661B80D2D3F06F531730A8D3032E80F5671C4F403B05F2EADB2DB887468029D0C3AE6D5A816F00D433453E146EA3E01142132DE3AEBDFF3EC94CF344F61632FA3D9A0251647815F8176DD90BEF6A731C76AD9CBD940DD4D2AD274045C0F3E561E526562FC906459CEE93182BC62D8CD1CB38548C146E816D0293562CC5B1EC37363B2F8E1069DE915C32076A2D190B3959C32AC9BE5B0C4193FF3868E62DA7EF4828187A9EBCDC64AFD10A496BBD91EE5BB3B9223CB6C7C9062F5D18FD76DF450D0717AB5AAF0A3AA6E1C5EFFC7DA1203EB9BF2B920302FD8DFC393C0E444452602F1C888EE785AE0DF7FB25F82AA7E25B28F304966BE92CC6B81CE7C61F5086DA154A02A81082133EAE92DDC144E67FF58E701DFB2CAA70AAE12381C03B211FA91F7DA64F13F",
|
||||
"response": "1245641FBA8C41AB7C9AC682950A5289D3A4FC57E445F4830C0F5BF8C0E91CED",
|
||||
"usage": "SecretValue"
|
||||
},
|
||||
{
|
||||
"challenge": "6B42F99DC8E40EE3AA4C663F57923DA705711E45E48C72A6B773B5697365A1BD",
|
||||
"commitment": "51D8A50F872475679481A207BFD2042572D4BCDBBCE97F9288ED61485EE944BD7163043BC5EB691C811A0B4571446E5E838491D2ADB37D44BBF2A0252F303C08B9570F379E8F5ACCCD97CD762A9FC9D401E07134F4E5337600741524C49D59629C6C389D0B6E8EE0E1DECD41E86B4DAE7A7FD1E06A9C2CA1DC8086F1C7A4E68A367B5FFD3EEB46046CCC6767EED648C6D678664BF15DAD95E3333D266864F83457A2306726CB8CE7C10CD4E2EB6D73D0938BB8BA049940C4CC234EB082858DF1007B5FBE45ACF1A94261114D309E7B9A59FDD258D37A1A901E55CBB342105B119690A105AB856DC1B2BD53E4C1382CF9FA5BED7C2AABBC7FA0EC6BD95FB5AB7A1304F60135B0A5F0FE836351DFFB8EC68186001ED27A519E388F9D895235AE391600EA6C3C7F1CF4B99C43F2ACB2589CC04D29CAEA98AAA4A9CAC523BED78FBC69828156D8EEF9AD633359BD86AF4F0541576629558E4C43616C04D86DDA47A8681B21278057BA94F3A25039C54018927E353F93B2B9717F7564DE4328ECF48CD9CDB502DFC78FD82FBC859AEB3C607F9F82B5CEC16CB6126217975626A6B91104009D4A8FDD8AE80C1E17DFDCA8B6B59F07C28EA8C8B40647E59E3E896DDD8A7AA636DE7C03AA7C94002DC687FD5B4FF7B92DDD81904C86A533EE2FE0CFD528AF468DEA88D72A863B13384771729B3B9E26538E98D0D5431FA293E37A57569A",
|
||||
"name": "Schnorr Proof",
|
||||
"public_key": "0C38BB43D919999789C91D26712EE6BD86ADDF39FF6693CF3B69CE4714ADF7913AE0EF4E8F6702C0B25A585335B3F54F2B2EB244209F7911FFEE5D331F475F85B920E08D2D2AE8DC2E9C5A99683A8222436C7B4B4E8F322CB3DA972971D15C8DF768BB76574FE6A04127470C526A67C378C57A15BA470B491BBF4A2C7BC9B610C7FF3ECAC7A2935083C067BE9F637FAD141078A12B1564F5609DF4ADA9513BC67FFCCE66D44A7A87752E371ED104BCE9AA9E80AF5DB237491099675C1DE6D094E9854699A9C8EE01D21724F8178F3D8F015CF9E288D7A8C56302B02FF71FB9792A4124CD1D5124E16937A8C08AFC3D35AB383918870E42FE3427CF3D2D4EAAF72DCC831529EB7F6E17A6995EEE18512ABA160B47A3B2BB3D6D132EFB518CBC504794C82BB3143E9D76640791CF8FF52810830ECCF9D8FC101015C694165A9B81A81CA30C75394AA2C2FD80ECDE40D9148C502C3E7EBC08DB7A41D7A8B31980F1055B58A2D7BDA18817BFA69A65FF5EAAE145DC8CD125BF5811E94D1A9F00CE1203ED961C4F7B2567546692B95D55D2D98D89C7C64949CA084BF9D57CDB400889D7D4A8F2550707F9F85F2F215AD13CAED3D92F89C27568E7B20635A36CFABC47DA134AFD56F954CD5E6E071A746EF006795EC6CB494F398F08B7D0039AC241C03857DB128CA033A5A82A4424F66A0B371A2997A0334168BEA3D28733F391F873",
|
||||
"response": "7F9F037C773259DF340CE3B1116A6074103D12AD06087E59757911A716375DF7",
|
||||
"usage": "SecretValue"
|
||||
}
|
||||
]
|
||||
},
|
||||
"auxiliary": {
|
||||
"owner_id": "guardian_2",
|
||||
"sequence_order": 2,
|
||||
"key": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApYyLSfBMCdZkXx8477by\nMD07wyfMNBS+ZM1fJhGiPYaEFIHQae7gDpLTa2+RpW/KWGYrC7ZP5TWq2HZ6bp+3\nUr9blfj58MQvIobnWZDd+bVygfPXaOwxoxxaGQnhEJ2VRd9H+ZshblGGBaB2Qbes\n5g8geBoE1a7iKT/1OiFlDn8TCZVbZq0Ncwz49AlDg4hk/c8wGjrbx5z7uT5vV21O\n9C8QujGU2ZS2QM3AjPs7itxsN3XhabQMjBawRXuSpZ503DqpLA0F/9EsyJb6HOL1\nnPTXnG4bcovy4q6BJeuuvRJRBYwweuZ1ApDQP8nEq0YKRWEphscTtxAJqaIP+5LB\ngC+hznSLIhR+lRPqpn+MY33hF8SG16syr4d25zk/4vHpPfnOgNGLHdYoJ3wL6HfR\ns1PoubU8x38GCib/WXWOshMLqGRkWdElLnmSt/O5FMdUfBygxa1HJ+HAWfiUdBCP\naTPZHa8fEEZbBeH8zalPm/knNXAtCWh4kI496pJWsfUblOr2P0db4hzO3Nt09hPG\nZ1hHOLCD1ftQSURIcqNjWraxV+IEU9v/NHc/b5ATetJV9vfKrawtzBU742tzFyNA\n+mrGArLrq280tZiYQnsmMHYJ9rns08NnXxQ8zVYSDcw7Nd5O/fAFG3V+nSgF/oDr\nZdTiEFX67mC8DSRNVpy5V4MCAwEAAQ==\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
}
|
||||
],
|
||||
"override_rsa": true
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
mediator > Share Backups (repeat for all of them)
|
||||
|
||||
```
|
||||
{
|
||||
"guardian_id": "guardian_1",
|
||||
"backups": [
|
||||
{
|
||||
"owner_id": "guardian_1",
|
||||
"designated_id": "guardian_2",
|
||||
"designated_sequence_order": 2,
|
||||
"encrypted_value": "DE156A0E08AE6827ACEF2FFDEB9F2431EE36C1BBDB811F1218158198AD7D3E7F"
|
||||
},
|
||||
{
|
||||
"owner_id": "guardian_1",
|
||||
"designated_id": "guardian_3",
|
||||
"designated_sequence_order": 3,
|
||||
"encrypted_value": "D209493D39911BFFC9F798E6649D64DEDE1841BAFF6A09C4BE6B498ABDBEBDE6"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
"guardian_id": "guardian_2",
|
||||
"backups": [
|
||||
{
|
||||
"owner_id": "guardian_2",
|
||||
"designated_id": "guardian_1",
|
||||
"designated_sequence_order": 1,
|
||||
"encrypted_value": "5297BB9A19DB204A61B8860DD0039898303E747EEB3FC30B1E8E67C96C9A507D"
|
||||
},
|
||||
{
|
||||
"owner_id": "guardian_2",
|
||||
"designated_id": "guardian_3",
|
||||
"designated_sequence_order": 3,
|
||||
"encrypted_value": "D6D157F819E0B0042736C9AEA4B635A62848AE9FDBF33259E9DE9D71A79F87AD"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
"guardian_id": "guardian_3",
|
||||
"backups": [
|
||||
{
|
||||
"owner_id": "guardian_3",
|
||||
"designated_id": "guardian_1",
|
||||
"designated_sequence_order": 1,
|
||||
"encrypted_value": "6FA79B63625F1B745969276DAA8095E50087BF7BECF44DFB95F94DC7B36F70ED"
|
||||
},
|
||||
{
|
||||
"owner_id": "guardian_3",
|
||||
"designated_id": "guardian_2",
|
||||
"designated_sequence_order": 2,
|
||||
"encrypted_value": "AEB7A68DD790A7A065F35469F5CEFDC53153B9B41B94A3A72C13873CBA1E3E99"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
guardian > Verify Backup (repeat for all) - make note of them
|
||||
|
||||
```
|
||||
{
|
||||
"guardian_id": "guardian_1",
|
||||
"backup": {
|
||||
"owner_id": "guardian_2",
|
||||
"designated_id": "guardian_1",
|
||||
"designated_sequence_order": 1,
|
||||
"encrypted_value": "5297BB9A19DB204A61B8860DD0039898303E747EEB3FC30B1E8E67C96C9A507D"
|
||||
},
|
||||
"override_rsa": true
|
||||
}
|
||||
|
||||
{
|
||||
"guardian_id": "guardian_1",
|
||||
"backup": {
|
||||
"owner_id": "guardian_3",
|
||||
"designated_id": "guardian_1",
|
||||
"designated_sequence_order": 1,
|
||||
"encrypted_value": "6FA79B63625F1B745969276DAA8095E50087BF7BECF44DFB95F94DC7B36F70ED"
|
||||
},
|
||||
"override_rsa": true
|
||||
}
|
||||
|
||||
{
|
||||
"guardian_id": "guardian_2",
|
||||
"backup": {
|
||||
"owner_id": "guardian_1",
|
||||
"designated_id": "guardian_2",
|
||||
"designated_sequence_order": 2,
|
||||
"encrypted_value": "DE156A0E08AE6827ACEF2FFDEB9F2431EE36C1BBDB811F1218158198AD7D3E7F"
|
||||
},
|
||||
"override_rsa": true
|
||||
}
|
||||
|
||||
{
|
||||
"guardian_id": "guardian_2",
|
||||
"backup": {
|
||||
"owner_id": "guardian_3",
|
||||
"designated_id": "guardian_2",
|
||||
"designated_sequence_order": 2,
|
||||
"encrypted_value": "AEB7A68DD790A7A065F35469F5CEFDC53153B9B41B94A3A72C13873CBA1E3E99"
|
||||
},
|
||||
"override_rsa": true
|
||||
}
|
||||
|
||||
{
|
||||
"guardian_id": "guardian_3",
|
||||
"backup": {
|
||||
"owner_id": "guardian_1",
|
||||
"designated_id": "guardian_3",
|
||||
"designated_sequence_order": 3,
|
||||
"encrypted_value": "D209493D39911BFFC9F798E6649D64DEDE1841BAFF6A09C4BE6B498ABDBEBDE6"
|
||||
},
|
||||
"override_rsa": true
|
||||
}
|
||||
|
||||
{
|
||||
"guardian_id": "guardian_3",
|
||||
"backup": {
|
||||
"owner_id": "guardian_2",
|
||||
"designated_id": "guardian_3",
|
||||
"designated_sequence_order": 3,
|
||||
"encrypted_value": "D6D157F819E0B0042736C9AEA4B635A62848AE9FDBF33259E9DE9D71A79F87AD"
|
||||
},
|
||||
"override_rsa": true
|
||||
}
|
||||
```
|
||||
|
||||
mediator > Verify Backups (repeat for all)
|
||||
|
||||
```
|
||||
{
|
||||
"key_name": "key_ceremony_1",
|
||||
"guardian_id": "guardian_1",
|
||||
"verifications": [
|
||||
{
|
||||
"owner_id": "guardian_2",
|
||||
"designated_id": "guardian_1",
|
||||
"verifier_id": "guardian_1",
|
||||
"verified": true
|
||||
},
|
||||
{
|
||||
"owner_id": "guardian_3",
|
||||
"designated_id": "guardian_1",
|
||||
"verifier_id": "guardian_1",
|
||||
"verified": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
"key_name": "key_ceremony_1",
|
||||
"guardian_id": "guardian_2",
|
||||
"verifications": [
|
||||
{
|
||||
"owner_id": "guardian_1",
|
||||
"designated_id": "guardian_2",
|
||||
"verifier_id": "guardian_2",
|
||||
"verified": true
|
||||
},
|
||||
{
|
||||
"owner_id": "guardian_3",
|
||||
"designated_id": "guardian_2",
|
||||
"verifier_id": "guardian_2",
|
||||
"verified": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
{
|
||||
"key_name": "key_ceremony_1",
|
||||
"guardian_id": "guardian_3",
|
||||
"verifications": [
|
||||
{
|
||||
"owner_id": "guardian_1",
|
||||
"designated_id": "guardian_3",
|
||||
"verifier_id": "guardian_3",
|
||||
"verified": true
|
||||
},
|
||||
{
|
||||
"owner_id": "guardian_2",
|
||||
"designated_id": "guardian_3",
|
||||
"verifier_id": "guardian_3",
|
||||
"verified": true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
mediator > Publish Joint Key
|
||||
|
||||
mediator > Close Key Ceremony
|
||||
|
||||
## Manifest
|
||||
|
||||
mediator > Submit Manifest
|
||||
|
||||
```
|
||||
// not provided, the example uses the hamilton county general election manifest
|
||||
```
|
||||
|
||||
mediator > Build Election Context
|
||||
|
||||
```
|
||||
{
|
||||
"manifest_hash": "F50393532F36544827661DF2279755CFFA079D32C6E0A8E8570D1D91FBAAD245",
|
||||
"elgamal_public_key": "B1CCFB49E3D0EA64FB0FE059291BE8C29AF358DFF7FCF8D546E1D846E120A7ECB9FB0BF4B1842F7FED78E57C3583A10154706194417D732CD88753BEB567267F004B6B14BF77AD60C552315072B6023A983241F902DD2B74B815F7F29ACC67D784858BDBB27D3865A50BAC6619E58B57B7758CFF6830A22E169F63A605164C7F1BA2CA790ACD5854CF8C980D01060EAADC484FC0F3554098581BB241FA60ACA932B8103BC8BFEC7D7F5779DEDB6837D084672ABB4BFA17D49C41DB8D6A5435827C1C1C86CA3EBA585181EEC64C76613D19CF4C97A617D0B19EF33A190777FCCE40C8667D9927005B9291B64E7FD0A589384B588717A6A5E632D3226BB8FC482369744881773BEC3B8EAAA37071B0797F5FA5E048A029693585AF68C006B19DE2B94E19334607ECBF69AE5D4C3FEDFC7B72D509D1139E58DDC907D1F43F9EEE90BAAB9D0C1D48E0E5D01EB259CAD7F7F24CE68AAD57E5C974032931C7EAFEBF1238F2FA8D5E5AEC297164E78E0E809E2F61B1228B7EF319837CE085E6DA47DF949027DC54C7A648BC0F2A6032184A9411966C48F528B4168B5633566CD481489CB1FAB52183658EB487423FB18A120E8B30832338A4CB72092734A4D01DB47AE0CC3C48FCA02CF432F5C334FC7C900C57BD4D506B8B84A7030863DC46463EC198734EC7D4CB8C7600CA2137EE36512BE1DAA076616C911A4BBE91B9E43F6745E7",
|
||||
"commitment_hash": "4F642D946DB82268AF873F6359D85019CF9A3658B6655942AE7CF84F7382505B",
|
||||
"number_of_guardians": 5,
|
||||
"quorum": 3
|
||||
}
|
||||
```
|
||||
|
||||
mediator > Create Election
|
||||
|
||||
```
|
||||
{
|
||||
"election_id": "hamilton-general-election-1",
|
||||
"key_name": "key_ceremony_1",
|
||||
"context": {
|
||||
"commitment_hash": "4F642D946DB82268AF873F6359D85019CF9A3658B6655942AE7CF84F7382505B",
|
||||
"crypto_base_hash": "B3B8CEA020B4C1CA796060D5B54ED24F20BEAB937F32C480D5648D3D38DC0882",
|
||||
"crypto_extended_base_hash": "30CD485CBD768C6C2BB92867876D401509E94701B8D24F6CD89A28A1D0579862",
|
||||
"elgamal_public_key": "B1CCFB49E3D0EA64FB0FE059291BE8C29AF358DFF7FCF8D546E1D846E120A7ECB9FB0BF4B1842F7FED78E57C3583A10154706194417D732CD88753BEB567267F004B6B14BF77AD60C552315072B6023A983241F902DD2B74B815F7F29ACC67D784858BDBB27D3865A50BAC6619E58B57B7758CFF6830A22E169F63A605164C7F1BA2CA790ACD5854CF8C980D01060EAADC484FC0F3554098581BB241FA60ACA932B8103BC8BFEC7D7F5779DEDB6837D084672ABB4BFA17D49C41DB8D6A5435827C1C1C86CA3EBA585181EEC64C76613D19CF4C97A617D0B19EF33A190777FCCE40C8667D9927005B9291B64E7FD0A589384B588717A6A5E632D3226BB8FC482369744881773BEC3B8EAAA37071B0797F5FA5E048A029693585AF68C006B19DE2B94E19334607ECBF69AE5D4C3FEDFC7B72D509D1139E58DDC907D1F43F9EEE90BAAB9D0C1D48E0E5D01EB259CAD7F7F24CE68AAD57E5C974032931C7EAFEBF1238F2FA8D5E5AEC297164E78E0E809E2F61B1228B7EF319837CE085E6DA47DF949027DC54C7A648BC0F2A6032184A9411966C48F528B4168B5633566CD481489CB1FAB52183658EB487423FB18A120E8B30832338A4CB72092734A4D01DB47AE0CC3C48FCA02CF432F5C334FC7C900C57BD4D506B8B84A7030863DC46463EC198734EC7D4CB8C7600CA2137EE36512BE1DAA076616C911A4BBE91B9E43F6745E7",
|
||||
"manifest_hash": "F50393532F36544827661DF2279755CFFA079D32C6E0A8E8570D1D91FBAAD245",
|
||||
"number_of_guardians": 5,
|
||||
"quorum": 3
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
mediator -> Encrypt Ballots
|
||||
|
||||
```
|
||||
{
|
||||
"election_id": "hamilton-general-election-1",
|
||||
"seed_hash": "B52C5D5946B74F45001E90F5D9BED765C52921F0ED11A5F430F50CDF82EE4756",
|
||||
"ballots":[...insert plaintext ballots here...]
|
||||
}
|
||||
```
|
||||
|
||||
mediator -> Cast / Spoil
|
||||
|
||||
```
|
||||
{
|
||||
"election_id": "hamilton-general-election-1",
|
||||
"ballots": [...insert ciphertext ballots here...]
|
||||
}
|
||||
```
|
||||
|
||||
## Tally
|
||||
|
||||
mediator -> Start A New Tally
|
||||
|
||||
```
|
||||
{{mediator-url}}/api/{{version}}/tally?election_id=hamilton-general-election-1&tally_name=hamilton-general-election-tally-1
|
||||
```
|
||||
|
||||
## Decrypt
|
||||
|
||||
guardian -> Compute Tally Decryption Share
|
||||
|
||||
```
|
||||
{
|
||||
"guardian_id": "guardian_1,
|
||||
"context": {...}
|
||||
"encrypted_tally: {...}
|
||||
}
|
||||
```
|
||||
|
||||
mediator -> Sumbit Tally Decryption Share
|
||||
|
||||
```
|
||||
{
|
||||
"share": {...}
|
||||
}
|
||||
```
|
||||
|
||||
mediator -> Decrypt Tally
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
Загрузка…
Ссылка в новой задаче