1use crate::error::Result;
4use crate::hash::{hash_bytes, hash_file, hash_reader};
5use crate::keys::KeyPair;
6use crate::signature::DocumentSignature;
7use std::collections::HashMap;
8use std::io::Read;
9use std::path::Path;
10
11#[derive(Debug)]
13pub struct Signer<'a> {
14 keypair: &'a KeyPair,
15 signer_id: Option<String>,
16 metadata: HashMap<String, String>,
17}
18
19impl<'a> Signer<'a> {
20 pub fn new(keypair: &'a KeyPair) -> Self {
22 Self {
23 keypair,
24 signer_id: None,
25 metadata: HashMap::new(),
26 }
27 }
28
29 pub fn with_signer_id<S: Into<String>>(mut self, signer_id: S) -> Self {
31 self.signer_id = Some(signer_id.into());
32 self
33 }
34
35 pub fn with_metadata<K: Into<String>, V: Into<String>>(mut self, key: K, value: V) -> Self {
37 self.metadata.insert(key.into(), value.into());
38 self
39 }
40
41 pub fn sign_bytes(&self, data: &[u8]) -> Result<DocumentSignature> {
43 let hash = hash_bytes(data);
44 let mut doc_sig = DocumentSignature::new(hash);
45 doc_sig.add_signature_with_metadata(
46 self.keypair,
47 self.signer_id.clone(),
48 self.metadata.clone(),
49 )?;
50 Ok(doc_sig)
51 }
52
53 pub fn sign_file<P: AsRef<Path>>(&self, path: P) -> Result<DocumentSignature> {
55 let hash = hash_file(path)?;
56 let mut doc_sig = DocumentSignature::new(hash);
57 doc_sig.add_signature_with_metadata(
58 self.keypair,
59 self.signer_id.clone(),
60 self.metadata.clone(),
61 )?;
62 Ok(doc_sig)
63 }
64
65 pub fn sign_reader<R: Read>(&self, reader: &mut R) -> Result<DocumentSignature> {
67 let hash = hash_reader(reader)?;
68 let mut doc_sig = DocumentSignature::new(hash);
69 doc_sig.add_signature_with_metadata(
70 self.keypair,
71 self.signer_id.clone(),
72 self.metadata.clone(),
73 )?;
74 Ok(doc_sig)
75 }
76
77 pub fn cosign(&self, doc_sig: &mut DocumentSignature) -> Result<()> {
79 doc_sig.add_signature_with_metadata(
80 self.keypair,
81 self.signer_id.clone(),
82 self.metadata.clone(),
83 )
84 }
85}
86
87pub fn sign_bytes(keypair: &KeyPair, data: &[u8]) -> Result<DocumentSignature> {
89 Signer::new(keypair).sign_bytes(data)
90}
91
92pub fn sign_file<P: AsRef<Path>>(keypair: &KeyPair, path: P) -> Result<DocumentSignature> {
94 Signer::new(keypair).sign_file(path)
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100
101 #[test]
102 fn test_sign_bytes() {
103 let keypair = KeyPair::generate();
104 let data = b"Test document content";
105
106 let doc_sig = Signer::new(&keypair)
107 .with_signer_id("test@example.com")
108 .sign_bytes(data)
109 .unwrap();
110
111 assert_eq!(doc_sig.signature_count(), 1);
112 assert_eq!(
113 doc_sig.signatures[0].signer_id,
114 Some("test@example.com".to_string())
115 );
116 }
117
118 #[test]
119 fn test_sign_with_metadata() {
120 let keypair = KeyPair::generate();
121 let data = b"Test document content";
122
123 let doc_sig = Signer::new(&keypair)
124 .with_signer_id("test@example.com")
125 .with_metadata("purpose", "testing")
126 .with_metadata("version", "1.0")
127 .sign_bytes(data)
128 .unwrap();
129
130 assert_eq!(doc_sig.signatures[0].metadata.get("purpose"), Some(&"testing".to_string()));
131 assert_eq!(doc_sig.signatures[0].metadata.get("version"), Some(&"1.0".to_string()));
132 }
133
134 #[test]
135 fn test_cosign() {
136 let keypair1 = KeyPair::generate();
137 let keypair2 = KeyPair::generate();
138 let data = b"Test document content";
139
140 let mut doc_sig = Signer::new(&keypair1)
141 .with_signer_id("alice@example.com")
142 .sign_bytes(data)
143 .unwrap();
144
145 Signer::new(&keypair2)
146 .with_signer_id("bob@example.com")
147 .cosign(&mut doc_sig)
148 .unwrap();
149
150 assert_eq!(doc_sig.signature_count(), 2);
151 }
152
153 #[test]
154 fn test_convenience_functions() {
155 let keypair = KeyPair::generate();
156 let data = b"Test document content";
157
158 let doc_sig = sign_bytes(&keypair, data).unwrap();
159 assert_eq!(doc_sig.signature_count(), 1);
160 }
161}
162