rust_sign/
signature.rs

1//! Signature data structures and JSON serialization.
2
3use crate::error::{Result, SignError};
4use crate::hash::DocumentHash;
5use crate::keys::{KeyPair, PublicKey};
6use chrono::{DateTime, Utc};
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9use std::fs;
10use std::path::Path;
11
12/// The current version of the signature format.
13pub const FORMAT_VERSION: &str = "1.0";
14
15/// A signed document containing one or more signatures.
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct DocumentSignature {
18    /// Format version for compatibility.
19    pub version: String,
20    
21    /// The BLAKE3 hash of the document (base64 encoded).
22    pub document_hash: String,
23    
24    /// List of signatures on this document.
25    pub signatures: Vec<SignatureEntry>,
26}
27
28/// A single signature entry with metadata.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct SignatureEntry {
31    /// Optional identifier for the signer (e.g., email, name).
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub signer_id: Option<String>,
34    
35    /// The public key that created this signature (base64 encoded).
36    pub public_key: String,
37    
38    /// The Ed25519 signature bytes (base64 encoded).
39    pub signature: String,
40    
41    /// Timestamp when the signature was created.
42    pub timestamp: DateTime<Utc>,
43    
44    /// Optional additional metadata.
45    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
46    pub metadata: HashMap<String, String>,
47}
48
49impl DocumentSignature {
50    /// Create a new document signature for the given hash.
51    pub fn new(document_hash: DocumentHash) -> Self {
52        Self {
53            version: FORMAT_VERSION.to_string(),
54            document_hash: document_hash.to_base64(),
55            signatures: Vec::new(),
56        }
57    }
58
59    /// Get the document hash.
60    pub fn get_hash(&self) -> Result<DocumentHash> {
61        DocumentHash::from_base64(&self.document_hash)
62    }
63
64    /// Add a signature to this document.
65    pub fn add_signature(
66        &mut self,
67        keypair: &KeyPair,
68        signer_id: Option<String>,
69    ) -> Result<()> {
70        self.add_signature_with_metadata(keypair, signer_id, HashMap::new())
71    }
72
73    /// Add a signature with custom metadata.
74    pub fn add_signature_with_metadata(
75        &mut self,
76        keypair: &KeyPair,
77        signer_id: Option<String>,
78        metadata: HashMap<String, String>,
79    ) -> Result<()> {
80        use base64::Engine;
81        let engine = base64::engine::general_purpose::STANDARD;
82
83        // Get the hash bytes
84        let hash = self.get_hash()?;
85        
86        // Sign the hash
87        let signature_bytes = keypair.sign(hash.as_bytes());
88        
89        let entry = SignatureEntry {
90            signer_id,
91            public_key: keypair.public_key().to_base64(),
92            signature: engine.encode(signature_bytes),
93            timestamp: Utc::now(),
94            metadata,
95        };
96        
97        self.signatures.push(entry);
98        Ok(())
99    }
100
101    /// Save the signature to a JSON file.
102    pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
103        let json = serde_json::to_string_pretty(self)?;
104        fs::write(path, json)?;
105        Ok(())
106    }
107
108    /// Load a signature from a JSON file.
109    pub fn load<P: AsRef<Path>>(path: P) -> Result<Self> {
110        let content = fs::read_to_string(path)?;
111        let sig: Self = serde_json::from_str(&content)?;
112        Ok(sig)
113    }
114
115    /// Parse a signature from a JSON string.
116    pub fn from_json(json: &str) -> Result<Self> {
117        let sig: Self = serde_json::from_str(json)?;
118        Ok(sig)
119    }
120
121    /// Serialize the signature to a JSON string.
122    pub fn to_json(&self) -> Result<String> {
123        let json = serde_json::to_string_pretty(self)?;
124        Ok(json)
125    }
126
127    /// Get the number of signatures.
128    pub fn signature_count(&self) -> usize {
129        self.signatures.len()
130    }
131
132    /// Check if there are any signatures.
133    pub fn has_signatures(&self) -> bool {
134        !self.signatures.is_empty()
135    }
136}
137
138impl SignatureEntry {
139    /// Get the public key from this entry.
140    pub fn get_public_key(&self) -> Result<PublicKey> {
141        PublicKey::from_base64(&self.public_key)
142    }
143
144    /// Get the signature bytes.
145    pub fn get_signature_bytes(&self) -> Result<[u8; 64]> {
146        use base64::Engine;
147        let engine = base64::engine::general_purpose::STANDARD;
148        
149        let bytes = engine.decode(&self.signature)?;
150        if bytes.len() != 64 {
151            return Err(SignError::InvalidFormat(format!(
152                "Invalid signature length: expected 64, got {}",
153                bytes.len()
154            )));
155        }
156        
157        let mut arr = [0u8; 64];
158        arr.copy_from_slice(&bytes);
159        Ok(arr)
160    }
161
162    /// Verify this signature against a document hash.
163    pub fn verify(&self, document_hash: &DocumentHash) -> Result<()> {
164        let public_key = self.get_public_key()?;
165        let signature = self.get_signature_bytes()?;
166        public_key.verify(document_hash.as_bytes(), &signature)
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173    use crate::hash::hash_bytes;
174
175    #[test]
176    fn test_create_and_sign() {
177        let keypair = KeyPair::generate();
178        let data = b"Test document content";
179        let hash = hash_bytes(data);
180        
181        let mut doc_sig = DocumentSignature::new(hash);
182        doc_sig.add_signature(&keypair, Some("alice@example.com".to_string())).unwrap();
183        
184        assert_eq!(doc_sig.signature_count(), 1);
185        assert!(doc_sig.has_signatures());
186    }
187
188    #[test]
189    fn test_multiple_signatures() {
190        let keypair1 = KeyPair::generate();
191        let keypair2 = KeyPair::generate();
192        let data = b"Test document content";
193        let hash = hash_bytes(data);
194        
195        let mut doc_sig = DocumentSignature::new(hash);
196        doc_sig.add_signature(&keypair1, Some("alice@example.com".to_string())).unwrap();
197        doc_sig.add_signature(&keypair2, Some("bob@example.com".to_string())).unwrap();
198        
199        assert_eq!(doc_sig.signature_count(), 2);
200    }
201
202    #[test]
203    fn test_json_roundtrip() {
204        let keypair = KeyPair::generate();
205        let data = b"Test document content";
206        let hash = hash_bytes(data);
207        
208        let mut doc_sig = DocumentSignature::new(hash);
209        doc_sig.add_signature(&keypair, Some("alice@example.com".to_string())).unwrap();
210        
211        let json = doc_sig.to_json().unwrap();
212        let restored = DocumentSignature::from_json(&json).unwrap();
213        
214        assert_eq!(doc_sig.document_hash, restored.document_hash);
215        assert_eq!(doc_sig.signature_count(), restored.signature_count());
216    }
217
218    #[test]
219    fn test_verify_signature() {
220        let keypair = KeyPair::generate();
221        let data = b"Test document content";
222        let hash = hash_bytes(data);
223        
224        let mut doc_sig = DocumentSignature::new(hash.clone());
225        doc_sig.add_signature(&keypair, None).unwrap();
226        
227        // Verification should succeed
228        let result = doc_sig.signatures[0].verify(&hash);
229        assert!(result.is_ok());
230        
231        // Verification with wrong hash should fail
232        let wrong_hash = hash_bytes(b"Different content");
233        let result = doc_sig.signatures[0].verify(&wrong_hash);
234        assert!(result.is_err());
235    }
236}
237