Skip to main content

blvm_protocol/
bip157.rs

1//! BIP157: Client-Side Block Filtering Network Protocol
2//!
3//! Specification: https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki
4//!
5//! Defines network messages for requesting and serving compact block filters.
6//! Enables efficient transaction discovery for light clients.
7
8use super::bip158::CompactBlockFilter;
9use crate::Hash;
10use sha2::{Digest, Sha256};
11
12/// Filter header - commits to previous filter header and current filter
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct FilterHeader {
15    /// Filter hash (double SHA256 of filter data)
16    pub filter_hash: Hash,
17    /// Previous filter header hash
18    pub prev_header_hash: Hash,
19}
20
21/// Double SHA256 of `data`.
22pub fn hash256(data: &[u8]) -> Hash {
23    let mut hasher = Sha256::new();
24    hasher.update(data);
25    let first = hasher.finalize();
26
27    let mut hasher = Sha256::new();
28    hasher.update(first);
29    let hash_bytes = hasher.finalize();
30
31    let mut hash = [0u8; 32];
32    hash.copy_from_slice(&hash_bytes);
33    hash
34}
35
36/// Double SHA256 of `left` followed by `right`, with no extra buffer.
37pub fn hash256_concat(left: &[u8], right: &[u8]) -> Hash {
38    let mut hasher = Sha256::new();
39    hasher.update(left);
40    hasher.update(right);
41    let first_hash = hasher.finalize();
42
43    let mut hasher = Sha256::new();
44    hasher.update(first_hash);
45    let hash_bytes = hasher.finalize();
46
47    let mut hash = [0u8; 32];
48    hash.copy_from_slice(&hash_bytes);
49    hash
50}
51
52impl FilterHeader {
53    /// Calculate filter header from filter and previous header
54    pub fn new(filter: &CompactBlockFilter, prev_header: Option<&FilterHeader>) -> Self {
55        let filter_hash = hash256(filter.filter_data.as_slice());
56
57        // Previous header hash is SHA256(SHA256(filter_hash || prev_header_hash)).
58        let prev_header_hash = if let Some(prev) = prev_header {
59            hash256_concat(&prev.filter_hash, &prev.prev_header_hash)
60        } else {
61            // Genesis filter header (all zeros or block hash)
62            [0u8; 32]
63        };
64
65        FilterHeader {
66            filter_hash,
67            prev_header_hash,
68        }
69    }
70
71    /// Calculate header hash (double SHA256 of filter_hash || prev_header_hash)
72    pub fn header_hash(&self) -> Hash {
73        hash256_concat(&self.filter_hash, &self.prev_header_hash)
74    }
75}
76
77/// Filter type (currently only Basic Compact Filters)
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79pub enum FilterType {
80    /// Basic compact filters (BIP158)
81    Basic = 0,
82}
83
84impl FilterType {
85    pub fn from_u8(value: u8) -> Option<Self> {
86        match value {
87            0 => Some(FilterType::Basic),
88            _ => None,
89        }
90    }
91
92    pub fn to_u8(self) -> u8 {
93        self as u8
94    }
95}
96
97/// getcfilters message - request filters for block range
98#[derive(Debug, Clone)]
99pub struct GetCfilters {
100    /// Filter type
101    pub filter_type: FilterType,
102    /// Start block height
103    pub start_height: u32,
104    /// Stop block hash
105    pub stop_hash: Hash,
106}
107
108/// cfilter message - compact block filter response
109#[derive(Debug, Clone)]
110pub struct CFilter {
111    /// Filter type
112    pub filter_type: FilterType,
113    /// Block hash
114    pub block_hash: Hash,
115    /// Compact block filter
116    pub filter: CompactBlockFilter,
117}
118
119/// getcfheaders message - request filter headers
120#[derive(Debug, Clone)]
121pub struct GetCfheaders {
122    /// Filter type
123    pub filter_type: FilterType,
124    /// Start block height
125    pub start_height: u32,
126    /// Stop block hash
127    pub stop_hash: Hash,
128}
129
130/// cfheaders message - filter headers response
131#[derive(Debug, Clone)]
132pub struct Cfheaders {
133    /// Filter type
134    pub filter_type: FilterType,
135    /// Stop block hash
136    pub stop_hash: Hash,
137    /// Previous filter header
138    pub prev_header: FilterHeader,
139    /// Filter headers (one per block in range)
140    pub filter_headers: Vec<Hash>,
141}
142
143/// getcfcheckpt message - request filter checkpoints
144#[derive(Debug, Clone)]
145pub struct GetCfcheckpt {
146    /// Filter type
147    pub filter_type: FilterType,
148    /// Stop block hash
149    pub stop_hash: Hash,
150}
151
152/// cfcheckpt message - filter checkpoint response
153#[derive(Debug, Clone)]
154pub struct Cfcheckpt {
155    /// Filter type
156    pub filter_type: FilterType,
157    /// Stop block hash
158    pub stop_hash: Hash,
159    /// Filter header hashes at checkpoint intervals
160    pub filter_header_hashes: Vec<Hash>,
161}
162
163/// BIP157 service flag bit
164pub const NODE_COMPACT_FILTERS: u64 = 1 << 6;
165
166#[cfg(test)]
167mod tests {
168    use super::*;
169
170    #[test]
171    fn test_filter_header() {
172        let empty_filter = CompactBlockFilter {
173            filter_data: std::sync::Arc::new(vec![1, 2, 3]),
174            num_elements: 0,
175        };
176
177        let header1 = FilterHeader::new(&empty_filter, None);
178        let header2 = FilterHeader::new(&empty_filter, Some(&header1));
179
180        // Filter hashes should be the same (same filter data)
181        assert_eq!(header1.filter_hash, header2.filter_hash);
182        // Header hashes should be different when chained
183        assert_ne!(header1.header_hash(), header2.header_hash());
184        assert_eq!(header2.prev_header_hash, header1.header_hash());
185        assert_eq!(
186            header1.header_hash(),
187            concat_hash256(&header1.filter_hash, &header1.prev_header_hash)
188        );
189        assert_eq!(
190            header2.header_hash(),
191            concat_hash256(&header2.filter_hash, &header2.prev_header_hash)
192        );
193    }
194
195    fn concat_hash256(left: &[u8], right: &[u8]) -> [u8; 32] {
196        use sha2::{Digest, Sha256};
197        let mut combined = Vec::new();
198        combined.extend_from_slice(left);
199        combined.extend_from_slice(right);
200        let mut hasher = Sha256::new();
201        hasher.update(&combined);
202        let first = hasher.finalize();
203        let mut hasher = Sha256::new();
204        hasher.update(first);
205        let bytes = hasher.finalize();
206        let mut hash = [0u8; 32];
207        hash.copy_from_slice(&bytes);
208        hash
209    }
210}