Skip to main content

rns_core/resource/
proof.rs

1use alloc::vec::Vec;
2
3use super::types::ResourceError;
4use crate::hash::full_hash;
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!(
94            validate_proof(&proof_data, &resource_hash, &expected),
95            Ok(true)
96        );
97    }
98
99    #[test]
100    fn test_validate_proof_wrong_hash_prefix_still_valid() {
101        // Python only checks the proof portion, not the resource_hash prefix
102        let data = b"resource data";
103        let random = [0x11; 4];
104        let resource_hash = compute_resource_hash(data, &random);
105        let expected = compute_expected_proof(data, &resource_hash);
106        let wrong_hash = [0xFF; 32];
107        let proof_data = build_proof_data(&wrong_hash, &expected);
108        // Proof is still valid because we only check bytes 32..64
109        assert_eq!(
110            validate_proof(&proof_data, &resource_hash, &expected),
111            Ok(true)
112        );
113    }
114
115    #[test]
116    fn test_validate_proof_invalid_proof() {
117        let data = b"resource data";
118        let random = [0x11; 4];
119        let resource_hash = compute_resource_hash(data, &random);
120        let expected = compute_expected_proof(data, &resource_hash);
121        let wrong_proof = [0xFF; 32];
122        let proof_data = build_proof_data(&resource_hash, &wrong_proof);
123        assert_eq!(
124            validate_proof(&proof_data, &resource_hash, &expected),
125            Ok(false)
126        );
127    }
128
129    #[test]
130    fn test_validate_proof_wrong_length() {
131        let hash = [0x11; 32];
132        let proof = [0x22; 32];
133        assert!(validate_proof(&[0; 50], &hash, &proof).is_err());
134        assert!(validate_proof(&[], &hash, &proof).is_err());
135    }
136
137    #[test]
138    fn test_proof_uses_unencrypted_data() {
139        // Verify that changing the data changes both hash and proof
140        let random = [0xAA; 4];
141        let hash1 = compute_resource_hash(b"data1", &random);
142        let hash2 = compute_resource_hash(b"data2", &random);
143        assert_ne!(hash1, hash2);
144
145        let proof1 = compute_expected_proof(b"data1", &hash1);
146        let proof2 = compute_expected_proof(b"data2", &hash2);
147        assert_ne!(proof1, proof2);
148    }
149}