1use crate::Transaction;
10use crate::bip152::siphash24;
11use std::sync::Arc;
12
13pub const BIP158_P: u8 = 19;
15
16pub const BIP158_M: u64 = 784_931;
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct CompactBlockFilter {
22 pub filter_data: Arc<Vec<u8>>,
24 pub num_elements: u32,
26}
27
28fn filter_key(block_hash: &[u8; 32]) -> (u64, u64) {
29 let k0 = u64::from_le_bytes(block_hash[0..8].try_into().expect("8"));
30 let k1 = u64::from_le_bytes(block_hash[8..16].try_into().expect("8"));
31 (k0, k1)
32}
33
34fn map_script(k0: u64, k1: u64, script: &[u8], f: u128) -> u64 {
36 let hashed = siphash24(k0, k1, script) as u128;
37 ((hashed.saturating_mul(f)) >> 64) as u64
38}
39
40struct BitWriter {
42 data: Vec<u8>,
43 current_byte: u8,
44 bit_count: u8,
45}
46
47impl BitWriter {
48 fn new() -> Self {
49 Self {
50 data: Vec::new(),
51 current_byte: 0,
52 bit_count: 0,
53 }
54 }
55
56 fn write_bit(&mut self, bit: bool) {
58 if bit {
59 self.current_byte |= 1u8 << (7 - self.bit_count);
60 }
61 self.bit_count += 1;
62
63 if self.bit_count == 8 {
64 self.data.push(self.current_byte);
65 self.current_byte = 0;
66 self.bit_count = 0;
67 }
68 }
69
70 fn write_bits(&mut self, value: u64, num_bits: u8) {
72 for i in 0..num_bits {
73 let bit = ((value >> (num_bits - 1 - i)) & 1) != 0;
74 self.write_bit(bit);
75 }
76 }
77
78 fn finish(mut self) -> Vec<u8> {
80 self.align_byte();
81 self.data
82 }
83
84 fn align_byte(&mut self) {
87 if self.bit_count > 0 {
88 self.data.push(self.current_byte);
89 self.current_byte = 0;
90 self.bit_count = 0;
91 }
92 }
93
94 fn write_golomb_rice(&mut self, value: u64, p: u8) {
98 let quotient = value >> p;
99 let remainder = value & ((1u64 << p) - 1);
100 for _ in 0..quotient {
101 self.write_bit(true);
102 }
103 self.write_bit(false);
104 self.write_bits(remainder, p);
105 }
106}
107
108#[cfg(test)]
110fn golomb_rice_encode(value: u64, p: u8) -> Vec<u8> {
111 let mut writer = BitWriter::new();
112 writer.write_golomb_rice(value, p);
113 writer.finish()
114}
115
116fn encode_sorted_differences(values: &[u64]) -> Vec<u8> {
118 let mut writer = BitWriter::new();
119 let mut prev = 0u64;
120 for value in values {
121 writer.write_golomb_rice(value.saturating_sub(prev), BIP158_P);
122 prev = *value;
123 }
124 writer.finish()
125}
126
127fn write_compact_size(n: u64, out: &mut Vec<u8>) {
128 if n < 253 {
129 out.push(n as u8);
130 } else if n <= u16::MAX as u64 {
131 out.push(0xfd);
132 out.extend_from_slice(&(n as u16).to_le_bytes());
133 } else if n <= u32::MAX as u64 {
134 out.push(0xfe);
135 out.extend_from_slice(&(n as u32).to_le_bytes());
136 } else {
137 out.push(0xff);
138 out.extend_from_slice(&n.to_le_bytes());
139 }
140}
141
142fn read_compact_size(data: &[u8]) -> Option<(u64, usize)> {
143 let first = *data.first()?;
144 match first {
145 n @ 0..=252 => Some((n as u64, 1)),
146 0xfd => {
147 let bytes = data.get(1..3)?;
148 Some((u16::from_le_bytes([bytes[0], bytes[1]]) as u64, 3))
149 }
150 0xfe => {
151 let bytes: [u8; 4] = data.get(1..5)?.try_into().ok()?;
152 Some((u32::from_le_bytes(bytes) as u64, 5))
153 }
154 _ => {
155 let bytes: [u8; 8] = data.get(1..9)?.try_into().ok()?;
156 Some((u64::from_le_bytes(bytes), 9))
157 }
158 }
159}
160
161struct BitReader<'a> {
163 data: &'a [u8],
164 bit_offset: usize,
165}
166
167impl<'a> BitReader<'a> {
168 fn new(data: &'a [u8]) -> Self {
169 Self {
170 data,
171 bit_offset: 0,
172 }
173 }
174
175 fn read_bit(&mut self) -> Option<bool> {
177 if self.bit_offset >= self.data.len() * 8 {
178 return None;
179 }
180 let byte_idx = self.bit_offset / 8;
181 let bit_idx = self.bit_offset % 8;
182 let bit = (self.data[byte_idx] >> (7 - bit_idx)) & 1;
183 self.bit_offset += 1;
184 Some(bit == 1)
185 }
186
187 fn read_bits(&mut self, p: u8) -> Option<u64> {
189 let mut value = 0u64;
190 for _ in 0..p {
191 if let Some(bit) = self.read_bit() {
192 value = (value << 1) | (if bit { 1 } else { 0 });
193 } else {
194 return None;
195 }
196 }
197 Some(value)
198 }
199}
200
201fn golomb_rice_decode(reader: &mut BitReader, p: u8) -> Option<u64> {
208 let mut quotient = 0u64;
210 loop {
211 match reader.read_bit() {
212 Some(true) => quotient += 1,
213 Some(false) => break,
214 None => return None,
215 }
216 }
217
218 let remainder = reader.read_bits(p)?;
220
221 Some((quotient << p) | remainder)
223}
224
225pub fn build_block_filter(
231 block_hash: &[u8; 32],
232 block_transactions: &[Transaction],
233 previous_outpoint_scripts: &[Vec<u8>],
234) -> Result<CompactBlockFilter, String> {
235 let refs: Vec<&[u8]> = previous_outpoint_scripts
236 .iter()
237 .map(Vec::as_slice)
238 .collect();
239 build_block_filter_slices(block_hash, block_transactions, &refs)
240}
241
242pub fn build_block_filter_slices<'a>(
244 block_hash: &[u8; 32],
245 block_transactions: &'a [Transaction],
246 previous_outpoint_scripts: &[&'a [u8]],
247) -> Result<CompactBlockFilter, String> {
248 let mut output_scripts = Vec::new();
249 for tx in block_transactions {
250 for output in &tx.outputs {
251 output_scripts.push(output.script_pubkey.as_slice());
252 }
253 }
254 build_block_filter_output_scripts(block_hash, output_scripts, previous_outpoint_scripts)
255}
256
257pub fn build_block_filter_output_scripts<'a>(
261 block_hash: &[u8; 32],
262 mut scripts: Vec<&'a [u8]>,
263 previous_outpoint_scripts: &[&'a [u8]],
264) -> Result<CompactBlockFilter, String> {
265 scripts.extend(previous_outpoint_scripts.iter().copied());
266 scripts.retain(|script| !script.is_empty() && script.first() != Some(&0x6a));
267 scripts.sort_unstable();
268 scripts.dedup();
269 let n = scripts.len() as u64;
270 if n == 0 {
271 return Ok(CompactBlockFilter {
273 filter_data: Arc::new(vec![0]),
274 num_elements: 0,
275 });
276 }
277
278 let (k0, k1) = filter_key(block_hash);
279 let f = (n as u128).saturating_mul(BIP158_M as u128);
280 let mut hashed_values: Vec<u64> = scripts
281 .iter()
282 .map(|script| map_script(k0, k1, script, f))
283 .collect();
284 hashed_values.sort_unstable();
285
286 let mut filter_data = Vec::new();
287 write_compact_size(n, &mut filter_data);
288 filter_data.extend(encode_sorted_differences(&hashed_values));
289
290 Ok(CompactBlockFilter {
291 filter_data: Arc::new(filter_data),
292 num_elements: n as u32,
293 })
294}
295
296pub fn match_filter(block_hash: &[u8; 32], filter: &CompactBlockFilter, script: &[u8]) -> bool {
306 if filter.num_elements == 0 {
307 return false;
308 }
309
310 let n = filter.num_elements as u64;
311 let Some((counted, offset)) = read_compact_size(filter.filter_data.as_slice()) else {
312 return false;
313 };
314 if counted != n {
315 return false;
316 }
317 let (k0, k1) = filter_key(block_hash);
318 let f = (n as u128).saturating_mul(BIP158_M as u128);
319 let script_hash = map_script(k0, k1, script, f);
320
321 let mut reader = BitReader::new(&filter.filter_data[offset..]);
322 let mut current_value = 0u64;
323 let mut found = false;
324 for _ in 0..filter.num_elements {
325 let Some(diff) = golomb_rice_decode(&mut reader, BIP158_P) else {
326 return false;
327 };
328 current_value += diff;
329 if current_value == script_hash {
330 found = true;
331 }
332 }
333 found
334}
335
336#[cfg(test)]
337mod tests {
338 use super::*;
339
340 const BLOCK_HASH: [u8; 32] = [0x11; 32];
341
342 #[test]
343 fn one_script_encodes_the_mapped_value_and_ignores_op_return() {
344 let script = b"\x51";
345 let filter =
346 build_block_filter_output_scripts(&BLOCK_HASH, vec![script.as_slice()], &[]).unwrap();
347 let (k0, k1) = filter_key(&BLOCK_HASH);
348 let mapped = ((siphash24(k0, k1, script) as u128 * 784_931) >> 64) as u64;
349 let mut expected = Vec::new();
350 write_compact_size(1, &mut expected);
351 expected.extend(golomb_rice_encode(mapped, BIP158_P));
352 assert_eq!(filter.filter_data.as_slice(), expected.as_slice());
353 assert_eq!(filter.num_elements, 1);
354 assert!(match_filter(&BLOCK_HASH, &filter, script));
355
356 let with_return = build_block_filter_output_scripts(
357 &BLOCK_HASH,
358 vec![script.as_slice(), &[0x6a, 0x01]],
359 &[],
360 )
361 .unwrap();
362 assert_eq!(with_return.filter_data, filter.filter_data);
363 }
364
365 #[test]
366 fn test_golomb_rice_encode() {
367 let encoded = golomb_rice_encode(0, BIP158_P);
369 assert!(!encoded.is_empty());
370
371 let encoded2 = golomb_rice_encode(1, BIP158_P);
372 assert!(!encoded2.is_empty());
373 }
374
375 #[test]
376 fn test_empty_filter() {
377 let filter = build_block_filter(&BLOCK_HASH, &[], &[]).unwrap();
378 assert_eq!(filter.num_elements, 0);
379 assert_eq!(filter.filter_data.as_slice(), &[0]);
380 }
381
382 #[test]
383 fn test_deltas_share_one_bitstream() {
384 let values = [0u64, 1, 100, 10_000];
385 let mut writer = BitWriter::new();
386 let mut prev = 0u64;
387 for value in values {
388 writer.write_golomb_rice(value - prev, BIP158_P);
389 prev = value;
390 }
391 assert_eq!(writer.finish(), encode_sorted_differences(&values));
392 }
393
394 #[test]
395 fn test_golomb_rice_encode_decode_roundtrip() {
396 let test_values = vec![0, 1, 2, 10, 100, 1000, 10000];
397
398 for value in test_values {
399 let encoded = golomb_rice_encode(value, BIP158_P);
400 let mut reader = BitReader::new(&encoded);
401 let decoded = golomb_rice_decode(&mut reader, BIP158_P);
402
403 assert_eq!(decoded, Some(value), "Roundtrip failed for value {value}");
404 }
405 }
406
407 #[test]
408 fn test_build_and_match_filter() {
409 use crate::{OutPoint, Transaction, TransactionInput, TransactionOutput};
410
411 let tx = Transaction {
413 version: 1,
414 inputs: vec![TransactionInput {
415 prevout: OutPoint {
416 hash: [0u8; 32],
417 index: 0,
418 },
419 script_sig: vec![],
420 sequence: 0xffffffff,
421 }]
422 .into(),
423 outputs: vec![TransactionOutput {
424 value: 1000,
425 script_pubkey: vec![blvm_consensus::opcodes::OP_1, blvm_consensus::opcodes::OP_2],
426 }]
427 .into(),
428 lock_time: 0,
429 };
430
431 let filter = build_block_filter(&BLOCK_HASH, &[tx.clone()], &[]).unwrap();
433 assert!(filter.num_elements > 0);
434
435 let script_in_filter = &tx.outputs[0].script_pubkey;
437 assert!(match_filter(&BLOCK_HASH, &filter, script_in_filter));
438
439 let script_not_in_filter = vec![0x53, 0x54]; let _matched = match_filter(&BLOCK_HASH, &filter, &script_not_in_filter);
443 let truncated = CompactBlockFilter {
448 filter_data: filter.filter_data.clone(),
449 num_elements: filter.num_elements + 1,
450 };
451 assert!(!match_filter(&BLOCK_HASH, &truncated, script_in_filter));
452 }
453
454 #[test]
455 fn test_filter_bytes_ignore_script_order() {
456 use crate::{Transaction, TransactionInput, TransactionOutput};
457
458 let tx = |scripts: &[&[u8]]| Transaction {
459 version: 1,
460 inputs: Vec::<TransactionInput>::new().into(),
461 outputs: scripts
462 .iter()
463 .map(|script| TransactionOutput {
464 value: 1,
465 script_pubkey: script.to_vec(),
466 })
467 .collect::<Vec<_>>()
468 .into(),
469 lock_time: 0,
470 };
471 let first =
472 build_block_filter(&BLOCK_HASH, &[tx(&[b"\x51\x52", b"\x53\x54"])], &[]).unwrap();
473 let second =
474 build_block_filter(&BLOCK_HASH, &[tx(&[b"\x53\x54", b"\x51\x52"])], &[]).unwrap();
475 assert_eq!(first, second);
476 assert_eq!(first.num_elements, 2);
477 }
478
479 #[test]
482 fn known_filter_bytes_match_an_independent_encoding() {
483 let one = build_block_filter_output_scripts(&BLOCK_HASH, vec![b"\x51"], &[]).unwrap();
484 let expected = hex_bytes("01719c40");
485 assert_eq!(one.filter_data.as_slice(), expected.as_slice());
486 assert_eq!(one.num_elements, 1);
487 assert!(match_filter(&BLOCK_HASH, &one, b"\x51"));
488 assert!(!match_filter(&BLOCK_HASH, &one, b"\x52"));
489
490 let with_return =
491 build_block_filter_output_scripts(&BLOCK_HASH, vec![b"\x51", &[0x6a, 0x01]], &[])
492 .unwrap();
493 assert_eq!(with_return.filter_data.as_slice(), expected.as_slice());
494
495 let duplicated =
496 build_block_filter_output_scripts(&BLOCK_HASH, vec![b"\x51", b"\x51"], &[]).unwrap();
497 assert_eq!(duplicated.filter_data.as_slice(), expected.as_slice());
498
499 let two =
500 build_block_filter_output_scripts(&BLOCK_HASH, vec![b"\x52", b"\x51"], &[]).unwrap();
501 assert_eq!(
502 two.filter_data.as_slice(),
503 hex_bytes("02b19c4999d800").as_slice()
504 );
505 assert_eq!(two.num_elements, 2);
506 assert!(match_filter(&BLOCK_HASH, &two, b"\x51"));
507 assert!(match_filter(&BLOCK_HASH, &two, b"\x52"));
508
509 let collided = build_block_filter_output_scripts(
510 &BLOCK_HASH,
511 vec![&[0x25, 0x01, 0x00], &[0x2e, 0x08, 0x00]],
512 &[],
513 )
514 .unwrap();
515 assert_eq!(collided.num_elements, 2);
516 assert_eq!(
517 collided.filter_data.as_slice(),
518 hex_bytes("0207aea00000").as_slice()
519 );
520 assert!(match_filter(&BLOCK_HASH, &collided, &[0x25, 0x01, 0x00]));
521 assert!(match_filter(&BLOCK_HASH, &collided, &[0x2e, 0x08, 0x00]));
522
523 let empty = build_block_filter_output_scripts(&BLOCK_HASH, vec![], &[]).unwrap();
524 assert_eq!(empty.filter_data.as_slice(), &[0]);
525 assert!(!match_filter(&BLOCK_HASH, &empty, b"\x51"));
526 let only_return =
527 build_block_filter_output_scripts(&BLOCK_HASH, vec![&[0x6a]], &[]).unwrap();
528 assert_eq!(only_return.filter_data.as_slice(), &[0]);
529
530 let unary =
532 build_block_filter_output_scripts(&BLOCK_HASH, vec![&[0x04, 0x00]], &[]).unwrap();
533 assert_eq!(
534 unary.filter_data.as_slice(),
535 hex_bytes("01991cb0").as_slice()
536 );
537 assert!(match_filter(&BLOCK_HASH, &unary, &[0x04, 0x00]));
538
539 let scripts: [&[u8]; 8] = [
541 &[0x01, 0x00, 0x5a],
542 &[0x02, 0x00, 0x5a],
543 &[0x03, 0x00, 0x5a],
544 &[0x04, 0x00, 0x5a],
545 &[0x05, 0x00, 0x5a],
546 &[0x06, 0x00, 0x5a],
547 &[0x07, 0x00, 0x5a],
548 &[0x08, 0x00, 0x5a],
549 ];
550 let wide = build_block_filter_output_scripts(&BLOCK_HASH, scripts.to_vec(), &[]).unwrap();
551 assert_eq!(wide.num_elements, 8);
552 assert_eq!(
553 wide.filter_data.as_slice(),
554 hex_bytes("08c09d02f1a544977793c53f77dc52be4722d7e3911d").as_slice()
555 );
556 for script in scripts {
557 assert!(match_filter(&BLOCK_HASH, &wide, script));
558 }
559 }
560
561 fn hex_bytes(hex: &str) -> Vec<u8> {
562 (0..hex.len())
563 .step_by(2)
564 .map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
565 .collect()
566 }
567}