use crate::io;
use crate::io::Cursor;
use alloc::vec::Vec;
use bitcoin::consensus::Encodable;
use bitcoin::hashes::Hash;
use bitcoin::util::bip158::{Error as FilterError, GCSFilterReader, GCSFilterWriter};
use bitcoin::{Block, BlockHash, FilterHash, FilterHeader, OutPoint};
const P: u8 = 19;
const M: u64 = 784931;
#[derive(Clone)]
pub struct BlockSpendFilter {
pub content: Vec<u8>,
}
impl BlockSpendFilter {
pub fn filter_header(&self, previous_filter_header: &FilterHeader) -> FilterHeader {
let filter_hash = FilterHash::hash(self.content.as_slice());
filter_hash.filter_header(previous_filter_header)
}
pub fn filter_hash(&self) -> FilterHash {
FilterHash::hash(self.content.as_slice())
}
pub fn new(content: &[u8]) -> BlockSpendFilter {
BlockSpendFilter {
content: content.to_vec(),
}
}
pub fn from_block(block: &Block) -> BlockSpendFilter {
let mut out = Vec::new();
{
let mut writer = BlockFilterWriter::new(&mut out, block);
writer.add_spent_outpoints();
writer.finish().expect("writing to a Vec cannot fail");
}
BlockSpendFilter { content: out }
}
pub fn match_any(
&self,
block_hash: &BlockHash,
query: &mut dyn Iterator<Item = &OutPoint>,
) -> bool {
let encoded: Vec<_> = query.map(|o| encode_point(&o)).collect();
let filter_reader = BlockFilterReader::new(block_hash);
filter_reader
.match_any(
&mut Cursor::new(self.content.as_slice()),
&mut encoded.iter().map(|x| x.as_slice()),
)
.expect("reading from a Vec cannot fail")
}
pub fn match_all(
&self,
block_hash: &BlockHash,
query: &mut dyn Iterator<Item = &OutPoint>,
) -> bool {
let encoded: Vec<_> = query.map(|o| encode_point(&o)).collect();
let filter_reader = BlockFilterReader::new(block_hash);
filter_reader
.match_all(
&mut Cursor::new(self.content.as_slice()),
&mut encoded.iter().map(|x| x.as_slice()),
)
.expect("reading from a Vec cannot fail")
}
}
fn hash_to_k0_k1(hash: &[u8; 32]) -> (u64, u64) {
let mut k0_slice = [0; 8];
k0_slice.copy_from_slice(&hash[0..8]);
let mut k1_slice = [0; 8];
k1_slice.copy_from_slice(&hash[8..16]);
let k0 = u64::from_le_bytes(k0_slice);
let k1 = u64::from_le_bytes(k1_slice);
(k0, k1)
}
pub struct BlockFilterWriter<'a> {
block: &'a Block,
writer: GCSFilterWriter<'a>,
}
impl<'a> BlockFilterWriter<'a> {
pub fn new(writer: &'a mut dyn io::Write, block: &'a Block) -> BlockFilterWriter<'a> {
let hash = block.block_hash().into_inner();
let (k0, k1) = hash_to_k0_k1(&hash);
let writer = GCSFilterWriter::new(writer, k0, k1, M, P);
BlockFilterWriter { block, writer }
}
pub fn add_spent_outpoints(&mut self) {
for transaction in &self.block.txdata {
for txin in &transaction.input {
let buf = encode_point(&txin.previous_output);
self.add_element(&buf);
}
}
}
pub fn add_element(&mut self, data: &[u8]) {
self.writer.add_element(data);
}
pub fn finish(&mut self) -> Result<usize, io::Error> {
self.writer.finish()
}
}
pub struct BlockFilterReader {
reader: GCSFilterReader,
}
impl BlockFilterReader {
pub fn new(block_hash: &BlockHash) -> BlockFilterReader {
let hash = block_hash.into_inner();
let (k0, k1) = hash_to_k0_k1(&hash);
BlockFilterReader {
reader: GCSFilterReader::new(k0, k1, M, P),
}
}
pub fn match_any(
&self,
reader: &mut dyn io::Read,
query: &mut dyn Iterator<Item = &[u8]>,
) -> Result<bool, FilterError> {
self.reader.match_any(reader, query)
}
pub fn match_all(
&self,
reader: &mut dyn io::Read,
query: &mut dyn Iterator<Item = &[u8]>,
) -> Result<bool, FilterError> {
self.reader.match_all(reader, query)
}
}
fn encode_point(point: &OutPoint) -> Vec<u8> {
let mut buf = Vec::new();
point.consensus_encode(&mut buf).unwrap();
buf
}
#[cfg(test)]
mod tests {
use crate::filter::BlockSpendFilter;
use bitcoin::hashes::Hash;
use bitcoin::{
Block, BlockHash, BlockHeader, OutPoint, PackedLockTime, Transaction, TxIn, TxMerkleNode,
Txid,
};
#[test]
fn filter_test() {
let mut txs = Vec::new();
for i in 0..4000 {
let tx = Transaction {
version: 0,
lock_time: PackedLockTime(0),
input: vec![TxIn {
previous_output: OutPoint {
txid: Txid::all_zeros(),
vout: i,
},
script_sig: Default::default(),
sequence: Default::default(),
witness: Default::default(),
}],
output: vec![],
};
txs.push(tx);
}
let block = Block {
header: BlockHeader {
version: 0,
prev_blockhash: BlockHash::all_zeros(),
merkle_root: TxMerkleNode::all_zeros(),
time: 0,
bits: 0,
nonce: 0,
},
txdata: txs,
};
let block_hash = block.block_hash();
let filter = BlockSpendFilter::from_block(&block);
assert!(filter.match_any(
&block_hash,
&mut vec![
OutPoint {
txid: Txid::all_zeros(),
vout: 1234,
},
OutPoint {
txid: Txid::all_zeros(),
vout: 5555,
}
]
.iter()
));
assert!(!filter.match_all(
&block_hash,
&mut vec![
OutPoint {
txid: Txid::all_zeros(),
vout: 1234,
},
OutPoint {
txid: Txid::all_zeros(),
vout: 5555,
}
]
.iter()
));
assert_eq!(filter.content.len(), 10530);
let txid = Txid::hash(&[1]);
for i in 0..100 {
let points = (0..1000)
.map(|j| OutPoint {
txid,
vout: i * 1000 + j,
})
.collect::<Vec<_>>();
if filter.match_any(&block_hash, &mut points.iter()) {
panic!("false positive at {}", i);
}
}
}
}