Skip to main content

rns_core/resource/
proof.rs

1use alloc::vec::Vec;
2
3use crate::hash::full_hash;
4use super::types::ResourceError;
5
6/// Compute resource hash: SHA-256(unencrypted_data + random_hash).
7/// `unencrypted_data` is the metadata-prefixed data BEFORE encryption.
8/// Returns full 32-byte hash.
9pub fn compute_resource_hash(unencrypted_data: &[u8], random_hash: &[u8]) -> [u8; 32] {
10    let mut input = Vec::with_capacity(unencrypted_data.len() + random_hash.len());
11    input.extend_from_slice(unencrypted_data);
12    input.extend_from_slice(random_hash);
13    full_hash(&input)
14}
15
16/// Compute expected proof: SHA-256(unencrypted_data + resource_hash).
17/// `unencrypted_data` is the same data used in compute_resource_hash.
18pub fn compute_expected_proof(unencrypted_data: &[u8], resource_hash: &[u8; 32]) -> [u8; 32] {
19    let mut input = Vec::with_capacity(unencrypted_data.len() + 32);
20    input.extend_from_slice(unencrypted_data);
21    input.extend_from_slice(resource_hash);
22    full_hash(&input)
23}
24
25/// Build proof data: [resource_hash: 32 bytes][proof: 32 bytes].
26pub fn build_proof_data(resource_hash: &[u8; 32], proof: &[u8; 32]) -> Vec<u8> {
27    let mut data = Vec::with_capacity(64);
28    data.extend_from_slice(resource_hash);
29    data.extend_from_slice(proof);
30    data
31}
32
33/// Validate proof data against expected proof.
34/// proof_data = [resource_hash: 32 bytes][proof: 32 bytes]
35///
36/// Only checks the proof portion (bytes 32..64) against the expected proof,
37/// matching Python's behavior which validates the proof hash, not the resource hash prefix.
38pub fn validate_proof(
39    proof_data: &[u8],
40    _expected_resource_hash: &[u8; 32],
41    expected_proof: &[u8; 32],
42) -> Result<bool, ResourceError> {
43    if proof_data.len() != 64 {
44        return Err(ResourceError::InvalidProof);
45    }
46    let recv_proof = &proof_data[32..64];
47    Ok(recv_proof == expected_proof.as_slice())
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53
54    #[test]
55    fn test_compute_resource_hash() {
56        let data = b"test resource data";
57        let random = [0xAA, 0xBB, 0xCC, 0xDD];
58        let hash = compute_resource_hash(data, &random);
59        assert_eq!(hash.len(), 32);
60
61        // Deterministic
62        let hash2 = compute_resource_hash(data, &random);
63        assert_eq!(hash, hash2);
64    }
65
66    #[test]
67    fn test_compute_expected_proof() {
68        let data = b"test resource data";
69        let random = [0xAA, 0xBB, 0xCC, 0xDD];
70        let resource_hash = compute_resource_hash(data, &random);
71        let proof = compute_expected_proof(data, &resource_hash);
72        assert_eq!(proof.len(), 32);
73        assert_ne!(proof, resource_hash); // proof != hash
74    }
75
76    #[test]
77    fn test_build_proof_data() {
78        let hash = [0x11u8; 32];
79        let proof = [0x22u8; 32];
80        let data = build_proof_data(&hash, &proof);
81        assert_eq!(data.len(), 64);
82        assert_eq!(&data[..32], &hash);
83        assert_eq!(&data[32..], &proof);
84    }
85
86    #[test]
87    fn test_validate_proof_valid() {
88        let data = b"resource data here";
89        let random = [0x11, 0x22, 0x33, 0x44];
90        let resource_hash = compute_resource_hash(data, &random);
91        let expected = compute_expected_proof(data, &resource_hash);
92        let proof_data = build_proof_data(&resource_hash, &expected);
93        assert_eq!(validate_proof(&proof_data, &resource_hash, &expected), Ok(true));
94    }
95
96    #[test]
97    fn test_validate_proof_wrong_hash_prefix_still_valid() {
98        // Python only checks the proof portion, not the resource_hash prefix
99        let data = b"resource data";
100        let random = [0x11; 4];
101        let resource_hash = compute_resource_hash(data, &random);
102        let expected = compute_expected_proof(data, &resource_hash);
103        let wrong_hash = [0xFF; 32];
104        let proof_data = build_proof_data(&wrong_hash, &expected);
105        // Proof is still valid because we only check bytes 32..64
106        assert_eq!(validate_proof(&proof_data, &resource_hash, &expected), Ok(true));
107    }
108
109    #[test]
110    fn test_validate_proof_invalid_proof() {
111        let data = b"resource data";
112        let random = [0x11; 4];
113        let resource_hash = compute_resource_hash(data, &random);
114        let expected = compute_expected_proof(data, &resource_hash);
115        let wrong_proof = [0xFF; 32];
116        let proof_data = build_proof_data(&resource_hash, &wrong_proof);
117        assert_eq!(validate_proof(&proof_data, &resource_hash, &expected), Ok(false));
118    }
119
120    #[test]
121    fn test_validate_proof_wrong_length() {
122        let hash = [0x11; 32];
123        let proof = [0x22; 32];
124        assert!(validate_proof(&[0; 50], &hash, &proof).is_err());
125        assert!(validate_proof(&[], &hash, &proof).is_err());
126    }
127
128    #[test]
129    fn test_proof_uses_unencrypted_data() {
130        // Verify that changing the data changes both hash and proof
131        let random = [0xAA; 4];
132        let hash1 = compute_resource_hash(b"data1", &random);
133        let hash2 = compute_resource_hash(b"data2", &random);
134        assert_ne!(hash1, hash2);
135
136        let proof1 = compute_expected_proof(b"data1", &hash1);
137        let proof2 = compute_expected_proof(b"data2", &hash2);
138        assert_ne!(proof1, proof2);
139    }
140}