use crate::error::{Result, SignError};
use crate::hash::DocumentHash;
use crate::keys::{KeyPair, PublicKey};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::Path;
pub const FORMAT_VERSION: &str = "1.0";
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocumentSignature {
pub version: String,
pub document_hash: String,
pub signatures: Vec<SignatureEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SignatureEntry {
#[serde(skip_serializing_if = "Option::is_none")]
pub signer_id: Option<String>,
pub public_key: String,
pub signature: String,
pub timestamp: DateTime<Utc>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub metadata: HashMap<String, String>,
}
impl DocumentSignature {
pub fn new(document_hash: DocumentHash) -> Self {
Self {
version: FORMAT_VERSION.to_string(),
document_hash: document_hash.to_base64(),
signatures: Vec::new(),
}
}
pub fn get_hash(&self) -> Result<DocumentHash> {
DocumentHash::from_base64(&self.document_hash)
}
pub fn add_signature(
&mut self,
keypair: &KeyPair,
signer_id: Option<String>,
) -> Result<()> {
self.add_signature_with_metadata(keypair, signer_id, HashMap::new())
}
pub fn add_signature_with_metadata(
&mut self,
keypair: &KeyPair,
signer_id: Option<String>,
metadata: HashMap<String, String>,
) -> Result<()> {
use base64::Engine;
let engine = base64::engine::general_purpose::STANDARD;
let hash = self.get_hash()?;
let signature_bytes = keypair.sign(hash.as_bytes());
let entry = SignatureEntry {
signer_id,
public_key: keypair.public_key().to_base64(),
signature: engine.encode(signature_bytes),
timestamp: Utc::now(),
metadata,
};
self.signatures.push(entry);
Ok(())
}
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let json = serde_json::to_string_pretty(self)?;
fs::write(path, json)?;
Ok(())
}
pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
let content = fs::read_to_string(path)?;
let sig: Self = serde_json::from_str(&content)?;
Ok(sig)
}
pub fn from_json(json: &str) -> Result<Self> {
let sig: Self = serde_json::from_str(json)?;
Ok(sig)
}
pub fn to_json(&self) -> Result<String> {
let json = serde_json::to_string_pretty(self)?;
Ok(json)
}
pub fn signature_count(&self) -> usize {
self.signatures.len()
}
pub fn has_signatures(&self) -> bool {
!self.signatures.is_empty()
}
}
impl SignatureEntry {
pub fn get_public_key(&self) -> Result<PublicKey> {
PublicKey::from_base64(&self.public_key)
}
pub fn get_signature_bytes(&self) -> Result<[u8; 64]> {
use base64::Engine;
let engine = base64::engine::general_purpose::STANDARD;
let bytes = engine.decode(&self.signature)?;
if bytes.len() != 64 {
return Err(SignError::InvalidFormat(format!(
"Invalid signature length: expected 64, got {}",
bytes.len()
)));
}
let mut arr = [0u8; 64];
arr.copy_from_slice(&bytes);
Ok(arr)
}
pub fn verify(&self, document_hash: &DocumentHash) -> Result<()> {
let public_key = self.get_public_key()?;
let signature = self.get_signature_bytes()?;
public_key.verify(document_hash.as_bytes(), &signature)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hash::hash_bytes;
#[test]
fn test_create_and_sign() {
let keypair = KeyPair::generate();
let data = b"Test document content";
let hash = hash_bytes(data);
let mut doc_sig = DocumentSignature::new(hash);
doc_sig.add_signature(&keypair, Some("alice@example.com".to_string())).unwrap();
assert_eq!(doc_sig.signature_count(), 1);
assert!(doc_sig.has_signatures());
}
#[test]
fn test_multiple_signatures() {
let keypair1 = KeyPair::generate();
let keypair2 = KeyPair::generate();
let data = b"Test document content";
let hash = hash_bytes(data);
let mut doc_sig = DocumentSignature::new(hash);
doc_sig.add_signature(&keypair1, Some("alice@example.com".to_string())).unwrap();
doc_sig.add_signature(&keypair2, Some("bob@example.com".to_string())).unwrap();
assert_eq!(doc_sig.signature_count(), 2);
}
#[test]
fn test_json_roundtrip() {
let keypair = KeyPair::generate();
let data = b"Test document content";
let hash = hash_bytes(data);
let mut doc_sig = DocumentSignature::new(hash);
doc_sig.add_signature(&keypair, Some("alice@example.com".to_string())).unwrap();
let json = doc_sig.to_json().unwrap();
let restored = DocumentSignature::from_json(&json).unwrap();
assert_eq!(doc_sig.document_hash, restored.document_hash);
assert_eq!(doc_sig.signature_count(), restored.signature_count());
}
#[test]
fn test_verify_signature() {
let keypair = KeyPair::generate();
let data = b"Test document content";
let hash = hash_bytes(data);
let mut doc_sig = DocumentSignature::new(hash.clone());
doc_sig.add_signature(&keypair, None).unwrap();
let result = doc_sig.signatures[0].verify(&hash);
assert!(result.is_ok());
let wrong_hash = hash_bytes(b"Different content");
let result = doc_sig.signatures[0].verify(&wrong_hash);
assert!(result.is_err());
}
}