1use crate::Transaction;
10use sha2::{Digest, Sha256};
11use std::sync::Arc;
12
13pub const BIP158_P: u8 = 19;
16
17pub const BIP158_M: u64 = 1 << BIP158_P; #[derive(Debug, Clone, PartialEq, Eq)]
22pub struct CompactBlockFilter {
23 pub filter_data: Arc<Vec<u8>>,
25 pub num_elements: u32,
27}
28
29fn hash_to_range(script: &[u8], n: u64, m: u64) -> u64 {
31 let mut hasher = Sha256::new();
33 hasher.update(script);
34 let hash = hasher.finalize();
35
36 let hash_value = u64::from_le_bytes([
38 hash[0], hash[1], hash[2], hash[3], hash[4], hash[5], hash[6], hash[7],
39 ]);
40
41 hash_value % (n * m)
43}
44
45struct BitWriter {
47 data: Vec<u8>,
48 current_byte: u8,
49 bit_count: u8,
50}
51
52impl BitWriter {
53 fn new() -> Self {
54 Self {
55 data: Vec::new(),
56 current_byte: 0,
57 bit_count: 0,
58 }
59 }
60
61 fn write_bit(&mut self, bit: bool) {
63 if bit {
64 self.current_byte |= 1u8 << (7 - self.bit_count);
65 }
66 self.bit_count += 1;
67
68 if self.bit_count == 8 {
69 self.data.push(self.current_byte);
70 self.current_byte = 0;
71 self.bit_count = 0;
72 }
73 }
74
75 fn write_bits(&mut self, value: u64, num_bits: u8) {
77 for i in 0..num_bits {
78 let bit = ((value >> (num_bits - 1 - i)) & 1) != 0;
79 self.write_bit(bit);
80 }
81 }
82
83 fn finish(mut self) -> Vec<u8> {
85 self.align_byte();
86 self.data
87 }
88
89 fn align_byte(&mut self) {
92 if self.bit_count > 0 {
93 self.data.push(self.current_byte);
94 self.current_byte = 0;
95 self.bit_count = 0;
96 }
97 }
98
99 fn write_golomb_rice(&mut self, value: u64, p: u8) {
103 let quotient = value >> p;
104 let remainder = value & ((1u64 << p) - 1);
105 for _ in 0..quotient {
106 self.write_bit(true);
107 }
108 self.write_bit(false);
109 self.write_bits(remainder, p);
110 }
111}
112
113#[cfg(test)]
115fn golomb_rice_encode(value: u64, p: u8) -> Vec<u8> {
116 let mut writer = BitWriter::new();
117 writer.write_golomb_rice(value, p);
118 writer.finish()
119}
120
121fn encode_sorted_differences(values: &[u64]) -> Vec<u8> {
124 let mut writer = BitWriter::new();
125 let Some((first, rest)) = values.split_first() else {
126 return writer.finish();
127 };
128 writer.write_golomb_rice(*first, BIP158_P);
129 writer.align_byte();
130 let mut prev = *first;
131 for value in rest {
132 writer.write_golomb_rice(value - prev, BIP158_P);
133 writer.align_byte();
134 prev = *value;
135 }
136 writer.finish()
137}
138
139struct BitReader<'a> {
141 data: &'a [u8],
142 bit_offset: usize,
143}
144
145impl<'a> BitReader<'a> {
146 fn new(data: &'a [u8]) -> Self {
147 Self {
148 data,
149 bit_offset: 0,
150 }
151 }
152
153 fn read_bit(&mut self) -> Option<bool> {
155 if self.bit_offset >= self.data.len() * 8 {
156 return None;
157 }
158 let byte_idx = self.bit_offset / 8;
159 let bit_idx = self.bit_offset % 8;
160 let bit = (self.data[byte_idx] >> (7 - bit_idx)) & 1;
161 self.bit_offset += 1;
162 Some(bit == 1)
163 }
164
165 fn read_bits(&mut self, p: u8) -> Option<u64> {
167 let mut value = 0u64;
168 for _ in 0..p {
169 if let Some(bit) = self.read_bit() {
170 value = (value << 1) | (if bit { 1 } else { 0 });
171 } else {
172 return None;
173 }
174 }
175 Some(value)
176 }
177}
178
179fn golomb_rice_decode(reader: &mut BitReader, p: u8) -> Option<u64> {
186 let mut quotient = 0u64;
188 loop {
189 match reader.read_bit() {
190 Some(true) => quotient += 1,
191 Some(false) => break,
192 None => return None,
193 }
194 }
195
196 let remainder = reader.read_bits(p)?;
198
199 Some((quotient << p) | remainder)
201}
202
203pub fn build_block_filter(
209 block_transactions: &[Transaction],
210 previous_outpoint_scripts: &[Vec<u8>],
211) -> Result<CompactBlockFilter, String> {
212 let refs: Vec<&[u8]> = previous_outpoint_scripts
213 .iter()
214 .map(Vec::as_slice)
215 .collect();
216 build_block_filter_slices(block_transactions, &refs)
217}
218
219pub fn build_block_filter_slices<'a>(
221 block_transactions: &'a [Transaction],
222 previous_outpoint_scripts: &[&'a [u8]],
223) -> Result<CompactBlockFilter, String> {
224 let mut output_scripts = Vec::new();
225 for tx in block_transactions {
226 for output in &tx.outputs {
227 output_scripts.push(output.script_pubkey.as_slice());
228 }
229 }
230 build_block_filter_output_scripts(output_scripts, previous_outpoint_scripts)
231}
232
233pub fn build_block_filter_output_scripts<'a>(
237 mut scripts: Vec<&'a [u8]>,
238 previous_outpoint_scripts: &[&'a [u8]],
239) -> Result<CompactBlockFilter, String> {
240 scripts.retain(|script| !script.is_empty());
241 scripts.extend(
242 previous_outpoint_scripts
243 .iter()
244 .copied()
245 .filter(|script| !script.is_empty()),
246 );
247
248 scripts.sort_unstable();
250 scripts.dedup();
251 let n = scripts.len() as u64;
252 if n == 0 {
253 return Ok(CompactBlockFilter {
255 filter_data: Arc::new(Vec::new()),
256 num_elements: 0,
257 });
258 }
259
260 let mut hashed_values: Vec<u64> = scripts
262 .iter()
263 .map(|script| hash_to_range(script, n, BIP158_M))
264 .collect();
265
266 hashed_values.sort_unstable();
268 hashed_values.dedup();
269
270 let n_final = hashed_values.len() as u64;
272 let filter_data = Arc::new(encode_sorted_differences(&hashed_values));
273
274 Ok(CompactBlockFilter {
275 filter_data,
276 num_elements: n_final as u32,
277 })
278}
279
280pub fn match_filter(filter: &CompactBlockFilter, script: &[u8]) -> bool {
290 if filter.num_elements == 0 {
291 return false;
292 }
293
294 let n = filter.num_elements as u64;
295 let script_hash = hash_to_range(script, n, BIP158_M);
296
297 let mut reader = BitReader::new(filter.filter_data.as_slice());
298 let mut current_value = 0u64;
299 let mut found = false;
300 for _ in 0..filter.num_elements {
301 let Some(diff) = golomb_rice_decode(&mut reader, BIP158_P) else {
302 return false;
303 };
304 current_value += diff;
305 if current_value == script_hash {
306 found = true;
307 }
308 }
309 found
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315
316 #[test]
317 fn test_hash_to_range() {
318 let script = b"test script";
319 let n = 100;
320 let m = BIP158_M;
321 let value = hash_to_range(script, n, m);
322 assert!(value < n * m);
323 }
324
325 #[test]
326 fn test_golomb_rice_encode() {
327 let encoded = golomb_rice_encode(0, BIP158_P);
329 assert!(!encoded.is_empty());
330
331 let encoded2 = golomb_rice_encode(1, BIP158_P);
332 assert!(!encoded2.is_empty());
333 }
334
335 #[test]
336 fn test_empty_filter() {
337 let filter = build_block_filter(&[], &[]).unwrap();
338 assert_eq!(filter.num_elements, 0);
339 assert!(filter.filter_data.is_empty());
340 }
341
342 #[test]
343 fn test_joined_golomb_matches_separate_encodings() {
344 let values = [0u64, 1, 100, 10_000];
345 let mut separate = Vec::new();
346 for value in values {
347 separate.extend(golomb_rice_encode(value, BIP158_P));
348 }
349 let mut writer = BitWriter::new();
350 for value in values {
351 writer.write_golomb_rice(value, BIP158_P);
352 writer.align_byte();
353 }
354 assert_eq!(writer.finish(), separate);
355 }
356
357 #[test]
358 fn test_golomb_rice_encode_decode_roundtrip() {
359 let test_values = vec![0, 1, 2, 10, 100, 1000, 10000];
360
361 for value in test_values {
362 let encoded = golomb_rice_encode(value, BIP158_P);
363 let mut reader = BitReader::new(&encoded);
364 let decoded = golomb_rice_decode(&mut reader, BIP158_P);
365
366 assert_eq!(decoded, Some(value), "Roundtrip failed for value {value}");
367 }
368 }
369
370 #[test]
371 fn test_build_and_match_filter() {
372 use crate::{OutPoint, Transaction, TransactionInput, TransactionOutput};
373
374 let tx = Transaction {
376 version: 1,
377 inputs: vec![TransactionInput {
378 prevout: OutPoint {
379 hash: [0u8; 32],
380 index: 0,
381 },
382 script_sig: vec![],
383 sequence: 0xffffffff,
384 }]
385 .into(),
386 outputs: vec![TransactionOutput {
387 value: 1000,
388 script_pubkey: vec![blvm_consensus::opcodes::OP_1, blvm_consensus::opcodes::OP_2],
389 }]
390 .into(),
391 lock_time: 0,
392 };
393
394 let filter = build_block_filter(&[tx.clone()], &[]).unwrap();
396 assert!(filter.num_elements > 0);
397
398 let script_in_filter = &tx.outputs[0].script_pubkey;
400 assert!(match_filter(&filter, script_in_filter));
401
402 let script_not_in_filter = vec![0x53, 0x54]; let _matched = match_filter(&filter, &script_not_in_filter);
406 let truncated = CompactBlockFilter {
411 filter_data: filter.filter_data.clone(),
412 num_elements: filter.num_elements + 1,
413 };
414 assert!(!match_filter(&truncated, script_in_filter));
415 }
416
417 #[test]
418 fn test_filter_bytes_ignore_script_order() {
419 use crate::{Transaction, TransactionInput, TransactionOutput};
420
421 let tx = |scripts: &[&[u8]]| Transaction {
422 version: 1,
423 inputs: Vec::<TransactionInput>::new().into(),
424 outputs: scripts
425 .iter()
426 .map(|script| TransactionOutput {
427 value: 1,
428 script_pubkey: script.to_vec(),
429 })
430 .collect::<Vec<_>>()
431 .into(),
432 lock_time: 0,
433 };
434 let first = build_block_filter(&[tx(&[b"\x51\x52", b"\x53\x54"])], &[]).unwrap();
435 let second = build_block_filter(&[tx(&[b"\x53\x54", b"\x51\x52"])], &[]).unwrap();
436 assert_eq!(first, second);
437 assert_eq!(first.num_elements, 2);
438 }
439}