entrenar/cli/commands/research/
verify.rs1use crate::cli::logging::log;
4use crate::cli::LogLevel;
5use crate::config::VerifyArgs;
6use crate::research::{PreRegistration, SignedPreRegistration, TimestampProof};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum SignatureStatus {
11 Valid,
13 Invalid,
15 Error(String),
17}
18
19pub fn verify_signature(signed: &SignedPreRegistration) -> SignatureStatus {
21 match signed.verify() {
22 Ok(true) => SignatureStatus::Valid,
23 Ok(false) => SignatureStatus::Invalid,
24 Err(e) => SignatureStatus::Error(e.to_string()),
25 }
26}
27
28pub fn log_git_proof(level: LogLevel, proof: &TimestampProof) {
30 if proof.is_git() {
31 log(level, LogLevel::Normal, "Git timestamp proof: GitCommit");
32 if let Some(commit) = proof.git_commit() {
33 log(level, LogLevel::Verbose, &format!(" Commit: {commit}"));
34 }
35 } else {
36 log(level, LogLevel::Normal, "Timestamp proof is not a git commit");
37 }
38}
39
40pub fn verify_signed_content(
42 signed: &SignedPreRegistration,
43 verify_git: bool,
44 level: LogLevel,
45) -> Result<(), String> {
46 match verify_signature(signed) {
47 SignatureStatus::Valid => {
48 log(level, LogLevel::Normal, "Signature verification: VALID");
49 }
50 SignatureStatus::Invalid => {
51 log(level, LogLevel::Normal, "Signature verification: INVALID");
52 return Err("Signature verification failed".to_string());
53 }
54 SignatureStatus::Error(e) => {
55 return Err(format!("Verification error: {e}"));
56 }
57 }
58
59 if verify_git {
60 if let Some(proof) = &signed.timestamp_proof {
61 log_git_proof(level, proof);
62 } else {
63 log(level, LogLevel::Normal, "No git timestamp proof found");
64 }
65 }
66
67 log(level, LogLevel::Normal, "Pre-registration verified successfully");
68 Ok(())
69}
70
71pub fn compute_commitment(prereg: &PreRegistration, level: LogLevel) {
73 let commitment = prereg.commit();
74 log(level, LogLevel::Normal, &format!("Computed commitment: {}...", &commitment.hash[..32]));
75}
76
77pub fn run_research_verify(args: VerifyArgs, level: LogLevel) -> Result<(), String> {
78 log(level, LogLevel::Normal, &format!("Verifying: {}", args.file.display()));
79
80 let content =
81 std::fs::read_to_string(&args.file).map_err(|e| format!("Failed to read file: {e}"))?;
82
83 if let Ok(signed) = serde_yaml::from_str::<SignedPreRegistration>(&content) {
84 return verify_signed_content(&signed, args.verify_git, level);
85 }
86
87 log(level, LogLevel::Normal, "File does not contain a signed pre-registration");
88
89 if let Some(original_path) = &args.original {
90 let original_content = std::fs::read_to_string(original_path)
91 .map_err(|e| format!("Failed to read original: {e}"))?;
92
93 let prereg: PreRegistration = serde_yaml::from_str(&original_content)
94 .map_err(|e| format!("Failed to parse original: {e}"))?;
95
96 compute_commitment(&prereg, level);
97 }
98
99 Ok(())
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn test_signature_status_eq() {
108 assert_eq!(SignatureStatus::Valid, SignatureStatus::Valid);
109 assert_eq!(SignatureStatus::Invalid, SignatureStatus::Invalid);
110 assert_ne!(SignatureStatus::Valid, SignatureStatus::Invalid);
111 }
112
113 #[test]
114 fn test_signature_status_error() {
115 let err = SignatureStatus::Error("test error".to_string());
116 assert_eq!(err, SignatureStatus::Error("test error".to_string()));
117 assert_ne!(err, SignatureStatus::Valid);
118 }
119
120 #[test]
121 fn test_signature_status_debug() {
122 let valid = SignatureStatus::Valid;
123 assert!(format!("{valid:?}").contains("Valid"));
124 }
125
126 #[test]
127 fn test_signature_status_clone() {
128 let orig = SignatureStatus::Error("test".to_string());
129 let cloned = orig.clone();
130 assert_eq!(orig, cloned);
131 }
132
133 #[test]
134 fn test_log_git_proof_with_git_commit() {
135 let proof = TimestampProof::GitCommit("abc123def456".to_string());
136 log_git_proof(LogLevel::Quiet, &proof);
138 log_git_proof(LogLevel::Verbose, &proof);
139 }
140
141 #[test]
142 fn test_log_git_proof_with_non_git() {
143 let proof = TimestampProof::Rfc3161(vec![1, 2, 3]);
144 log_git_proof(LogLevel::Quiet, &proof);
146 }
147
148 #[test]
149 fn test_log_git_proof_opentimestamps() {
150 let proof = TimestampProof::OpenTimestamps(vec![4, 5, 6]);
151 log_git_proof(LogLevel::Normal, &proof);
153 }
154
155 #[test]
156 fn test_compute_commitment() {
157 let prereg =
158 PreRegistration::new("Test Title", "Test Hypothesis", "Test Methods", "Test Analysis");
159 compute_commitment(&prereg, LogLevel::Quiet);
161 }
162
163 #[test]
164 fn test_verify_signature_with_valid_signed() {
165 use ed25519_dalek::SigningKey;
166 let prereg =
167 PreRegistration::new("Test Title", "Test Hypothesis", "Test Methods", "Test Analysis");
168 let signing_key = SigningKey::from_bytes(&[1u8; 32]);
169 let signed = SignedPreRegistration::sign(&prereg, &signing_key);
170 assert_eq!(verify_signature(&signed), SignatureStatus::Valid);
171 }
172
173 #[test]
174 fn test_verify_signature_with_invalid_signature() {
175 let prereg =
176 PreRegistration::new("Test Title", "Test Hypothesis", "Test Methods", "Test Analysis");
177 let commitment = prereg.commit();
178 let signed = SignedPreRegistration {
180 registration: prereg,
181 commitment,
182 signature: "0".repeat(128), public_key: "0".repeat(64), timestamp_proof: None,
185 };
186 let status = verify_signature(&signed);
188 assert!(matches!(status, SignatureStatus::Invalid | SignatureStatus::Error(_)));
189 }
190
191 #[test]
192 fn test_verify_signature_with_malformed_key() {
193 let prereg =
194 PreRegistration::new("Test Title", "Test Hypothesis", "Test Methods", "Test Analysis");
195 let commitment = prereg.commit();
196 let signed = SignedPreRegistration {
198 registration: prereg,
199 commitment,
200 signature: "not-hex".to_string(),
201 public_key: "also-not-hex".to_string(),
202 timestamp_proof: None,
203 };
204 let status = verify_signature(&signed);
205 assert!(matches!(status, SignatureStatus::Error(_)));
206 }
207
208 #[test]
209 fn test_verify_signed_content_valid() {
210 use ed25519_dalek::SigningKey;
211 let prereg =
212 PreRegistration::new("Test Title", "Test Hypothesis", "Test Methods", "Test Analysis");
213 let signing_key = SigningKey::from_bytes(&[2u8; 32]);
214 let signed = SignedPreRegistration::sign(&prereg, &signing_key);
215 let result = verify_signed_content(&signed, false, LogLevel::Quiet);
216 assert!(result.is_ok());
217 }
218
219 #[test]
220 fn test_verify_signed_content_invalid() {
221 let prereg =
222 PreRegistration::new("Test Title", "Test Hypothesis", "Test Methods", "Test Analysis");
223 let commitment = prereg.commit();
224 let signed = SignedPreRegistration {
225 registration: prereg,
226 commitment,
227 signature: "0".repeat(128),
228 public_key: "0".repeat(64),
229 timestamp_proof: None,
230 };
231 let result = verify_signed_content(&signed, false, LogLevel::Quiet);
232 assert!(result.is_err());
233 }
234
235 #[test]
236 fn test_verify_signed_content_with_git_proof() {
237 use ed25519_dalek::SigningKey;
238 let prereg =
239 PreRegistration::new("Test Title", "Test Hypothesis", "Test Methods", "Test Analysis");
240 let signing_key = SigningKey::from_bytes(&[3u8; 32]);
241 let mut signed = SignedPreRegistration::sign(&prereg, &signing_key);
242 signed.timestamp_proof = Some(TimestampProof::GitCommit("abc123".to_string()));
243 let result = verify_signed_content(&signed, true, LogLevel::Quiet);
244 assert!(result.is_ok());
245 }
246
247 #[test]
248 fn test_verify_signed_content_no_git_proof() {
249 use ed25519_dalek::SigningKey;
250 let prereg =
251 PreRegistration::new("Test Title", "Test Hypothesis", "Test Methods", "Test Analysis");
252 let signing_key = SigningKey::from_bytes(&[4u8; 32]);
253 let signed = SignedPreRegistration::sign(&prereg, &signing_key);
254 let result = verify_signed_content(&signed, true, LogLevel::Quiet);
256 assert!(result.is_ok());
257 }
258
259 #[test]
260 fn test_verify_signed_content_error_propagation() {
261 let prereg =
262 PreRegistration::new("Test Title", "Test Hypothesis", "Test Methods", "Test Analysis");
263 let commitment = prereg.commit();
264 let signed = SignedPreRegistration {
265 registration: prereg,
266 commitment,
267 signature: "invalid".to_string(),
268 public_key: "invalid".to_string(),
269 timestamp_proof: None,
270 };
271 let result = verify_signed_content(&signed, false, LogLevel::Quiet);
272 assert!(result.is_err());
273 assert!(result.unwrap_err().contains("error"));
274 }
275}