use crate::errors::PassringError::{MalformedSignature};
use crate::payload::{Payload};
use crate::{PublicKey, Result};
use curve25519_dalek::{ristretto::CompressedRistretto, Scalar};
use nazgul::blsag::BLSAG;
#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct FullSignature {
#[cfg_attr(feature = "serde", serde(with = "hex::serde"))]
pub challenge: Vec<u8>,
#[cfg_attr(feature = "serde", serde(with = "hex::serde"))]
pub responses: Vec<u8>,
#[cfg_attr(feature = "serde", serde(with = "hex::serde"))]
pub key_image: Vec<u8>,
pub payload: Payload,
}
impl FullSignature {
pub fn from_blsag(blsag: &BLSAG, payload: Payload) -> Self {
let responses: Vec<[u8; 32]> = blsag.responses.iter().map(Scalar::to_bytes).collect();
let responses: Vec<u8> = responses.iter().flat_map(|x| x.iter()).copied().collect();
FullSignature {
challenge: blsag.challenge.to_bytes().to_vec(),
responses,
key_image: blsag.key_image.compress().to_bytes().to_vec(),
payload,
}
}
pub fn to_blsag(&self, ring: &[PublicKey]) -> Result<BLSAG> {
let Ok(challenge_bytes) = self.challenge.as_slice().try_into() else {
return Err(MalformedSignature);
};
let Some(challenge) = Scalar::from_canonical_bytes(challenge_bytes).into() else {
return Err(MalformedSignature);
};
if self.responses.len() % 32 != 0 {
return Err(MalformedSignature);
}
let mut responses_unwrap_err = false;
let responses: Vec<Scalar> = self
.responses
.clone()
.chunks_exact(32)
.map(|x| {
let bytes = if let Ok(b) = x.try_into() {
b
} else {
responses_unwrap_err = true;
[0u8; 32]
};
if let Some(s) = Scalar::from_canonical_bytes(bytes).into() {
s
} else {
responses_unwrap_err = true;
Scalar::default()
}
})
.collect();
if responses_unwrap_err {
return Err(MalformedSignature);
}
let Ok(key_image_uncompressed) = CompressedRistretto::from_slice(&self.key_image) else {
return Err(MalformedSignature);
};
let Some(key_image) = key_image_uncompressed.decompress() else {
return Err(MalformedSignature);
};
Ok(BLSAG {
challenge,
responses,
ring: ring.iter().map(|x| (*x).into()).collect(),
key_image,
})
}
}
#[cfg(test)]
mod tests {
use curve25519_dalek::RistrettoPoint;
use nazgul::traits::Sign;
use super::*;
use rand_core::OsRng;
use sha3::Keccak512;
use crate::choices::{BasicVotingChoice, VotingChoice};
use crate::payload::ClearPayload;
use crate::PrivateKey;
#[test]
fn test_full_signature() {
let private_key = Scalar::random(&mut OsRng);
let ring: Vec<_> = (0..9).map(|_| RistrettoPoint::random(&mut OsRng)).collect();
let choice = VotingChoice::Basic { choice: BasicVotingChoice::For };
let clear_payload = ClearPayload::new_random(uuid::Uuid::new_v4(), choice, &mut OsRng);
let payload = clear_payload.encrypt(&[0u8; 32], &mut OsRng).unwrap();
let blsag_signature = BLSAG::sign::<Keccak512, OsRng>(
private_key,
ring.clone(),
0,
&serde_json::to_vec(&payload).unwrap(),
);
let full_signature = FullSignature::from_blsag(&blsag_signature, payload.clone());
let mut ring2: Vec<PublicKey> = ring.iter().map(|x| PublicKey::from(*x)).collect();
let my_public_key = PublicKey::from(PrivateKey::from(private_key));
let my_index = 0;
ring2.insert(my_index, my_public_key);
let blsag_signature_2 = full_signature.to_blsag(&ring2).unwrap();
assert_eq!(blsag_signature.challenge, blsag_signature_2.challenge, "Challenge mismatch");
assert_eq!(blsag_signature.responses, blsag_signature_2.responses, "Responses mismatch");
assert_eq!(blsag_signature.key_image, blsag_signature_2.key_image, "Key image mismatch");
assert_eq!(blsag_signature.ring, blsag_signature_2.ring, "Ring mismatch");
}
}