use std::{
collections::{BTreeSet, HashMap},
future::Future,
};
use curve25519_dalek::Scalar;
use serde_json::{Value, json};
use sha2::{Digest, Sha256};
use trust_tasks_rs::specs::voting::trustees::{
confirm::v0_1 as confirm, deal::v0_1 as deal, enrol::v0_1 as enrol, justify::v0_1 as justify,
};
use crate::{
ceremony::{Ceremony, CeremonyError, SealedDealing, Step},
trustees::{TransportKey, scalar_from_text},
wire,
};
pub const KIND: &str = "trusteeStep";
pub const RETIRED_KINDS: [&str; 4] = ["transportKey", "dealing", "confirmation", "justification"];
pub fn entry(request: &Value) -> Value {
json!({ "kind": KIND, "request": request })
}
#[derive(Debug, Clone)]
pub enum TrusteeStep {
Enrol(TransportKey),
Deal(SealedDealing),
Confirm {
trustee: u32,
complaints: BTreeSet<u32>,
},
Justify {
dealer: u32,
recipient: u32,
share: Scalar,
},
}
impl TrusteeStep {
pub fn apply(self, c: &mut Ceremony) -> Result<Step, CeremonyError> {
match self {
Self::Enrol(tk) => c.enrol(tk),
Self::Deal(d) => c.deal(d),
Self::Confirm {
trustee,
complaints,
} => c.confirm(trustee, complaints),
Self::Justify {
dealer,
recipient,
share,
} => c.justify(dealer, recipient, share),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum StepError {
#[error("not a trustee step: {0}")]
NotAStep(String),
#[error("a step of another election")]
OtherElection,
#[error("its issuer is not the trustee the manifest names at index {0}")]
NotTheTrustee(u32),
#[error("its proof is not its issuer's")]
Unproven,
#[error("{0}")]
Malformed(String),
}
#[derive(Debug, Clone, Copy)]
pub struct Election<'a> {
pub community: &'a str,
pub election_id: &'a str,
pub trustees: &'a [&'a str],
}
fn type_uri<P: trust_tasks_rs::Payload>() -> String {
P::TYPE_URI.to_string()
}
fn parsed<P: serde::de::DeserializeOwned>(payload: &Value) -> Result<P, StepError> {
serde_json::from_value(payload.clone()).map_err(|e| StepError::Malformed(e.to_string()))
}
fn index(v: &Value) -> Result<u32, StepError> {
v.as_u64()
.and_then(|n| u32::try_from(n).ok())
.ok_or_else(|| StepError::Malformed("a trustee index".into()))
}
pub fn read(
request: &Value,
election: Election<'_>,
signer: &str,
) -> Result<TrusteeStep, StepError> {
let ty = request["type"].as_str().unwrap_or_default();
let payload = &request["payload"];
if request["issuer"].as_str() != Some(signer) {
return Err(StepError::Unproven);
}
if payload["community"].as_str() != Some(election.community)
|| payload["electionId"].as_str() != Some(election.election_id)
{
return Err(StepError::OtherElection);
}
let malformed = |e: crate::trustees::TrusteeError| StepError::Malformed(e.to_string());
let (trustee, step) = if ty == type_uri::<enrol::Payload>() {
let _: enrol::Payload = parsed(payload)?;
let tk = wire::transport_key(&payload["transportKey"]).map_err(malformed)?;
(
index(&payload["transportKey"]["trustee"])?,
TrusteeStep::Enrol(tk),
)
} else if ty == type_uri::<deal::Payload>() {
let _: deal::Payload = parsed(payload)?;
let d = wire::dealing(&payload["dealing"]).map_err(malformed)?;
(index(&payload["dealing"]["dealer"])?, TrusteeStep::Deal(d))
} else if ty == type_uri::<confirm::Payload>() {
let _: confirm::Payload = parsed(payload)?;
let trustee = index(&payload["trustee"])?;
let complaints = payload["complaints"]
.as_array()
.map_or(Ok(BTreeSet::new()), |a| a.iter().map(index).collect())?;
(
trustee,
TrusteeStep::Confirm {
trustee,
complaints,
},
)
} else if ty == type_uri::<justify::Payload>() {
let _: justify::Payload = parsed(payload)?;
let dealer = index(&payload["dealer"])?;
let share =
scalar_from_text(payload["share"].as_str().unwrap_or_default()).map_err(malformed)?;
(
dealer,
TrusteeStep::Justify {
dealer,
recipient: index(&payload["recipient"])?,
share,
},
)
} else {
return Err(StepError::NotAStep(ty.to_string()));
};
let named = trustee
.checked_sub(1)
.and_then(|i| election.trustees.get(i as usize));
if named != Some(&signer) {
return Err(StepError::NotTheTrustee(trustee));
}
Ok(step)
}
#[derive(Debug, Clone, Default)]
pub struct ProvenRequests {
signers: HashMap<[u8; 32], String>,
}
fn digest(request: &Value) -> Option<[u8; 32]> {
serde_json_canonicalizer::to_vec(request)
.ok()
.map(|b| Sha256::digest(b).into())
}
impl ProvenRequests {
pub async fn prove<F, Fut>(entries: &[Value], mut verify: F) -> Self
where
F: FnMut(Value) -> Fut,
Fut: Future<Output = Option<String>>,
{
let mut signers = HashMap::new();
for e in entries
.iter()
.filter(|e| e["kind"] == KIND || e["kind"] == "decryptionAuthorized")
{
let request = &e["request"];
if let (Some(d), Some(signer)) = (digest(request), verify(request.clone()).await) {
signers.insert(d, signer);
}
}
Self { signers }
}
pub async fn prove_requests<F, Fut>(requests: &[Value], verify: F) -> Self
where
F: FnMut(Value) -> Fut,
Fut: Future<Output = Option<String>>,
{
let entries: Vec<Value> = requests.iter().map(entry).collect();
Self::prove(&entries, verify).await
}
pub fn signer(&self, request: &Value) -> Option<&str> {
self.signers.get(&digest(request)?).map(String::as_str)
}
}
pub fn replay(
requests: &[Value],
election: Election<'_>,
threshold: crate::trustees::Threshold,
ctx: Vec<u8>,
proven: &ProvenRequests,
deadline_passed: bool,
) -> Result<Ceremony, String> {
let mut c = Ceremony::new(threshold, ctx);
for (i, r) in requests.iter().enumerate() {
let signer = proven
.signer(r)
.ok_or_else(|| format!("step {i}: {}", StepError::Unproven))?;
read(r, election, signer)
.map_err(|e| format!("step {i}: {e}"))?
.apply(&mut c)
.map_err(|e| format!("step {i}: {e}"))?;
}
if deadline_passed {
c.deadline();
}
Ok(c)
}