Skip to main content

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}