* 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:
Matt Wilhelm 2021-08-17 12:31:48 -07:00 коммит произвёл GitHub
Родитель bee0f8d2e4
Коммит 28444eda9b
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
44 изменённых файлов: 2332 добавлений и 1015 удалений

3
.gitignore поставляемый
Просмотреть файл

@ -142,3 +142,6 @@ cython_debug/
# Mac
.DS_Store
# Project
storage/

8
.vscode/launch.json поставляемый
Просмотреть файл

@ -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"
}
}
]

Просмотреть файл

@ -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"

126
app/core/ballot.py Normal file
Просмотреть файл

@ -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

105
app/core/election.py Normal file
Просмотреть файл

@ -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

89
app/core/manifest.py Normal file
Просмотреть файл

@ -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"

184
app/core/tally.py Normal file
Просмотреть файл

@ -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

86
app/core/tally_decrypt.py Normal file
Просмотреть файл

@ -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"
}

1
app/data/manifest.json Normal file

Различия файлов скрыты, потому что одна или несколько строк слишком длинны

Просмотреть файл

@ -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');

564
tests/manual.md Normal file
Просмотреть файл

@ -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

Различия файлов скрыты, потому что одна или несколько строк слишком длинны