ironshield_core/verify.rs
1//! Verification functions for IronShield proof-of-work solutions.
2//!
3//! This module contains functions for verifying that proposed solutions
4//! satisfy the proof-of-work requirements for both legacy string-based
5//! challenges and the new IronShieldChallenge struct-based challenges.
6
7use hex;
8use sha2::{Digest, Sha256};
9use ironshield_types::*;
10
11/// Verify that an `IronShieldChallengeResponse` contains
12/// a valid solution.
13///
14/// This function uses the same optimized hashing approach
15/// as `find_solution_single_threaded`. It extracts the
16/// challenge and solution from the response and verifies
17/// that the solution is valid for the challenge.
18///
19/// # Arguments
20/// * `response`: The `IronShieldChallengeResponse` containing
21/// both the challenge and solution
22///
23/// # Returns
24/// * `bool`: `true` if the solution produces a hash less than
25/// the `challenge_param`, `false` if the solution
26/// is invalid or doesn't meet the requirements.
27///
28/// # Example
29/// ```
30/// use ironshield_core::{find_solution, verify_ironshield_solution, IronShieldChallenge, SigningKey};
31///
32/// let dummy_key = SigningKey::from_bytes(&[0u8; 32]);
33/// let challenge = IronShieldChallenge::new(
34/// "test_website".to_string(),
35/// 1, // Easy difficulty
36/// dummy_key,
37/// [0x00; 32],
38/// );
39///
40/// let response = find_solution(&challenge, None, None, None, None).unwrap();
41/// assert!(verify_ironshield_solution(&response));
42/// ```
43pub fn verify_ironshield_solution(response: &IronShieldChallengeResponse) -> bool {
44 let challenge: &IronShieldChallenge = &response.solved_challenge;
45 let nonce: i64 = response.solution;
46
47 // Parse the random_nonce from hex string to bytes
48 let random_nonce_bytes: Vec<u8> = match hex::decode(&challenge.random_nonce) {
49 Ok(bytes) => bytes,
50 Err(_) => return false, // Invalid hex string
51 };
52
53 // Convert nonce to little-endian bytes
54 let nonce_bytes: [u8; 8] = nonce.to_le_bytes();
55
56 // Use the same optimized hashing approach as the main function
57 let mut hasher = Sha256::new();
58 hasher.update(&random_nonce_bytes); // First part of the input
59 hasher.update(&nonce_bytes); // Second part of the input
60 let hash_result = hasher.finalize();
61
62 // Compare with the challenge parameter
63 hash_result.as_slice() < &challenge.challenge_param
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69
70 #[test]
71 fn test_verify_ironshield_solution() {
72 // Create a challenge with reasonable threshold
73 let dummy_key = SigningKey::from_bytes(&[0u8; 32]);
74 let challenge: IronShieldChallenge = IronShieldChallenge::new(
75 "test_website".to_string(),
76 2, // Medium threshold
77 dummy_key,
78 [0x00; 32],
79 );
80
81 // Find a solution using the solver
82 let result = crate::solve::find_solution(&challenge, None, None, None, None);
83 assert!(result.is_ok(), "Should find solution for reasonable challenge");
84
85 let response = result.unwrap();
86
87 // Verify using our verification function
88 assert!(verify_ironshield_solution(&response),
89 "Verification function should confirm the solution is valid");
90 }
91
92 #[test]
93 fn test_verify_ironshield_solution_edge_cases() {
94 // Test with very easy challenge (all 0xFF)
95 let dummy_key = SigningKey::from_bytes(&[0u8; 32]);
96 let easy_challenge = IronShieldChallenge::new(
97 "test_website".to_string(),
98 1, // Very easy
99 dummy_key.clone(),
100 [0x00; 32],
101 );
102
103 // Create responses with different nonces - almost any nonce should work for this challenge
104 let response1 = IronShieldChallengeResponse::new(easy_challenge.clone(), 0);
105 let response2 = IronShieldChallengeResponse::new(easy_challenge.clone(), 1);
106 let response3 = IronShieldChallengeResponse::new(easy_challenge.clone(), 12345);
107
108 assert!(verify_ironshield_solution(&response1));
109 assert!(verify_ironshield_solution(&response2));
110 assert!(verify_ironshield_solution(&response3));
111
112 // Test with impossible challenge (all 0x00)
113 let impossible_challenge = IronShieldChallenge::new(
114 "test_website".to_string(),
115 u64::MAX, // Impossible
116 dummy_key,
117 [0x00; 32],
118 );
119
120 // Create responses - no nonce should work for this challenge
121 let impossible_response1 = IronShieldChallengeResponse::new(impossible_challenge.clone(), 0);
122 let impossible_response2 = IronShieldChallengeResponse::new(impossible_challenge.clone(), 1);
123 let impossible_response3 = IronShieldChallengeResponse::new(impossible_challenge, 12345);
124
125 assert!(!verify_ironshield_solution(&impossible_response1));
126 assert!(!verify_ironshield_solution(&impossible_response2));
127 assert!(!verify_ironshield_solution(&impossible_response3));
128 }
129}