use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
use k256::elliptic_curve::{sec1::ToEncodedPoint, PrimeField};
use k256::Scalar;
use serde::{Deserialize, Serialize};
use serde_cbor::Error as CborError;
use crate::types::KeyImage;
use crate::types::{BlsagSignatureBinary, RingSignatureBinary};
use std::convert::TryFrom;
const SIGNATURE_PREFIX: &str = "ring";
const SIGNATURE_VERSION: char = 'A';
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct SerializedSignatureData {
#[serde(rename = "v")]
variant: String,
#[serde(with = "serde_bytes")]
#[serde(rename = "c")]
c0: Vec<u8>,
#[serde(rename = "s")]
s: Vec<Vec<u8>>,
#[serde(rename = "i", skip_serializing_if = "Option::is_none", default)]
key_image: Option<Vec<u8>>,
}
#[derive(thiserror::Error, Debug, PartialEq)]
pub enum SerializationError {
#[error("CBOR serialization error: {0}")]
CborSerialization(String),
#[error("CBOR deserialization error: {0}")]
CborDeserialization(String),
#[error("Base64 decoding error: {0}")]
Base64Decoding(#[from] base64::DecodeError),
#[error("Invalid prefix: expected '{SIGNATURE_PREFIX}{SIGNATURE_VERSION}', got '{0}'")]
InvalidPrefix(String),
#[error("Invalid signature variant: '{0}'")]
InvalidVariant(String),
#[error("Invalid data length: {0}")]
InvalidLength(String),
#[error("Missing key image for BLSAG signature")]
MissingKeyImage,
#[error("Unexpected key image found for SAG signature")]
UnexpectedKeyImage,
#[error("SEC1 encoding error: {0}")]
Sec1Error(String),
#[error("Internal error: {0}")]
InternalError(String),
}
impl From<CborError> for SerializationError {
fn from(e: CborError) -> Self {
SerializationError::CborDeserialization(e.to_string()) }
}
impl From<k256::elliptic_curve::Error> for SerializationError {
fn from(e: k256::elliptic_curve::Error) -> Self {
SerializationError::Sec1Error(e.to_string())
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum CompactSignature {
Sag(RingSignatureBinary),
Blsag(BlsagSignatureBinary, KeyImage),
}
impl CompactSignature {
pub fn serialize(&self) -> Result<String, SerializationError> {
let data: SerializedSignatureData = self.try_into()?;
let cbor_bytes = serde_cbor::to_vec(&data)
.map_err(|e| SerializationError::CborSerialization(e.to_string()))?;
let base64_encoded = URL_SAFE_NO_PAD.encode(&cbor_bytes);
Ok(format!(
"{}{}{}",
SIGNATURE_PREFIX, SIGNATURE_VERSION, base64_encoded
))
}
pub fn deserialize(encoded: &str) -> Result<Self, SerializationError> {
let prefix = format!("{}{}", SIGNATURE_PREFIX, SIGNATURE_VERSION);
if !encoded.starts_with(&prefix) {
return Err(SerializationError::InvalidPrefix(
encoded
.split_at(prefix.len().min(encoded.len()))
.0
.to_string(),
));
}
let base64_data = &encoded[prefix.len()..];
let cbor_bytes = URL_SAFE_NO_PAD.decode(base64_data)?;
let data: SerializedSignatureData = serde_cbor::from_slice(&cbor_bytes)
.map_err(|e| SerializationError::CborDeserialization(e.to_string()))?;
Self::try_from(data)
}
pub fn variant(&self) -> &'static str {
match self {
CompactSignature::Sag(_) => "sag",
CompactSignature::Blsag(_, _) => "blsag",
}
}
}
impl TryFrom<&CompactSignature> for SerializedSignatureData {
type Error = SerializationError;
fn try_from(value: &CompactSignature) -> Result<Self, Self::Error> {
match value {
CompactSignature::Sag(sig) => {
if sig.s.is_empty() {
return Err(SerializationError::InternalError(
"Invalid signature format: empty s values".to_string(),
));
}
Ok(SerializedSignatureData {
variant: "sag".to_string(),
c0: sig.c0.to_bytes().to_vec(),
s: sig.s.iter().map(|s| s.to_bytes().to_vec()).collect(),
key_image: None,
})
}
CompactSignature::Blsag(sig, key_image) => {
if sig.s.is_empty() {
return Err(SerializationError::InternalError(
"Invalid signature format: empty s values".to_string(),
));
}
let key_image_bytes = key_image
.as_point()
.to_encoded_point(true)
.as_bytes()
.to_vec();
if key_image_bytes.len() != 33 {
return Err(SerializationError::InvalidLength(
"Key image must be 33 bytes".to_string(),
));
}
Ok(SerializedSignatureData {
variant: "blsag".to_string(),
c0: sig.c0.to_bytes().to_vec(),
s: sig.s.iter().map(|s| s.to_bytes().to_vec()).collect(),
key_image: Some(key_image_bytes),
})
}
}
}
}
impl TryFrom<SerializedSignatureData> for CompactSignature {
type Error = SerializationError;
fn try_from(data: SerializedSignatureData) -> Result<Self, Self::Error> {
if data.c0.len() != 32 {
return Err(SerializationError::InvalidLength(
"c0 must be 32 bytes".to_string(),
));
}
let c0_bytes: [u8; 32] = data
.c0
.try_into()
.map_err(|_| SerializationError::InvalidLength("c0 conversion failed".to_string()))?;
let c0_opt = Scalar::from_repr_vartime(c0_bytes.into());
if c0_opt.is_none() {
return Err(SerializationError::InternalError(
"Invalid scalar value for c0".into(),
));
}
let c0 = c0_opt.unwrap();
let mut s_scalars = Vec::with_capacity(data.s.len());
for s_bytes_vec in data.s {
if s_bytes_vec.len() != 32 {
return Err(SerializationError::InvalidLength(
"Each s must be 32 bytes".to_string(),
));
}
let s_bytes: [u8; 32] = s_bytes_vec.try_into().map_err(|_| {
SerializationError::InvalidLength("s conversion failed".to_string())
})?;
let s_opt = Scalar::from_repr_vartime(s_bytes.into());
if s_opt.is_none() {
return Err(SerializationError::InternalError(
"Invalid scalar value for s".into(),
));
}
s_scalars.push(s_opt.unwrap());
}
match data.variant.as_str() {
"sag" => {
if data.key_image.is_some() {
return Err(SerializationError::UnexpectedKeyImage);
}
let sig = RingSignatureBinary { c0, s: s_scalars };
Ok(CompactSignature::Sag(sig))
}
"blsag" => {
let key_image_bytes = data.key_image.ok_or(SerializationError::MissingKeyImage)?;
if key_image_bytes.len() != 33 {
return Err(SerializationError::InvalidLength(
"Key image must be 33 bytes".to_string(),
));
}
let key_image_point = match k256::PublicKey::from_sec1_bytes(&key_image_bytes) {
Ok(pk) => pk.to_projective(),
Err(e) => {
return Err(SerializationError::InternalError(format!(
"Invalid key image: {}",
e
)))
}
};
let sig = BlsagSignatureBinary { c0, s: s_scalars };
let key_image = KeyImage(key_image_point);
Ok(CompactSignature::Blsag(sig, key_image))
}
_ => Err(SerializationError::InvalidVariant(data.variant)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::blsag::sign_blsag_binary;
use crate::keys::generate_keypair_hex;
use crate::sag::sign_binary as sag_sign_binary;
use crate::types::hex_to_scalar;
use crate::utils::hex_to_point;
use k256::ProjectivePoint;
use rand::rngs::OsRng;
#[test]
fn test_sag_serialization_deserialization() {
let kp1 = generate_keypair_hex("compressed");
let kp2 = generate_keypair_hex("compressed");
let ring_hex = vec![kp1.public_key_hex, kp2.public_key_hex.clone()];
let ring_points: Vec<ProjectivePoint> =
ring_hex.iter().map(|h| hex_to_point(h).unwrap()).collect();
let signer_priv = hex_to_scalar(&kp2.private_key_hex).unwrap();
let message = b"test message for sag";
let original_sig = sag_sign_binary(message, &signer_priv, &ring_points, OsRng).unwrap();
let compact_sig = CompactSignature::Sag(original_sig.clone());
let serialized = compact_sig.serialize().unwrap();
println!("Serialized SAG: {}", serialized);
assert!(serialized.starts_with("ringA"));
let deserialized = CompactSignature::deserialize(&serialized).unwrap();
assert_eq!(compact_sig, deserialized);
match deserialized {
CompactSignature::Sag(deserialized_sig) => {
assert_eq!(original_sig.c0, deserialized_sig.c0);
assert_eq!(original_sig.s, deserialized_sig.s);
}
_ => panic!("Deserialized into wrong variant"),
}
}
#[test]
fn test_blsag_serialization_deserialization() {
let kp1 = generate_keypair_hex("compressed");
let kp2 = generate_keypair_hex("compressed");
let ring_hex = vec![kp1.public_key_hex, kp2.public_key_hex.clone()];
let ring_points: Vec<ProjectivePoint> =
ring_hex.iter().map(|h| hex_to_point(h).unwrap()).collect();
let signer_priv = hex_to_scalar(&kp2.private_key_hex).unwrap();
let message = b"test message for blsag";
let (original_sig, original_key_image) =
sign_blsag_binary(message, &signer_priv, &ring_points).unwrap();
let compact_sig = CompactSignature::Blsag(original_sig.clone(), original_key_image.clone());
let serialized = compact_sig.serialize().unwrap();
println!("Serialized BLSAG: {}", serialized);
assert!(serialized.starts_with("ringA"));
let deserialized = CompactSignature::deserialize(&serialized).unwrap();
assert_eq!(compact_sig, deserialized);
match deserialized {
CompactSignature::Blsag(deserialized_sig, deserialized_key_image) => {
assert_eq!(original_sig.c0, deserialized_sig.c0);
assert_eq!(original_sig.s, deserialized_sig.s);
assert_eq!(original_key_image, deserialized_key_image); }
_ => panic!("Deserialized into wrong variant"),
}
}
#[test]
fn test_invalid_prefix() {
let invalid_encoded = "rngA....";
let result = CompactSignature::deserialize(invalid_encoded);
assert!(matches!(result, Err(SerializationError::InvalidPrefix(_))));
}
#[test]
fn test_invalid_base64() {
let invalid_encoded = "ringA***";
let result = CompactSignature::deserialize(invalid_encoded);
assert!(matches!(result, Err(SerializationError::Base64Decoding(_))));
}
#[test]
fn test_invalid_cbor() {
let invalid_cbor_base64 = URL_SAFE_NO_PAD.encode(&[0x01, 0x02, 0x03]);
let invalid_encoded = format!("ringA{}", invalid_cbor_base64);
let result = CompactSignature::deserialize(&invalid_encoded);
assert!(matches!(
result,
Err(SerializationError::CborDeserialization(_))
));
}
#[test]
fn test_missing_key_image_blsag() {
let data = SerializedSignatureData {
variant: "blsag".to_string(),
c0: vec![0u8; 32],
s: vec![vec![0u8; 32]],
key_image: None, };
let cbor_bytes = serde_cbor::to_vec(&data).unwrap();
let base64_encoded = URL_SAFE_NO_PAD.encode(&cbor_bytes);
let encoded = format!("ringA{}", base64_encoded);
let result = CompactSignature::deserialize(&encoded);
assert_eq!(result, Err(SerializationError::MissingKeyImage));
}
#[test]
fn test_unexpected_key_image_sag() {
let data = SerializedSignatureData {
variant: "sag".to_string(),
c0: vec![0u8; 32],
s: vec![vec![0u8; 32]],
key_image: Some(vec![2u8; 33]), };
let cbor_bytes = serde_cbor::to_vec(&data).unwrap();
let base64_encoded = URL_SAFE_NO_PAD.encode(&cbor_bytes);
let encoded = format!("ringA{}", base64_encoded);
let result = CompactSignature::deserialize(&encoded);
assert_eq!(result, Err(SerializationError::UnexpectedKeyImage));
}
}