use std::collections::HashMap;
use sha2::{Digest, Sha256};
use crate::{
VotingError,
election::Election,
scheme::{Fr, G1, Hvk, Open, VotingParams},
voter::Cast,
};
pub type BallotDigest = [u8; 32];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Revote {
Replace,
Refuse,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Admission {
Counted { tag: String },
Replaced { tag: String, previous: BallotDigest },
AlreadyCounted { tag: String },
}
pub trait NullifierStore {
fn get(&self, tag: &str) -> Result<Option<BallotDigest>, VotingError>;
fn insert_if_absent(
&mut self,
tag: &str,
digest: BallotDigest,
) -> Result<Option<BallotDigest>, VotingError>;
fn swap(
&mut self,
tag: &str,
digest: BallotDigest,
) -> Result<Option<BallotDigest>, VotingError>;
}
#[derive(Debug, Default)]
pub struct MemoryNullifiers(HashMap<String, BallotDigest>);
impl MemoryNullifiers {
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
impl NullifierStore for MemoryNullifiers {
fn get(&self, tag: &str) -> Result<Option<BallotDigest>, VotingError> {
Ok(self.0.get(tag).copied())
}
fn insert_if_absent(
&mut self,
tag: &str,
digest: BallotDigest,
) -> Result<Option<BallotDigest>, VotingError> {
if let Some(d) = self.0.get(tag) {
return Ok(Some(*d));
}
self.0.insert(tag.to_string(), digest);
Ok(None)
}
fn swap(
&mut self,
tag: &str,
digest: BallotDigest,
) -> Result<Option<BallotDigest>, VotingError> {
Ok(self.0.insert(tag.to_string(), digest))
}
}
pub struct BallotBox<S> {
open: Open,
hvk: Hvk,
election: Election,
phi: Fr,
point: G1,
revote: Revote,
store: S,
}
impl<S> std::fmt::Debug for BallotBox<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BallotBox")
.field("election", &self.election)
.field("revote", &self.revote)
.finish_non_exhaustive()
}
}
impl<S: NullifierStore> BallotBox<S> {
pub fn new(
params: &VotingParams,
election: Election,
revote: Revote,
store: S,
) -> Result<Self, VotingError> {
if params.community() != election.community() {
return Err(VotingError::WrongCommunity {
expected: params.community().to_string(),
actual: election.community().to_string(),
});
}
Ok(Self {
phi: params.open().enc_pred(&election.predicate())?,
point: election.point()?,
open: params.open().clone(),
hvk: params.hvk().clone(),
election,
revote,
store,
})
}
pub fn election(&self) -> &Election {
&self.election
}
pub fn store(&self) -> &S {
&self.store
}
pub fn verify(&self, cast: &Cast, ballot: &[u8]) -> Result<String, VotingError> {
if cast.attestation.phi != self.phi {
return Err(VotingError::WrongClass);
}
self.open
.check_attestation_in_context(
&self.hvk,
&self.point,
&cast.attestation,
&self.election.ballot_context(ballot),
)
.map_err(VotingError::ProofRejected)?;
cast.tag()
}
pub fn admit(&mut self, cast: &Cast, ballot: &[u8]) -> Result<Admission, VotingError> {
let digest: BallotDigest = Sha256::digest(ballot).into();
if self.revote == Revote::Refuse {
let tag = cast.tag()?;
if let Some(held) = self.store.get(&tag)?
&& held != digest
{
return Err(VotingError::AlreadyVoted);
}
}
let tag = self.verify(cast, ballot)?;
let previous = match self.revote {
Revote::Replace => self.store.swap(&tag, digest)?,
Revote::Refuse => self.store.insert_if_absent(&tag, digest)?,
};
match previous {
None => Ok(Admission::Counted { tag }),
Some(p) if p == digest => Ok(Admission::AlreadyCounted { tag }),
Some(_) if self.revote == Revote::Refuse => Err(VotingError::AlreadyVoted),
Some(previous) => Ok(Admission::Replaced { tag, previous }),
}
}
}