Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ergo filters #1

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,12 @@ edition = "2018"
[dependencies]
bitcoin = { version = "^0.26", features = [ "rand" ] }
bitcoin_hashes = "^0.9"
[dependencies.ergotree-ir]
git = "https://github.com/hexresearch/sigma-rust.git"
rev = "1a69a89a0b00eb9b58ab87ef6b95e5607e1a1e1f"
[dependencies.sigma-ser]
git = "https://github.com/hexresearch/sigma-rust.git"
rev = "1a69a89a0b00eb9b58ab87ef6b95e5607e1a1e1f"
[dependencies.ergo-lib]
git = "https://github.com/hexresearch/sigma-rust.git"
rev = "1a69a89a0b00eb9b58ab87ef6b95e5607e1a1e1f"
32 changes: 10 additions & 22 deletions src/btc.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
use bitcoin::{Block, BlockHash, OutPoint, Script};
use bitcoin::util::bip158::{BlockFilterWriter, BlockFilterReader, Error};
use bitcoin::{Block, OutPoint, Script};
use bitcoin::util::bip158::{BlockFilterWriter, Error};
use std::io::Cursor;

/// A BIP158 like filter that diverge only in which data is added to the filter.
///
/// Ergvein wallet adds only segwit scripts and data carrier to save bandwith for mobile clients.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ErgveinFilter {
pub struct BtcFilter {
/// Golomb encoded filter
pub content: Vec<u8>
}

impl ErgveinFilter {
impl BtcFilter {
/// create a new filter from pre-computed data
pub fn new (content: &[u8]) -> ErgveinFilter {
ErgveinFilter { content: content.to_vec() }
pub fn new (content: &[u8]) -> BtcFilter {
BtcFilter { content: content.to_vec() }
}

/// Compute a SCRIPT_FILTER that contains spent and output scripts
pub fn new_script_filter<M>(block: &Block, script_for_coin: M) -> Result<ErgveinFilter, Error>
pub fn new_script_filter<M>(block: &Block, script_for_coin: M) -> Result<BtcFilter, Error>
where M: Fn(&OutPoint) -> Result<Script, Error> {
let mut out = Cursor::new(Vec::new());
{
Expand All @@ -27,19 +27,7 @@ impl ErgveinFilter {
add_input_scripts(&mut writer, block, script_for_coin)?;
writer.finish()?;
}
Ok(ErgveinFilter { content: out.into_inner() })
}

/// match any query pattern
pub fn match_any(&self, block_hash: &BlockHash, query: &mut dyn Iterator<Item=&[u8]>) -> Result<bool, Error> {
let filter_reader = BlockFilterReader::new(block_hash);
filter_reader.match_any(&mut Cursor::new(self.content.as_slice()), query)
}

/// match all query pattern
pub fn match_all(&self, block_hash: &BlockHash, query: &mut dyn Iterator<Item=&[u8]>) -> Result<bool, Error> {
let filter_reader = BlockFilterReader::new(block_hash);
filter_reader.match_all(&mut Cursor::new(self.content.as_slice()), query)
Ok(BtcFilter { content: out.into_inner() })
}
}

Expand Down Expand Up @@ -93,13 +81,13 @@ mod tests {
let filter_content = Vec::from_hex("13461a23a8ce05d6ce6a435b1d11d65707a3c6fce967152b8ae09f851d42505b3c41dd87b705d5f4cc2c3062ddcdfebe7a1e80").unwrap();
let block = load_block("./test/block1");
let txmap = make_inputs_map(load_txs("./test/block1-txs"));
let filter = ErgveinFilter::new_script_filter(&block,
let filter = BtcFilter::new_script_filter(&block,
|o| if let Some(s) = txmap.get(o) {
Ok(s.clone())
} else {
Err(Error::UtxoMissing(o.clone()))
}).unwrap();
let test_filter = ErgveinFilter::new(filter_content.as_slice());
let test_filter = BtcFilter::new(filter_content.as_slice());

assert_eq!(test_filter.content, filter.content);

Expand Down
91 changes: 91 additions & 0 deletions src/ergo.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use bitcoin::util::bip158::GCSFilterWriter;
use ergo_lib::chain::{ergo_box::BoxId, transaction::Transaction};
use ergotree_ir::ergo_tree::ErgoTree;
use ergotree_ir::serialization::{SerializationError, SigmaSerializable, constant_store::ConstantStore, sigma_byte_reader::SigmaByteReader};
use sigma_ser::{peekable_reader::PeekableReader, vlq_encode::ReadSigmaVlqExt};
use std::io::{self, Cursor};
use super::utils::{slice_to_u64_le, M, P};

/// A BIP158 like filter that diverge only in which data is added to the filter.
///
/// Ergvein wallet adds only segwit scripts and data carrier to save bandwith for mobile clients.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ErgoFilter {
/// Golomb encoded filter
pub content: Vec<u8>
}

impl ErgoFilter {
/// create a new filter from pre-computed data
pub fn new (content: &[u8]) -> ErgoFilter {
ErgoFilter { content: content.to_vec() }
}

/// Compute script filter for ergo block. It takes transaction data as returned by
/// ergo node as input
pub fn new_script_filter<M>(block_id: &[u8], block: &[u8], script_for_coin: M) -> Result<ErgoFilter, SerializationError>
where M: Fn(&BoxId) -> Result<ErgoTree, SerializationError>
{
let mut out = Cursor::new(Vec::new());
{
let mut block_own = block.to_owned();
let mut writer = ErgoFilterWriter::new(&mut out, block_id, &mut block_own);
writer.add_scripts(script_for_coin)?;
writer.finish()?;
}
Ok(ErgoFilter { content: out.into_inner() })
}
}

/// Compiles and writes a block filter
pub struct ErgoFilterWriter<'a> {
reader: SigmaByteReader<PeekableReader<Cursor<&'a mut [u8]>>>,
writer: GCSFilterWriter<'a>,
}

impl<'a> ErgoFilterWriter<'a> {
/// Create a block filter writer
pub fn new(writer: &'a mut dyn io::Write, block_id: &'a [u8], block: &'a mut [u8]) -> ErgoFilterWriter<'a> {
let k0 = slice_to_u64_le(&block_id[0..8]);
let k1 = slice_to_u64_le(&block_id[8..16]);
let writer = GCSFilterWriter::new(writer, k0, k1, M, P);

// FIXME: Maybe there're saner ways of creating SigmaByteReader.
// NOTE: ConstantStore is not public
let cursor = Cursor::new(block);
let peekable = PeekableReader::new(cursor);
let reader = SigmaByteReader::new(peekable, ConstantStore::empty());

ErgoFilterWriter { reader, writer }
}

/// Add consumed output scripts of a block to filter
pub fn add_scripts<M>(&mut self, script_for_coin: M) -> Result<(), SerializationError>
where M: Fn(&BoxId) -> Result<ErgoTree, SerializationError>
{
let n_tx = {
let n = self.reader.get_u32()?;
if n == 10000002 { self.reader.get_u32()? } else { n }
};

for i in 1..n_tx {
let tx = Transaction::sigma_parse(&mut self.reader)?;
if i == 1 { continue; } // skip coinbase
for out in tx.output_candidates {
let script = out.ergo_tree.sigma_serialize_bytes();
self.writer.add_element(&script);
}
for bid in tx.inputs {
let script = script_for_coin(&bid.box_id)?.sigma_serialize_bytes();
self.writer.add_element(&script);
}
}

Ok(())
}

/// Write block filter
pub fn finish(&mut self) -> Result<usize, io::Error> {
self.writer.finish()
}
}
79 changes: 79 additions & 0 deletions src/filter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use bitcoin::util::bip158::{GCSFilterReader, Error};
use std::io::{self, Cursor};
use super::btc::BtcFilter;
use super::ergo::ErgoFilter;
use super::utils::{slice_to_u64_le, M, P};

pub enum FilterCurrency {
Btc,
Ergo,
}

/// A BIP158 like filter for any supported currency for ergvein wallet
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ErgveinFilter {
Btc(BtcFilter),
Ergo(ErgoFilter),
}

impl ErgveinFilter {
/// create a new filter from pre-computed data
pub fn new(currency: FilterCurrency, content: &[u8]) -> ErgveinFilter {
match currency {
FilterCurrency::Btc => ErgveinFilter::Btc(BtcFilter::new(content)),
FilterCurrency::Ergo => ErgveinFilter::Ergo(ErgoFilter::new(content)),
}
}

/// Get currency tag of the filter
pub fn currency(&self) -> FilterCurrency {
match self {
ErgveinFilter::Btc(_) => FilterCurrency::Btc,
ErgveinFilter::Ergo(_) => FilterCurrency::Ergo,
}
}

/// Get raw content of filter
pub fn content(&self) -> &[u8] {
match self {
ErgveinFilter::Btc(filter) => filter.content.as_slice(),
ErgveinFilter::Ergo(filter) => filter.content.as_slice(),
}
}

/// match any query pattern
pub fn match_any(&self, block_hash: &[u8], query: &mut dyn Iterator<Item=&[u8]>) -> Result<bool, Error> {
let filter_reader = RawFilterReader::new(block_hash);
filter_reader.match_any(&mut Cursor::new(self.content()), query)
}

/// match all query pattern
pub fn match_all(&self, block_hash: &[u8], query: &mut dyn Iterator<Item=&[u8]>) -> Result<bool, Error> {
let filter_reader = RawFilterReader::new(block_hash);
filter_reader.match_all(&mut Cursor::new(self.content()), query)
}
}

/// Reads and interpret a block filter
pub struct RawFilterReader {
reader: GCSFilterReader
}

impl RawFilterReader {
/// Create a block filter reader
pub fn new(block_hash: &[u8]) -> RawFilterReader {
let k0 = slice_to_u64_le(&block_hash[0..8]);
let k1 = slice_to_u64_le(&block_hash[8..16]);
RawFilterReader { reader: GCSFilterReader::new(k0, k1, M, P) }
}

/// match any query pattern
pub fn match_any(&self, reader: &mut dyn io::Read, query: &mut dyn Iterator<Item=&[u8]>) -> Result<bool, Error> {
self.reader.match_any(reader, query)
}

/// match all query pattern
pub fn match_all(&self, reader: &mut dyn io::Read, query: &mut dyn Iterator<Item=&[u8]>) -> Result<bool, Error> {
self.reader.match_all(reader, query)
}
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
pub mod btc;
pub mod ergo;
pub mod filter;
pub mod utils;
18 changes: 18 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/// Golomb encoding parameter as in BIP-158, see also https://gist.github.com/sipa/576d5f09c3b86c3b1b75598d799fc845
pub const P: u8 = 19;
pub const M: u64 = 784931;

macro_rules! define_slice_to_le {
($name: ident, $type: ty) => {
#[inline]
pub fn $name(slice: &[u8]) -> $type {
assert_eq!(slice.len(), ::std::mem::size_of::<$type>());
let mut res = 0;
for i in 0..::std::mem::size_of::<$type>() {
res |= (slice[i] as $type) << i*8;
}
res
}
}
}
define_slice_to_le!(slice_to_u64_le, u64);