https://github.com/deadkennedyx/niebla-158
BIP-158 - Compact Block Filters
https://github.com/deadkennedyx/niebla-158
bip157 bip158 blockchain btc btc-wallet crypto cryptocurrency cryptography privacy-enhancing-technologies
Last synced: 24 days ago
JSON representation
BIP-158 - Compact Block Filters
- Host: GitHub
- URL: https://github.com/deadkennedyx/niebla-158
- Owner: DeadKennedyx
- Created: 2025-09-28T21:22:03.000Z (about 1 month ago)
- Default Branch: master
- Last Pushed: 2025-09-29T07:52:18.000Z (about 1 month ago)
- Last Synced: 2025-09-29T08:32:39.236Z (about 1 month ago)
- Topics: bip157, bip158, blockchain, btc, btc-wallet, crypto, cryptocurrency, cryptography, privacy-enhancing-technologies
- Language: Rust
- Homepage:
- Size: 29.3 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.MD
Awesome Lists containing this project
README
# niebla-158
this library is **Still a work in progress**
**BIP-158 compact filter** client engine for privacy wallets.
It verifies **cfheaders** (rolling compact-filter headers) against checkpoints, scans **block filters** for your wallet’s scripts/addresses, and fetches matching blocks to hand back relevant transactions — without revealing your whole address list to a third-party server.
## What this crate gives you
- `Niebla158` — the orchestrator (verify → scan → fetch → notify).
- `FilterSource` — trait you implement to fetch:
- cfheaders batches,
- per-block compact filters,
- raw blocks when a filter hits.
- `WalletHooks` — trait your wallet implements to:
- provide a **watchlist** (scripts/addresses outpoints),
- receive **on_block_match(height, hash, txs)** callbacks.
- `Store` — tiny persistence layer for:
- latest verified cfheaders tip,
- last scanned height,
- optional birth height.
- `SqliteStore` — bundled, embedded SQLite implementation (no system SQLite needed).
## How you integrate it
You implement three small traits that the engine depends on:
FilterSource — provide cfheaders batches, per-block cfilters, and raw blocks (bytes).
WalletHooks — return your watchlist (scripts) and handle on_block_match callbacks.
Store — persist a couple of integers (verified cfheaders tip and last scanned height).
A bundled SQLite store is available behind the store-sqlite feature.
Because the engine consumesw bytes at the boundary, its agnostic to which network client you use.
Using Nakamoto
You can use Nakamoto as your FilterSource by adding a tiny adapter that converts its responses into the byte shapes this crate expects (cfheaders as [u8; 32] hashes, cfilters as raw bytes, blocks as raw bytes).
## Status / Future plans
Today: the crate ships the engine + traits and SQLite store.
Planned: provide a first-party FilterSource implementation backed by Nakamoto and expose convenient constructors.
## Example use:
```rust
use std::sync::Arc;
use anyhow::Result;
use async_trait::async_trait;
use bitcoin::{consensus, BlockHash, ScriptBuf, Transaction};
use niebla_158::prelude::*;
use niebla_158::store::sqlite_store::SqliteStore;
// --- 1) Nakamoto adapter ----------------------------------------------------
// Replace these `nakamoto::*` paths with the real ones from the crate you use.
mod naka {
pub mod client {
use super::types::*;
#[derive(Clone)]
pub struct Handle;
impl Handle {
pub async fn connect_testnet() -> anyhow::Result { unimplemented!() }
pub async fn tip_height(&self) -> anyhow::Result { unimplemented!() }
pub async fn header_hash(&self, _h: u32) -> anyhow::Result { unimplemented!() }
pub async fn get_cfheaders(&self, _start: u32, _stop: BlockHash) -> anyhow::Result> { unimplemented!() }
pub async fn get_cfilter_bytes(&self, _bh: BlockHash) -> anyhow::Result> { unimplemented!() }
pub async fn get_block_bytes(&self, _bh: BlockHash) -> anyhow::Result> { unimplemented!() }
}
pub mod types { pub use bitcoin::BlockHash; }
}
}
use naka::client::Handle as NakaHandle;
/// Single adapter that implements BOTH `FilterSource` and `HeaderSource`.
#[derive(Clone)]
struct NakaSource {
node: Arc,
}
impl NakaSource {
async fn connect() -> Result {
let node = NakaHandle::connect_testnet().await?;
Ok(Self { node: Arc::new(node) })
}
}
#[async_trait]
impl FilterSource for NakaSource {
async fn get_cfheaders(&self, start_h: u32, stop: BlockHash) -> Result {
let headers = self.node.get_cfheaders(start_h, stop).await?;
Ok(niebla_158::filter_source::CfHeadersBatch { start_height: start_h, headers })
}
async fn get_cfilter(&self, block: BlockHash) -> Result> {
// Raw BIP158 filter bytes for this block
self.node.get_cfilter_bytes(block).await
}
async fn get_block(&self, block: BlockHash) -> Result> {
// Raw block bytes (can also fetch structured block & `serialize` here)
self.node.get_block_bytes(block).await
}
}
#[async_trait]
impl niebla_158::headers::HeaderSource for NakaSource {
async fn tip_height(&self) -> Result {
self.node.tip_height().await
}
async fn hash_at_height(&self, h: u32) -> Result {
self.node.header_hash(h).await
}
}
// --- 2) Your wallet hooks ---------------------------------------------------
struct MyWalletHooks {
watch: Vec,
}
#[async_trait]
impl WalletHooks for MyWalletHooks {
async fn watchlist(&self) -> Result> {
// Provide bech32/legacy scripts you want to track.
// In a real app, derive from your xpubs or address book.
Ok(self.watch.clone())
}
async fn on_block_match(&self, height: u32, block: BlockHash, txs: Vec) -> Result<()> {
println!("Hit at {} ({}) with {} txs", height, block, txs.len());
Ok(())
}
}
// --- 3) Wire it up ---
#[tokio::main]
async fn main() -> Result<()> {
// (a) Store: use the bundled SQLite store
let store = SqliteStore::new("niebla158.db")?;
// (b) Source & headers: both backed by Nakamoto
let source = NakaSource::connect().await?;
let headers = source.clone();
// (c) Hooks: your wallet watchlist (example: one P2WPKH script)
use bitcoin::{Address, Network};
let addr = Address::from_str("tb1qexample...").unwrap()
.require_network(Network::Testnet).unwrap();
let watch_script = addr.script_pubkey();
let hooks = MyWalletHooks { watch: vec![watch_script] };
// (d) Engine
let engine = Niebla158::new(store, hooks, source, headers);
// Optional: add checkpoints once you have a list
// let engine = engine.with_checkpoints(your_checkpoints_vec);
// (e) Go! The engine will:
// - verify cfheaders
// - stream cfilters for new heights, match against your watchlist
// - fetch & decode blocks on a hit and call `on_block_match`
engine.run_to_tip().await?;
Ok(())
}
```