The Crypto E-Voting API is built around a five-party protocol where each party holds a distinct role and no single party ever possesses enough information to compromise ballot secrecy. The FastAPI application exposes four router groups (Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Crypto-Project-ENSTA/back-end/llms.txt
Use this file to discover all available pages before exploring further.
voters, voting, results, config) backed by five service classes that coordinate RSA blind signatures, nonce-based authentication, and an anonymizer pipeline to decouple voter identity from cast ballots.
The five parties
Voter
Registers with an email address, receives N1 and N2 credentials, blinds their ballot, and submits the encrypted result. The voter never shares their raw vote with any party that also knows their identity.
Commissioner
Manages the credential store. Validates that an N1 code exists before a ballot is accepted, and verifies that an N2 hash was legitimately issued before a counted vote is accepted as valid. Implemented in
CommissionerService.Administrator
Holds the RSA private key used to blind-sign ballots. The administrator signs masked (blinded) ballots using
sign_masked_ballot, so it never sees the plaintext vote. Implemented in AdministratorService.Anonymizer
Acts as the submission gateway. Validates the voter’s N1 credential (consuming it on first use) and stores the encrypted ballot. When all expected votes are received it automatically triggers the counter. Implemented in
AnonymizerService.Counter
Decrypts all collected ballots with the counter’s RSA private key, verifies each ballot’s administrator signature, checks each N2 fingerprint with the commissioner, and records a
CountedVote with a status of VALID, INVALID_SIGNATURE, or INVALID_N2. Implemented in CounterService.No single party can link a voter’s identity to their ballot. The administrator signs without seeing content; the anonymizer submits without knowing the vote; the counter decrypts without knowing who submitted.
Request flow
Voter registration
The administrator calls
POST /voters/register with a list of email addresses. The API stores each email in the voters table and waits for the election to open.Election start and credential delivery
Calling
POST /voting/start-vote transitions the voting_config record to VOTE_STARTED. The system generates a unique N1 nonce and N2 nonce for every registered voter. N1 is stored as plaintext; N2 is stored only as its SHA-256 hash (hash_n2). Both are delivered to the voter by email.N1 verification
The voter calls
POST /voters/check_n1 with their N1 code. CommissionerService.is_n1_exist confirms the code is present in the credentials table. This step does not yet consume the credential.Blind ballot creation
The voter’s client creates a
VoterBallotDTO(vote, n2, random_bits), converts it to a big-endian integer, and blinds it with the administrator’s RSA public key: m' = m * k^e mod N. The blinding factor k is kept client-side.Blind signature
The masked ballot is sent to the administrator endpoint.
AdministratorService.sign_masked_ballot applies s' = (m')^d mod N and returns the signed masked value without ever seeing the original ballot.Unblinding
The voter’s client removes the blinding factor:
s = s' * k⁻¹ mod N. The result s is a valid RSA signature on the original ballot verifiable by anyone with the administrator’s public key.Ballot encryption and submission
The signed ballot is encrypted with the counter’s public key:
c = s^e mod N. The voter then calls POST /voters/submit_vote with their N2 code and vote choice. The server reads N1 from the session automatically. AnonymizerService.anonymize_and_submit_vote validates and consumes N1 (removing it from the credential store), then writes the encrypted vote to the votes table.Automatic tally
When the number of submitted votes reaches
num_voters (configured in voting_config), the anonymizer automatically invokes CounterService.finalize_voting. The counter decrypts, verifies, and tallies all ballots in a single pass, writing each result to counted_votes and transitioning the system to VOTE_ENDED.Component diagram
The five service classes are wired together at dependency-injection time. Here is how they reference each other:CommissionerService— standalone; owns thecredentialstable viacredentials_repository.AdministratorService— depends onCommissionerServiceto validate N1 before signing.CounterService— depends onAdministratorService(for signature verification) andCommissionerService(for N2 hash checks).AnonymizerService— depends onCommissionerService(N1 validation) andCounterService(to trigger tally).VotingSystemService— orchestratesAdministratorService,AnonymizerService, andCounterServiceinto the full blind-signature submission workflow called by the voter-facing router.
VotingSystemService is the only class the voting router calls directly. All cryptographic steps — blinding, signing, unblinding, encrypting — happen inside get_blind_signed_ballot and get_encrypted_signed_ballot, keeping the router thin.Key design decisions
Why RSA blind signatures?
Blind signatures allow the administrator to certify that a ballot is legitimate (i.e., the voter held valid credentials) without learning what the ballot contains. The administrator’s signature proves authenticity; the blinding guarantees secrecy. Without blinding, the administrator could record which candidate each voter chose.How anonymization works
The anonymizer sits between the voter and the ballot store. It validates the voter’s N1 credential and then discards the association — thevotes table row contains only the encrypted ciphertext and a timestamp, with no reference to the voter’s identity or email. The link between “who voted” (N1) and “what was voted” (encrypted ballot) is permanently severed at submission time.
Why the N1 + N2 scheme prevents double voting
Two independent controls work together:- N1 is consumed (deleted or marked used) the moment a ballot is submitted. A second submission attempt with the same N1 is rejected by
AnonymizerService.check_n1. - N2 is stored as a SHA-256 hash in the credential record. At counting time, the counter re-derives the hash and asks the commissioner whether it exists. A ballot carrying an N2 value that was never issued by the system (or that was already counted) is rejected with status
INVALID_N2.