use chacha20poly1305::{AeadCore, ChaCha20Poly1305, KeyInit};
use chacha20poly1305::aead::Aead;
use chacha20poly1305::aead::generic_array::GenericArray;
use rand_core::CryptoRngCore;
use serde::{Deserialize, Serialize};
use crate::choices::VotingChoice;
use crate::errors::PassringError::{InvalidPayload, SymmetricError};
use crate::Result;
#[allow(clippy::module_name_repetitions)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct Payload {
pub voting_id: uuid::Uuid,
pub encrypted: Vec<u8>,
pub nonce: Vec<u8>,
}
#[allow(clippy::module_name_repetitions)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct ClearPayload {
pub voting_id: uuid::Uuid,
pub choice: VotingChoice,
pub randomness: Vec<u8>,
}
impl Payload {
#[must_use]
pub fn new(voting_id: uuid::Uuid, encrypted: Vec<u8>, nonce: Vec<u8>) -> Self {
Payload {
voting_id,
encrypted,
nonce,
}
}
pub fn decrypt(&self, key: &[u8]) -> Result<ClearPayload> {
let Ok(cipher) = ChaCha20Poly1305::new_from_slice(key) else {
return Err(SymmetricError);
};
let nonce = GenericArray::clone_from_slice(&self.nonce);
let Ok(d) = cipher.decrypt(&nonce, self.encrypted.as_slice()) else {
return Err(SymmetricError);
};
match serde_json::from_slice::<ClearPayload>(&d) {
Ok(payload) => Ok(payload),
Err(_) => Err(InvalidPayload),
}
}
}
impl ClearPayload {
#[must_use]
pub fn new(voting_id: uuid::Uuid, choice: VotingChoice, randomness: Vec<u8>) -> Self {
ClearPayload {
voting_id,
choice,
randomness,
}
}
pub fn new_random(voting_id: uuid::Uuid, choice: VotingChoice, rng: &mut impl CryptoRngCore) -> Self {
let mut randomness = vec![0u8; 32];
rng.fill_bytes(&mut randomness);
ClearPayload::new(voting_id, choice, randomness)
}
pub fn encrypt<R: CryptoRngCore>(&self, key: &[u8], rng: &mut R) -> Result<Payload> {
let Ok(cipher) = ChaCha20Poly1305::new_from_slice(key) else {
return Err(SymmetricError);
};
let nonce = ChaCha20Poly1305::generate_nonce(rng);
let Ok(message) = serde_json::to_vec(&self) else {
return Err(InvalidPayload);
};
let Ok(e) = cipher.encrypt(&nonce, message.as_slice()) else {
return Err(SymmetricError);
};
Ok(Payload {
voting_id: self.voting_id,
encrypted: e,
nonce: nonce.to_vec(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand_core::OsRng;
use crate::choices::BasicVotingChoice;
#[test]
fn test_payload() {
let voting_id = uuid::Uuid::new_v4();
let choice = VotingChoice::Basic { choice: BasicVotingChoice::For };
let clear_payload = ClearPayload::new(voting_id, choice, vec![0u8; 32]);
let key = ChaCha20Poly1305::generate_key(&mut OsRng);
let payload = clear_payload.encrypt(&key, &mut OsRng).unwrap();
let decrypted = payload.decrypt(&key).unwrap();
assert_eq!(clear_payload, decrypted);
}
#[test]
fn test_payload_random() {
let voting_id = uuid::Uuid::new_v4();
let choice = VotingChoice::Basic { choice: BasicVotingChoice::For };
let clear_payload = ClearPayload::new_random(voting_id, choice, &mut OsRng);
let key = ChaCha20Poly1305::generate_key(&mut OsRng);
let payload = clear_payload.encrypt(&key, &mut OsRng).unwrap();
let decrypted = payload.decrypt(&key).unwrap();
assert_eq!(clear_payload, decrypted);
}
}