use crate::errors::PassringError::{
InvalidPayload, InvalidSignature, KeyNotFound, VotingIdMismatch,
};
use crate::key::{private::PrivateKey, public::PublicKey};
use crate::signature::FullSignature;
use crate::Result;
use nazgul::traits::{Link, Verify};
use nazgul::{blsag::BLSAG, traits::Sign};
use rand_core::{CryptoRng, RngCore};
use sha3::Keccak512;
use crate::payload::Payload;
pub struct Passring {
pub voting_id: uuid::Uuid,
pub ring: Vec<PublicKey>,
}
impl Passring {
#[must_use]
pub fn new(voting_id: uuid::Uuid, ring: Vec<PublicKey>) -> Self {
Passring { voting_id, ring }
}
pub fn issue<RNG: CryptoRng + RngCore + Default>(
&self,
payload: Payload,
private_key: PrivateKey,
) -> Result<FullSignature> {
let Ok(message) = serde_json::to_string(&payload) else {
return Err(InvalidPayload);
};
let my_public_key = PublicKey::from(private_key);
let Some(my_index) = self.ring.iter().position(|x| x == &my_public_key) else {
return Err(KeyNotFound);
};
let ring_copy = self.ring.clone();
let ring_copy: Vec<PublicKey> = ring_copy
.into_iter()
.filter(|x| x != &my_public_key)
.collect();
let blsag_signature = BLSAG::sign::<Keccak512, RNG>(
private_key.into(),
ring_copy.iter().map(|x| (*x).into()).collect(),
my_index,
&message.as_bytes().to_vec(),
);
Ok(FullSignature::from_blsag(&blsag_signature, payload))
}
pub fn verify(&self, full_signature: &FullSignature) -> Result<()> {
let blsag_signature = full_signature.to_blsag(&self.ring)?;
if full_signature.payload.voting_id != self.voting_id {
return Err(VotingIdMismatch);
}
let Ok(message) = serde_json::to_string(&full_signature.payload) else {
return Err(InvalidPayload);
};
if !BLSAG::verify::<Keccak512>(blsag_signature, &message.as_bytes().to_vec()) {
return Err(InvalidSignature);
}
Ok(())
}
pub fn validate(&self, full_signature: &FullSignature, key: &[u8]) -> Result<()> {
self.verify(full_signature)?;
let decrypted = full_signature.payload.decrypt(key)?;
if decrypted.voting_id != self.voting_id {
return Err(VotingIdMismatch);
}
if decrypted.voting_id != full_signature.payload.voting_id {
return Err(VotingIdMismatch);
}
Ok(())
}
pub fn link(
&self,
full_signature_1: &FullSignature,
full_signature_2: &FullSignature,
) -> Result<bool> {
let blsag_signature_1 = full_signature_1.to_blsag(&self.ring)?;
let blsag_signature_2 = full_signature_2.to_blsag(&self.ring)?;
Ok(BLSAG::link(blsag_signature_1, blsag_signature_2))
}
}
#[cfg(test)]
mod tests {
use rand_core::OsRng;
use crate::{Passring, PrivateKey, PublicKey};
use crate::choices::{BasicVotingChoice, VotingChoice};
use crate::payload::ClearPayload;
use crate::traits::Random;
#[test]
fn test_passring() {
let voting_id = uuid::Uuid::new_v4();
let choice = VotingChoice::Basic { choice: BasicVotingChoice::For };
let private_key = PrivateKey::random(&mut OsRng);
let public_key = PublicKey::from(private_key);
let ring: Vec<_> = (0..9).map(|_| PublicKey::random(&mut OsRng)).chain(std::iter::once(public_key)).collect();
let passring = Passring::new(voting_id, ring);
let clear_payload = ClearPayload::new_random(voting_id, choice, &mut OsRng);
let payload = clear_payload.encrypt(&[0u8; 32], &mut OsRng).unwrap();
let full_signature = passring.issue::<OsRng>(payload.clone(), private_key).expect("Failed to issue signature");
let full_signature2 = passring.issue::<OsRng>(payload, private_key).expect("Failed to issue signature");
passring.validate(&full_signature, &[0u8; 32]).expect("Failed to validate signature");
passring.validate(&full_signature2, &[0u8; 32]).expect("Failed to validate signature");
assert!(passring.link(&full_signature, &full_signature2).unwrap(), "Signatures signed by the same key should be linkable");
}
#[test]
fn test_linking() {
let voting_id = uuid::Uuid::new_v4();
let choice = VotingChoice::Basic { choice: BasicVotingChoice::For };
let private_key = PrivateKey::random(&mut OsRng);
let public_key = PublicKey::from(private_key);
let private_key2 = PrivateKey::random(&mut OsRng);
let public_key2 = PublicKey::from(private_key2);
let ring: Vec<_> = (0..8).map(|_| PublicKey::random(&mut OsRng)).chain(std::iter::once(public_key)).chain(std::iter::once(public_key2)).collect();
let passring = Passring::new(voting_id, ring);
let clear_payload = ClearPayload::new_random(voting_id, choice, &mut OsRng);
let payload = clear_payload.encrypt(&[0u8; 32], &mut OsRng).unwrap();
let full_signature = passring.issue::<OsRng>(payload.clone(), private_key).expect("Failed to issue signature");
passring.validate(&full_signature, &[0u8; 32]).expect("Failed to validate signature");
let full_signature2 = passring.issue::<OsRng>(payload.clone(), private_key).expect("Failed to issue signature");
passring.validate(&full_signature2, &[0u8; 32]).expect("Failed to validate signature");
let full_signature3 = passring.issue::<OsRng>(payload, private_key2).expect("Failed to issue signature");
passring.validate(&full_signature3, &[0u8; 32]).expect("Failed to validate signature");
assert!(passring.link(&full_signature, &full_signature2).unwrap(), "Signatures signed by the same key should be linkable");
assert!(!passring.link(&full_signature, &full_signature3).unwrap(), "Signatures signed by different keys should not be linkable");
}
}