Skip to main content

irontide_core/
hash_request.rs

1//! BEP 52 hash request types and validation.
2
3use crate::Id32;
4
5/// A request for Merkle tree hashes (BEP 52).
6///
7/// Represents a range of hashes at a specific layer of a file's Merkle tree.
8/// `base` = 0 is the leaf (block) layer, higher values are coarser layers.
9/// The piece layer is at `log2(piece_length / 16384)`.
10#[derive(Debug, Clone, PartialEq, Eq)]
11pub struct HashRequest {
12    /// SHA-256 root hash of the file's Merkle tree.
13    pub file_root: Id32,
14    /// Tree layer: 0 = block layer, `piece_layer` = `log2(blocks_per_piece)`.
15    pub base: u32,
16    /// Index of the first hash at the specified layer.
17    pub index: u32,
18    /// Number of hashes requested.
19    pub count: u32,
20    /// Number of uncle proof layers needed for verification.
21    pub proof_layers: u32,
22}
23
24/// Validate a hash request against file parameters.
25///
26/// Checks that the request is well-formed and within the file's bounds.
27/// `file_num_blocks` is the total number of 16 KiB blocks in the file.
28/// `file_num_pieces` is the total number of pieces in the file.
29#[must_use]
30pub fn validate_hash_request(
31    req: &HashRequest,
32    file_num_blocks: u32,
33    _file_num_pieces: u32,
34) -> bool {
35    if req.count == 0 || req.count > 8192 {
36        return false;
37    }
38
39    let num_leafs = file_num_blocks.next_power_of_two();
40    let num_layers = num_leafs.trailing_zeros() + 1;
41
42    if req.base >= num_layers {
43        return false;
44    }
45
46    // Number of nodes at the requested layer
47    let layer_size = num_leafs >> req.base;
48
49    if req.index >= layer_size || req.index + req.count > layer_size {
50        return false;
51    }
52
53    if req.proof_layers >= num_layers - req.base {
54        return false;
55    }
56
57    true
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63    use crate::Id32;
64
65    #[test]
66    fn valid_request_passes() {
67        let req = HashRequest {
68            file_root: Id32::ZERO,
69            base: 0,
70            index: 0,
71            count: 512,
72            proof_layers: 3,
73        };
74        assert!(validate_hash_request(&req, 8192, 512));
75    }
76
77    #[test]
78    fn reject_count_too_large() {
79        let req = HashRequest {
80            file_root: Id32::ZERO,
81            base: 0,
82            index: 0,
83            count: 8193, // max is 8192
84            proof_layers: 0,
85        };
86        assert!(!validate_hash_request(&req, 16384, 8192));
87    }
88
89    #[test]
90    fn reject_index_out_of_range() {
91        let req = HashRequest {
92            file_root: Id32::ZERO,
93            base: 0,
94            index: 100,
95            count: 10,
96            proof_layers: 0,
97        };
98        // file has 64 blocks, index 100 is out of range
99        assert!(!validate_hash_request(&req, 64, 4));
100    }
101
102    #[test]
103    fn reject_zero_count() {
104        let req = HashRequest {
105            file_root: Id32::ZERO,
106            base: 0,
107            index: 0,
108            count: 0,
109            proof_layers: 0,
110        };
111        assert!(!validate_hash_request(&req, 64, 4));
112    }
113
114    #[test]
115    fn piece_layer_request_valid() {
116        // piece_layer = log2(blocks_per_piece), e.g. 16 blocks/piece → base=4
117        let req = HashRequest {
118            file_root: Id32::ZERO,
119            base: 4, // piece layer for 16 blocks/piece
120            index: 0,
121            count: 32,
122            proof_layers: 2,
123        };
124        assert!(validate_hash_request(&req, 512, 32));
125    }
126}