use predicate_credential_system::pcs::{
Attestation, Credential, IssuanceState, PredicateCredentialSystem, RootRequest, UserSecretKey,
};
use rand::{CryptoRng, RngCore};
use crate::{
VotingError,
election::Election,
registrar::PreTicket,
scheme::{Base, E, G1, VotingParams, point_text},
};
pub type TicketRequest = RootRequest<E, Base>;
pub type TicketState = IssuanceState<E, Base>;
pub struct Voter {
usk: UserSecretKey<E>,
id: G1,
}
impl std::fmt::Debug for Voter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Voter").finish_non_exhaustive()
}
}
pub struct Ticket {
election: Election,
cred: Credential<E, Base>,
}
impl std::fmt::Debug for Ticket {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Ticket")
.field("election", &self.election.id())
.finish_non_exhaustive()
}
}
impl Ticket {
pub fn election(&self) -> &Election {
&self.election
}
pub fn to_text(&self) -> Result<String, VotingError> {
vti_vetting_pcs::scheme::enc(&self.cred)
.map_err(|e| VotingError::Serialization(e.to_string()))
}
pub fn from_text(election: Election, text: &str) -> Result<Self, VotingError> {
let cred = vti_vetting_pcs::scheme::dec(text)
.map_err(|e| VotingError::Serialization(e.to_string()))?;
Ok(Self { election, cred })
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Cast {
pub attestation: Attestation<E, Base>,
}
impl Cast {
pub fn tag(&self) -> Result<String, VotingError> {
point_text(&self.attestation.tag).map_err(|e| VotingError::Serialization(e.to_string()))
}
}
impl Voter {
pub fn generate<R: RngCore + CryptoRng>(
params: &VotingParams,
rng: &mut R,
) -> Result<Self, VotingError> {
let (id, usk) = params.open().user_keygen(rng)?;
Ok(Self { usk, id })
}
pub fn from_seed(params: &VotingParams, seed: &[u8; 32]) -> Result<Self, VotingError> {
Self::generate(params, &mut vti_vetting_pcs::scheme::seeded_rng(seed))
}
pub fn id(&self) -> &G1 {
&self.id
}
pub fn ticket_request<R: RngCore + CryptoRng>(
&self,
params: &VotingParams,
election: &Election,
rng: &mut R,
) -> Result<(TicketRequest, TicketState), VotingError> {
same_community(params, election)?;
Ok(params.open().root_request(
params.hvk(),
&election.predicate(),
&self.id,
&self.usk,
rng,
)?)
}
pub fn accept_ticket(
&self,
params: &VotingParams,
election: &Election,
pre: &PreTicket,
state: &TicketState,
) -> Result<Ticket, VotingError> {
same_community(params, election)?;
let cred =
params
.open()
.unblind(params.hvk(), &self.usk, &election.predicate(), pre, state)?;
Ok(Ticket {
election: election.clone(),
cred,
})
}
pub fn tag_for<R: RngCore + CryptoRng>(
&self,
params: &VotingParams,
ticket: &Ticket,
rng: &mut R,
) -> Result<String, VotingError> {
self.cast(params, ticket, b"vti/vote/tag", rng)?.tag()
}
pub fn cast_wire<R: RngCore + CryptoRng>(
&self,
params: &VotingParams,
ticket: &Ticket,
ballot: &serde_json::Value,
rng: &mut R,
) -> Result<serde_json::Value, VotingError> {
let bytes = serde_json_canonicalizer::to_vec(ballot)
.map_err(|e| VotingError::Serialization(e.to_string()))?;
let cast = self.cast(params, ticket, &bytes, rng)?;
let attestation = vti_vetting_pcs::scheme::enc(&cast.attestation)
.map_err(|e| VotingError::Serialization(e.to_string()))?;
Ok(serde_json::json!({
"ballot": ballot,
"tag": cast.tag()?,
"attestation": attestation,
}))
}
pub fn cast_encrypted_wire<R: RngCore + CryptoRng>(
&self,
params: &VotingParams,
ticket: &Ticket,
form: &EncryptedForm,
rng: &mut R,
) -> Result<serde_json::Value, VotingError> {
let (field, value) = match form {
EncryptedForm::Sealed(v) => ("sealed", v),
EncryptedForm::Encrypted(v) => ("encrypted", v),
};
let bytes = serde_json_canonicalizer::to_vec(value)
.map_err(|e| VotingError::Serialization(e.to_string()))?;
let cast = self.cast(params, ticket, &bytes, rng)?;
let attestation = vti_vetting_pcs::scheme::enc(&cast.attestation)
.map_err(|e| VotingError::Serialization(e.to_string()))?;
Ok(serde_json::json!({
field: value,
"tag": cast.tag()?,
"attestation": attestation,
}))
}
pub fn cast<R: RngCore + CryptoRng>(
&self,
params: &VotingParams,
ticket: &Ticket,
ballot: &[u8],
rng: &mut R,
) -> Result<Cast, VotingError> {
let election = &ticket.election;
same_community(params, election)?;
let attestation = params.open().attest_in_context(
params.hvk(),
&self.usk,
&election.predicate(),
&ticket.cred,
&election.point()?,
&election.ballot_context(ballot),
rng,
)?;
Ok(Cast { attestation })
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum EncryptedForm {
Sealed(serde_json::Value),
Encrypted(serde_json::Value),
}
pub fn encrypted_form<R: RngCore + CryptoRng>(
manifest: &trust_tasks_rs::specs::voting::election::register::v0_1::Manifest,
election: &Election,
key: &crate::trustees::ElectionKey,
ballot: &serde_json::Value,
tag: &str,
rng: &mut R,
) -> Result<EncryptedForm, VotingError> {
use trust_tasks_rs::specs::voting::election::register::v0_1::{Question, SecrecyLevel};
let bad = |why: String| VotingError::Serialization(why);
let ctx = crate::trustees::cast_ctx(&election.trustee_context(), tag);
match manifest.secrecy {
SecrecyLevel::Sealed => {
let bytes = serde_json_canonicalizer::to_vec(ballot).map_err(|e| bad(e.to_string()))?;
let sealed =
crate::trustees::seal(key, &bytes, &ctx, rng).map_err(|e| bad(e.to_string()))?;
Ok(EncryptedForm::Sealed(crate::wire::sealed_value(&sealed)))
}
SecrecyLevel::TalliedEncrypted => {
let mut questions = Vec::with_capacity(manifest.questions.len());
for q in &manifest.questions {
let Question::Choice {
key: qkey,
options,
min_selections,
max_selections,
optional,
..
} = q
else {
return Err(bad("a tallied-encrypted election asks choices only".into()));
};
let answer = ballot["answers"]
.as_array()
.and_then(|a| a.iter().find(|a| a["question"] == qkey.as_str()));
let chosen: Vec<bool> = options
.iter()
.map(|o| {
answer
.and_then(|a| a["choice"].as_array())
.is_some_and(|c| c.iter().any(|k| k == o.key.as_str()))
})
.collect();
let counts = crate::trustees::admitted_counts(
options.len(),
*min_selections,
max_selections.get(),
*optional,
)
.map_err(|e| bad(e.to_string()))?;
let eq = crate::trustees::encrypt_vote(
key,
&chosen,
&counts,
&crate::trustees::question_ctx(&ctx, qkey.as_str()),
rng,
)
.map_err(|e| bad(e.to_string()))?;
questions.push(crate::wire::encrypted_question_value(qkey.as_str(), &eq));
}
Ok(EncryptedForm::Encrypted(serde_json::json!({
"electionId": election.id().as_str(),
"manifestDigest": crate::rules::digest_multibase(election.manifest_digest()),
"questions": questions,
})))
}
_ => Err(bad("an open election is cast in the clear".into())),
}
}
fn same_community(params: &VotingParams, election: &Election) -> Result<(), VotingError> {
if params.community() == election.community() {
Ok(())
} else {
Err(VotingError::WrongCommunity {
expected: params.community().to_string(),
actual: election.community().to_string(),
})
}
}