From df2635a44d749eff65bb75a38734d75b63ef84e3 Mon Sep 17 00:00:00 2001 From: Roman Zeyde Date: Sun, 8 Mar 2026 14:38:33 +0100 Subject: [PATCH] [WIP] Add BIP-352 tweak index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The index maps `block_next_txnum || txid[:8]` (12 bytes) -> `32 byte tweak`. After compaction, `sptweak` column family takes ~8.3GB @ Apr 2026. It allows efficiently: - reading per-block tweaks (blockhash → `block_next_txnum`). - looking up per-transaction tweak (given its blockhash). Also added a test vector for [signet block 200000](https://mempool.space/signet/block/0000007d60f5ffc47975418ac8331c0ea52cf551730ef7ead7ff9082a536f13c). --- Cargo.lock | 66 ++- bindex-lib/Cargo.toml | 3 + bindex-lib/src/chain.rs | 7 +- bindex-lib/src/db.rs | 15 +- bindex-lib/src/index/mod.rs | 14 + bindex-lib/src/index/sptweak.rs | 490 ++++++++++++++++++ ...4b038fc6dc442c1c1682ba9065e4402b3eaa0.json | 45 ++ ...31c0ea52cf551730ef7ead7ff9082a536f13c.json | 137 +++++ ...34b038fc6dc442c1c1682ba9065e4402b3eaa0.bin | Bin 0 -> 26529 bytes ...331c0ea52cf551730ef7ead7ff9082a536f13c.bin | Bin 0 -> 31146 bytes ...34b038fc6dc442c1c1682ba9065e4402b3eaa0.bin | Bin 0 -> 2676 bytes ...331c0ea52cf551730ef7ead7ff9082a536f13c.bin | Bin 0 -> 4707 bytes bindex.sh | 2 +- 13 files changed, 766 insertions(+), 13 deletions(-) create mode 100644 bindex-lib/src/index/sptweak.rs create mode 100644 bindex-lib/src/tests/signet/bip352_tweaks_00000012c5a8005cd56f2cb97334b038fc6dc442c1c1682ba9065e4402b3eaa0.json create mode 100644 bindex-lib/src/tests/signet/bip352_tweaks_0000007d60f5ffc47975418ac8331c0ea52cf551730ef7ead7ff9082a536f13c.json create mode 100644 bindex-lib/src/tests/signet/block_00000012c5a8005cd56f2cb97334b038fc6dc442c1c1682ba9065e4402b3eaa0.bin create mode 100644 bindex-lib/src/tests/signet/block_0000007d60f5ffc47975418ac8331c0ea52cf551730ef7ead7ff9082a536f13c.bin create mode 100644 bindex-lib/src/tests/signet/spent_00000012c5a8005cd56f2cb97334b038fc6dc442c1c1682ba9065e4402b3eaa0.bin create mode 100644 bindex-lib/src/tests/signet/spent_0000007d60f5ffc47975418ac8331c0ea52cf551730ef7ead7ff9082a536f13c.bin diff --git a/Cargo.lock b/Cargo.lock index f4f4266..9fa44b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,7 +86,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" dependencies = [ "bitcoin-internals", - "bitcoin_hashes", + "bitcoin_hashes 0.14.1", ] [[package]] @@ -107,12 +107,24 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bech32" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" + [[package]] name = "bech32" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" +[[package]] +name = "bimap" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" + [[package]] name = "bindex" version = "0.0.18" @@ -125,6 +137,9 @@ dependencies = [ "rayon", "rusqlite", "rust-rocksdb", + "serde", + "serde_json", + "silentpayments", "tempfile", "thiserror", "ureq", @@ -171,12 +186,12 @@ checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66" dependencies = [ "base58ck", "base64 0.21.7", - "bech32", + "bech32 0.11.1", "bitcoin-internals", "bitcoin-io", "bitcoin-units", - "bitcoin_hashes", - "hex-conservative", + "bitcoin_hashes 0.14.1", + "hex-conservative 0.2.2", "hex_lit", "secp256k1", "serde", @@ -207,6 +222,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bitcoin_hashes" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446819536d8121575eeb7e89efdbadb3f055e87e4bb66c6679a6d5cc2f4b64fd" +dependencies = [ + "hex-conservative 0.1.2", +] + [[package]] name = "bitcoin_hashes" version = "0.14.1" @@ -214,7 +238,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" dependencies = [ "bitcoin-io", - "hex-conservative", + "hex-conservative 0.2.2", "serde", ] @@ -225,7 +249,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943d4257fdfc85afe2a13d2b539a5213e97bb978329dd79b624acbce73042fb" dependencies = [ "bitcoin", - "bitcoin_hashes", + "bitcoin_hashes 0.14.1", ] [[package]] @@ -529,6 +553,21 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-conservative" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" + [[package]] name = "hex-conservative" version = "0.2.2" @@ -928,7 +967,7 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "bitcoin_hashes", + "bitcoin_hashes 0.14.1", "secp256k1-sys", "serde", ] @@ -991,6 +1030,19 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "silentpayments" +version = "0.5.0" +source = "git+https://github.com/cygnet3/spdk?rev=ce68148d8621e58a33abe251e28b7587b04998dd#ce68148d8621e58a33abe251e28b7587b04998dd" +dependencies = [ + "bech32 0.9.1", + "bimap", + "bitcoin_hashes 0.13.1", + "hex", + "secp256k1", + "serde", +] + [[package]] name = "smallvec" version = "1.15.1" diff --git a/bindex-lib/Cargo.toml b/bindex-lib/Cargo.toml index c909800..477488b 100644 --- a/bindex-lib/Cargo.toml +++ b/bindex-lib/Cargo.toml @@ -28,4 +28,7 @@ rust-rocksdb = { version = "0.36.0", default-features = false, features = ["zstd hex_lit = "0.1" # bitcoind path should be specified via BITCOIND_EXE corepc-node = { version = "0.10", default-features = false, features = ["29_0"] } +serde = "1.0" +serde_json = "1.0" tempfile = "3" +silentpayments = { git = "https://github.com/cygnet3/spdk", rev = "ce68148d8621e58a33abe251e28b7587b04998dd" } diff --git a/bindex-lib/src/chain.rs b/bindex-lib/src/chain.rs index d376516..26d73c2 100644 --- a/bindex-lib/src/chain.rs +++ b/bindex-lib/src/chain.rs @@ -252,7 +252,7 @@ impl IndexedChain { use rayon::prelude::*; let mut batches = Vec::with_capacity(headers.len()); - for chunk in headers.chunks(10) { + for chunk in headers.chunks(128) { let items: Vec<_> = chunk .par_iter() .map(|header| self.fetch_data(header.block_hash())) @@ -287,10 +287,8 @@ impl IndexedChain { for batch in batches { self.headers.add(batch.header); } - - stats.elapsed = t.elapsed(); if stats.indexed_blocks > 0 { - self.store.flush()?; + stats.elapsed = t.elapsed(); info!( "block={} height={}: indexed {} blocks, {:.3}[MB], dt = {:.3}[s]: {:.3} [ms/block], {:.3} [MB/block], {:.3} [MB/s]", self.headers.tip_hash(), @@ -302,6 +300,7 @@ impl IndexedChain { stats.size_read as f64 / (1e6 * stats.indexed_blocks as f64), stats.size_read as f64 / (1e6 * stats.elapsed.as_secs_f64()), ); + self.store.flush()?; } else { // Start autocompactions when there are no new indexed blocks self.store.start_compactions()?; diff --git a/bindex-lib/src/db.rs b/bindex-lib/src/db.rs index 69fd6b0..91e2b77 100644 --- a/bindex-lib/src/db.rs +++ b/bindex-lib/src/db.rs @@ -35,8 +35,9 @@ const HEADERS_CF: &str = "headers"; const SCRIPT_HASH_CF: &str = "script_hash"; const TXPOS_CF: &str = "txpos"; const TXID_CF: &str = "txid"; +const SPTWEAK_CF: &str = "sptweak"; -const COLUMN_FAMILIES: &[&str] = &[HEADERS_CF, TXPOS_CF, TXID_CF, SCRIPT_HASH_CF]; +const COLUMN_FAMILIES: &[&str] = &[HEADERS_CF, TXPOS_CF, TXID_CF, SPTWEAK_CF, SCRIPT_HASH_CF]; fn cf_descriptors( opts: &rocksdb::Options, @@ -114,6 +115,18 @@ impl DB { .map(index::TxBlockPosRow::serialize) .for_each(|(k, v)| f(&mut write_batch, cf, &k, &v)); + // key = next_txnum || txid_prefix || tweak + let cf = self.cf(SPTWEAK_CF); + // Batch are grouped by `txnum`. + for batch in batches { + let mut rows: Vec<&index::TxTweakRow> = batch.sptweak_rows.iter().collect(); + // Sort row group by `txid` prefix. + rows.sort_unstable_by_key(|item| &item.txid); + for row in rows { + f(&mut write_batch, cf, &row.serialize(&batch.header), b""); + } + } + // key = next_txnum, value = blockhash || blockheader let cf = self.cf(HEADERS_CF); for batch in batches { diff --git a/bindex-lib/src/index/mod.rs b/bindex-lib/src/index/mod.rs index bc5affc..47cf51b 100644 --- a/bindex-lib/src/index/mod.rs +++ b/bindex-lib/src/index/mod.rs @@ -1,5 +1,6 @@ mod header; mod scripthash; +mod sptweak; mod txid; mod txpos; @@ -8,6 +9,7 @@ use bitcoin_slices::bsl; pub use header::IndexedHeader; pub use scripthash::ScriptHash; +pub use sptweak::TxTweakRow; pub use txpos::{TxBlockPos, TxBlockPosRow}; #[derive(thiserror::Error, Debug)] @@ -150,6 +152,14 @@ impl BlockBytes { pub fn txs_count(&self) -> u32 { parse_txs_count(&self.0[bitcoin::block::Header::SIZE..]) } + + #[cfg(test)] + pub fn txdata(&self) -> Vec { + use bitcoin::consensus::encode::deserialize; + + let block: bitcoin::Block = deserialize(&self.0).expect("invalid block"); + block.txdata + } } pub struct SpentBytes(Vec); @@ -194,6 +204,7 @@ pub struct Batch { pub scripthash_rows: Vec, pub txid_rows: Vec, pub txpos_rows: Vec, + pub sptweak_rows: Vec, pub header: IndexedHeader, } @@ -208,16 +219,19 @@ impl Batch { let scripthash = scripthash::index(block, spent, first_txnum)?; let txpos = txpos::index(block, first_txnum)?; let txid = txid::index(block, first_txnum)?; + let sptweak = sptweak::index(block, spent, first_txnum)?; // All must have the same number of transactions assert_eq!(txnum_range.next, scripthash.next_txnum); assert_eq!(txnum_range.next, txpos.next_txnum); assert_eq!(txnum_range.next, txid.next_txnum); + assert_eq!(txnum_range.next, sptweak.next_txnum); Ok(Batch { scripthash_rows: scripthash.rows, txpos_rows: txpos.rows, txid_rows: txid.rows, + sptweak_rows: sptweak.rows, header: IndexedHeader::new(txnum_range.next, blockhash, block), }) } diff --git a/bindex-lib/src/index/sptweak.rs b/bindex-lib/src/index/sptweak.rs new file mode 100644 index 0000000..cb01821 --- /dev/null +++ b/bindex-lib/src/index/sptweak.rs @@ -0,0 +1,490 @@ +use bitcoin::{consensus::Decodable, secp256k1, Transaction, Txid}; +use bitcoin::{ + hashes::{sha256t_hash_newtype, Hash, HashEngine}, + OutPoint, +}; +use secp256k1::{PublicKey, Scalar, XOnlyPublicKey}; + +use crate::index::{self, BlockBytes, Error, IndexedBlock, SpentBytes, TxNum}; + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +pub struct TxTweakRow { + pub txid: Txid, + pub tweak: PublicKey, +} + +impl TxTweakRow { + const TXID_PREFIX_LEN: usize = 8; + const KEY_LEN: usize = TxNum::LEN + Self::TXID_PREFIX_LEN; + const LEN: usize = Self::KEY_LEN + secp256k1::constants::PUBLIC_KEY_SIZE; + + pub fn serialize(&self, header: &index::IndexedHeader) -> [u8; Self::LEN] { + let mut key = [0; Self::LEN]; + // group by block height + order by txid prefix + + key[..TxNum::LEN].copy_from_slice(&header.next_txnum().serialize()); + key[TxNum::LEN..Self::KEY_LEN].copy_from_slice(&self.txid[..Self::TXID_PREFIX_LEN]); + key[Self::KEY_LEN..].copy_from_slice(&self.tweak.serialize()); + key + } +} + +struct PerInput<'a> { + outpoint: &'a OutPoint, + pubkey: PublicKey, +} + +impl<'a> PerInput<'a> { + fn new(txin: &'a bitcoin::TxIn, pubkey: PublicKey) -> Self { + Self { + outpoint: &txin.previous_output, + pubkey, + } + } + + fn combine(self, other: PerInput<'a>) -> Result { + Ok(PerInput { + outpoint: self.outpoint.min(other.outpoint), + pubkey: self.pubkey.combine(&other.pubkey)?, + }) + } +} + +pub fn index( + block: &BlockBytes, + spent: &SpentBytes, + next_txnum: TxNum, +) -> Result, Error> { + let secp = secp256k1::Secp256k1::verification_only(); // OPTIMIZE + let mut result = IndexedBlock::new(next_txnum); + let block = bitcoin::Block::consensus_decode_from_finite_reader(&mut &block.0[..])?; + let spent = decode_spent(&spent.0)?; + assert_eq!(block.txdata.len(), spent.len()); + for (tx, txouts_spent) in block.txdata.into_iter().zip(spent.into_iter()) { + result.next_txnum.increment_by(1); + if !maybe_silent_payment(&tx) { + // no taproot outputs, or coinbase + continue; + } + assert_eq!(tx.input.len(), txouts_spent.len()); + // let per_input_entries = tx.input.into_iter().zip(txouts_spent.into_iter()); + // Iterate through each input of the transaction and collect its eligible pubkey: + let per_input_entries = tx + .input + .iter() + .zip(txouts_spent.into_iter()) + .filter_map(|(txin, spent_txout)| get_pubkey_from_input(txin, spent_txout).transpose()); + + // Reduce into (smallest_outpoint, A_sum) pair: + let total = match per_input_entries.into_iter().reduce(|a, b| a?.combine(b?)) { + Some(Ok(total)) => total, + Some(Err(err)) => { + log::warn!("skipping {}: {}", tx.compute_txid(), err); + continue; + } + None => continue, + }; + // Calculate the tweak data based on the public keys and outpoints + let tweak = compute_tweak(total, &secp); + let txid = tx.compute_txid(); + result.rows.push(TxTweakRow { txid, tweak }); + } + Ok(result) +} + +fn maybe_silent_payment(tx: &Transaction) -> bool { + if tx.is_coinbase() { + return false; + } + tx.output.iter().any(|txo| txo.script_pubkey.is_p2tr()) +} + +fn compute_tweak(total: PerInput, secp: &secp256k1::Secp256k1) -> PublicKey { + let PerInput { + outpoint: smallest_outpoint, + pubkey: sum_pubkeys, + } = total; + let input_hash = InputsHash::new(smallest_outpoint, sum_pubkeys).to_scalar(); + sum_pubkeys + .mul_tweak(secp, &input_hash) + .expect("`mul_tweak()` failed") +} + +sha256t_hash_newtype! { + pub(crate) struct InputsTag = hash_str("BIP0352/Inputs"); + + /// BIP0352-tagged hash with tag \"Inputs\". + /// + /// This is used for computing the inputs hash. + #[hash_newtype(forward)] + pub(crate) struct InputsHash(_); +} + +impl InputsHash { + fn new(smallest_outpoint: &OutPoint, sum_pubkeys: PublicKey) -> InputsHash { + let mut eng = InputsHash::engine(); + eng.input(&smallest_outpoint.txid[..]); + eng.input(&smallest_outpoint.vout.to_le_bytes()); + eng.input(&sum_pubkeys.serialize()); + InputsHash::from_engine(eng) + } + fn to_scalar(self) -> Scalar { + // This is statistically extremely unlikely to panic. + Scalar::from_be_bytes(self.to_byte_array()).expect("hash value greater than curve order") + } +} + +fn decode_spent(buf: &[u8]) -> Result>, Error> { + let mut r = bitcoin::io::Cursor::new(buf); + let txs_count = bitcoin::VarInt::consensus_decode_from_finite_reader(&mut r)?.0 as usize; + let mut block_result = Vec::with_capacity(txs_count); + for _ in 0..txs_count { + let outputs_count = + bitcoin::VarInt::consensus_decode_from_finite_reader(&mut r)?.0 as usize; + let mut tx_result = Vec::with_capacity(outputs_count); + for _ in 0..outputs_count { + let output = bitcoin::TxOut::consensus_decode_from_finite_reader(&mut r)?; + tx_result.push(output); + } + block_result.push(tx_result); + } + Ok(block_result) +} + +// !!! The code below has been vendored from https://github.com/cygnet3/spdk/blob/ce68148d8621e58a33abe251e28b7587b04998dd/silentpayments/src/utils/receiving.rs !!! + +/// [BIP341](https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki)-defined 'Nothing Up My Sleeve' point. +pub const NUMS_H: [u8; 32] = [ + 0x50, 0x92, 0x9b, 0x74, 0xc1, 0xa0, 0x49, 0x54, 0xb7, 0x8b, 0x4b, 0x60, 0x35, 0xe9, 0x7a, 0x5e, + 0x07, 0x8a, 0x5a, 0x0f, 0x28, 0xec, 0x96, 0xd5, 0x47, 0xbf, 0xee, 0x9a, 0xce, 0x80, 0x3a, 0xc0, +]; + +// Define OP_CODES used in script template matching for readability +const OP_1: u8 = 0x51; +const OP_0: u8 = 0x00; +const OP_PUSHBYTES_20: u8 = 0x14; +const OP_PUSHBYTES_32: u8 = 0x20; +const OP_HASH160: u8 = 0xA9; +const OP_EQUAL: u8 = 0x87; +const OP_DUP: u8 = 0x76; +const OP_EQUALVERIFY: u8 = 0x88; +const OP_CHECKSIG: u8 = 0xAC; + +// Only compressed pubkeys are supported for silent payments +const COMPRESSED_PUBKEY_SIZE: usize = 33; + +#[derive(thiserror::Error, Debug)] +enum SpError { + #[error("invalid vin: {0}")] + InvalidVin(&'static str), + #[error("secp256k1 error: {0}")] + Secp256k1Error(#[from] secp256k1::Error), +} + +/// Get the public keys from a set of input data. +/// +/// # Arguments +/// +/// * `script_sig` - The script signature as a byte array. +/// * `txinwitness` - The witness data. +/// * `script_pub_key` - The scriptpubkey from the output spent. This requires looking up the previous output. +/// +/// # Returns +/// +/// If no errors occur, this function will optionally return a [PublicKey] if this input is silent payment-eligible. +/// +/// # Errors +/// +/// This function will error if: +/// +/// * The provided Vin data is incorrect. +fn get_pubkey_from_input( + txin: &bitcoin::TxIn, + spent_txout: bitcoin::TxOut, +) -> Result>, SpError> { + use bitcoin::hashes::{hash160, Hash}; + + let txinwitness = &txin.witness; + let script_sig = txin.script_sig.as_bytes(); + let script_pub_key = spent_txout.script_pubkey.as_bytes(); + + if is_p2pkh(script_pub_key) { + match (txinwitness.is_empty(), script_sig.is_empty()) { + (true, false) => { + let spk_hash = &script_pub_key[3..23]; + for i in (COMPRESSED_PUBKEY_SIZE..=script_sig.len()).rev() { + if let Some(pubkey_bytes) = script_sig.get(i - COMPRESSED_PUBKEY_SIZE..i) { + let pubkey_hash = hash160::Hash::hash(pubkey_bytes); + if &pubkey_hash[..] == spk_hash { + let pubkey = PublicKey::from_slice(pubkey_bytes) + .map_err(SpError::Secp256k1Error)?; + return Ok(Some(PerInput::new(txin, pubkey))); + } + } else { + return Ok(None); + } + } + } + (_, true) => return Err(SpError::InvalidVin("Empty script_sig for spending a p2pkh")), + (false, _) => { + return Err(SpError::InvalidVin( + "non empty witness for spending a p2pkh", + )) + } + } + } else if is_p2sh(script_pub_key) { + match (txinwitness.is_empty(), script_sig.is_empty()) { + (false, false) => { + let redeem_script = &script_sig[1..]; + if is_p2wpkh(redeem_script) { + if let Some(value) = txinwitness.last() { + match ( + PublicKey::from_slice(value), + value.len() == COMPRESSED_PUBKEY_SIZE, + ) { + (Ok(pubkey), true) => { + return Ok(Some(PerInput::new(txin, pubkey))); + } + (_, false) => { + return Ok(None); + } + // Not sure how we could get an error here, so just return none for now + // if the pubkey cant be parsed + (Err(_), _) => { + return Ok(None); + } + } + } + } + } + (_, true) => return Err(SpError::InvalidVin("Empty script_sig for spending a p2sh")), + (true, false) => return Ok(None), + } + } else if is_p2wpkh(script_pub_key) { + match (txinwitness.is_empty(), script_sig.is_empty()) { + (false, true) => { + if let Some(value) = txinwitness.last() { + match ( + PublicKey::from_slice(value), + value.len() == COMPRESSED_PUBKEY_SIZE, + ) { + (Ok(pubkey), true) => { + return Ok(Some(PerInput::new(txin, pubkey))); + } + (_, false) => { + return Ok(None); + } + // Not sure how we could get an error here, so just return none for now + // if the pubkey cant be parsed + (Err(_), _) => { + return Ok(None); + } + } + } else { + return Err(SpError::InvalidVin("Empty witness")); + } + } + (_, false) => { + return Err(SpError::InvalidVin( + "Non empty script sig for spending a segwit output", + )) + } + (true, _) => { + return Err(SpError::InvalidVin( + "Empty witness for spending a segwit output", + )) + } + } + } else if is_p2tr(script_pub_key) { + match (txinwitness.is_empty(), script_sig.is_empty()) { + (false, true) => { + // check for the optional annex + let annex = match txinwitness.last().and_then(|value| value.first()) { + Some(&0x50) => 1, + Some(_) => 0, + None => return Err(SpError::InvalidVin("Empty or invalid witness")), + }; + + // Check for script path + let stack_size = txinwitness.len(); + if stack_size > annex && txinwitness[stack_size - annex - 1][1..33] == NUMS_H { + return Ok(None); + } + + // Return the pubkey from the script pubkey + return XOnlyPublicKey::from_slice(&script_pub_key[2..34]) + .map_err(SpError::Secp256k1Error) + .map(|x_only_public_key| { + Some(PerInput::new( + txin, + x_only_public_key.public_key(secp256k1::Parity::Even), + )) + }); + } + (_, false) => { + return Err(SpError::InvalidVin( + "Non empty script sig for spending a segwit output", + )) + } + (true, _) => { + return Err(SpError::InvalidVin( + "Empty witness for spending a segwit output", + )) + } + } + } + Ok(None) +} + +// script templates for inputs allowed in BIP352 shared secret derivation + +/// Check if a script_pub_key is taproot. +fn is_p2tr(spk: &[u8]) -> bool { + matches!(spk, [OP_1, OP_PUSHBYTES_32, ..] if spk.len() == 34) +} + +fn is_p2wpkh(spk: &[u8]) -> bool { + matches!(spk, [OP_0, OP_PUSHBYTES_20, ..] if spk.len() == 22) +} + +fn is_p2sh(spk: &[u8]) -> bool { + matches!(spk, [OP_HASH160, OP_PUSHBYTES_20, .., OP_EQUAL] if spk.len() == 23) +} + +fn is_p2pkh(spk: &[u8]) -> bool { + matches!(spk, [OP_DUP, OP_HASH160, OP_PUSHBYTES_20, .., OP_EQUALVERIFY, OP_CHECKSIG] if spk.len() == 25) +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + use std::path::Path; + + use bitcoin::{hex::prelude::*, key::TweakedPublicKey, ScriptBuf}; + use hex_lit::hex; + use secp256k1::SecretKey; + use serde::Deserialize; + + use super::*; + + #[derive(Deserialize)] + struct TestVector { + bip352_tweaks: Vec, + } + + impl TestVector { + fn read>(path: P) -> Result> { + let data = std::fs::read(path)?; + Ok(serde_json::from_slice(&data)?) + } + + fn assert_equal(&self, indexed: &IndexedBlock) { + assert_eq!(self.bip352_tweaks.len(), indexed.rows.len()); + for (hex_tweak, row) in self.bip352_tweaks.iter().zip(&indexed.rows) { + assert_eq!(*hex_tweak, row.tweak.serialize().to_lower_hex_string()); + } + } + } + + #[test] + fn test_index_signet_block_200000() -> Result<(), Box> { + const TXS_COUNT: u32 = 141; + + // Generated by `curl -v http://localhost:38332/rest/block/0000007d60f5ffc47975418ac8331c0ea52cf551730ef7ead7ff9082a536f13c.bin`. + let block_bytes = BlockBytes(std::fs::read("src/tests/signet/block_0000007d60f5ffc47975418ac8331c0ea52cf551730ef7ead7ff9082a536f13c.bin")?); + assert_eq!(block_bytes.txs_count(), TXS_COUNT); + + // Generated by `curl -v http://localhost:38332/rest/spenttxouts/000000182c6f19960871023d851d2b758fc1123aa14645c56666e54673386780.bin`. + let spent_bytes = SpentBytes(std::fs::read("src/tests/signet/spent_0000007d60f5ffc47975418ac8331c0ea52cf551730ef7ead7ff9082a536f13c.bin")?); + assert_eq!(spent_bytes.txs_count(), TXS_COUNT); + + let mut txnum = TxNum(100000); + let indexed = index(&block_bytes, &spent_bytes, txnum)?; + txnum.increment_by(TXS_COUNT); + assert_eq!(indexed.next_txnum, txnum); + + // Generated by `bitcoin-cli -signet getsilentpaymentblockdata 0000007d60f5ffc47975418ac8331c0ea52cf551730ef7ead7ff9082a536f13c`. + // (https://github.com/Sjors/bitcoin/pull/86/changes/03ce1ad0a58c044937f51d67e5cad5ccc3f206b0) + TestVector::read("src/tests/signet/bip352_tweaks_0000007d60f5ffc47975418ac8331c0ea52cf551730ef7ead7ff9082a536f13c.json")?.assert_equal(&indexed); + Ok(()) + } + + #[test] + fn test_scan_signet_block_295125() -> Result<(), Box> { + let secp = secp256k1::Secp256k1::new(); // TODO + + let block_bytes = BlockBytes(std::fs::read("src/tests/signet/block_00000012c5a8005cd56f2cb97334b038fc6dc442c1c1682ba9065e4402b3eaa0.bin")?); + let spent_bytes = SpentBytes(std::fs::read("src/tests/signet/spent_00000012c5a8005cd56f2cb97334b038fc6dc442c1c1682ba9065e4402b3eaa0.bin")?); + let indexed = index(&block_bytes, &spent_bytes, TxNum::default())?; + assert_eq!(indexed.next_txnum, TxNum(68)); + TestVector::read("src/tests/signet/bip352_tweaks_00000012c5a8005cd56f2cb97334b038fc6dc442c1c1682ba9065e4402b3eaa0.json")?.assert_equal(&indexed); + + let scan_sk = SecretKey::from_slice(&[0x01; 32]).unwrap(); + let scan_pk = scan_sk.public_key(&secp); + let spend_sk = SecretKey::from_slice(&[0x02; 32]).unwrap(); + let spend_pk = spend_sk.public_key(&secp); + + use silentpayments::receiving::{Label, Receiver}; + + let change_label = Label::new(scan_sk, 0); + let recv = Receiver::new( + 0, + scan_pk, + spend_pk, + change_label, + silentpayments::Network::Testnet, + )?; + + let txs: HashMap = block_bytes + .txdata() + .into_iter() + .map(|tx| (tx.compute_txid(), tx)) + .collect(); + + let mut found = HashMap::, Vec<(XOnlyPublicKey, Scalar)>>::new(); + for row in indexed.rows { + let txouts = &txs.get(&row.txid).expect("missing tx").output[..]; + let shared = row.tweak.mul_tweak(&secp, &scan_sk.into()).unwrap(); + // Scan transaction for relevant P2TR public keys and key tweaks + let matches = recv.scan_transaction(&shared, p2tr_pubkeys(&txouts))?; + for (label, items) in matches { + found.entry(label).or_default().extend(items.into_iter()); + } + } + assert_eq!(found.len(), 1); + let pubkey = XOnlyPublicKey::from_slice(&hex!( + "dbd93fdd869e3522405749a594c2e3f4833ac98d0f4e70da6e7294f6623258c3" + )) + .unwrap(); + let tweak = Scalar::from_be_bytes(hex!( + "78258954dccdba6597729dab70068bc4353ebd046f7156d9a3f8db8438b62aa5" + )) + .unwrap(); + assert_eq!(found, HashMap::from_iter([(None, vec![(pubkey, tweak)])])); + + // Verify that tweaked spending secret key matches tweaked public key + let tweaked_spend_sk = spend_sk.add_tweak(&tweak).unwrap(); + let tweaked_spend_pk = tweaked_spend_sk.x_only_public_key(&secp).0; + assert_eq!(tweaked_spend_pk, pubkey); + + // Verify the relevant transaction (93a9b81f81244f8e6be29d8d6b0a9dbe6d6de6d2d4b018001ebf855bc870be88): + assert_eq!( + block_bytes.txdata()[33].output[0].script_pubkey, + ScriptBuf::new_p2tr_tweaked(TweakedPublicKey::dangerous_assume_tweaked(pubkey)) + ); + Ok(()) + } + + fn p2tr_pubkeys(txos: &[bitcoin::TxOut]) -> Vec { + txos.iter() + .filter_map(|txo| { + let script = &txo.script_pubkey; + if !script.is_p2tr() { + return None; + } + let bytes = &script.as_bytes()[2..]; + Some(XOnlyPublicKey::from_slice(bytes).expect("invalid public key")) + }) + .collect() + } +} diff --git a/bindex-lib/src/tests/signet/bip352_tweaks_00000012c5a8005cd56f2cb97334b038fc6dc442c1c1682ba9065e4402b3eaa0.json b/bindex-lib/src/tests/signet/bip352_tweaks_00000012c5a8005cd56f2cb97334b038fc6dc442c1c1682ba9065e4402b3eaa0.json new file mode 100644 index 0000000..d6e3c17 --- /dev/null +++ b/bindex-lib/src/tests/signet/bip352_tweaks_00000012c5a8005cd56f2cb97334b038fc6dc442c1c1682ba9065e4402b3eaa0.json @@ -0,0 +1,45 @@ +{ + "bip352_tweaks": [ + "022d383831d3f689d94e44b906801d6fea6f98bf8d17e7ef0b49ec5515dd105702", + "035991fb9d25be112cad28e562a70e5a4c27f56fd7abdeab4eace2059368efa416", + "026aa748d7237396fbc32106e239804b0964a15b323aa15d2d9d43101d5726bc09", + "038a3fa263797330fad2a672632f40586e4d4cf0d5c08722ee3ca053a2952e3012", + "0219c549bc9c4fb4a24899d13e800746af15eceb33cb9924340163fb1334c6d15f", + "039edecdce943720eb28567a72b44bc8d93546ba817457e818cd6bac36b5e7219d", + "025e2501b6d12f07eb711b6c34a8ac16ff779026c96858334650c74da1c3593caf", + "03cbfe148d589f721a8c7e19efd3e995e031d3a4c738f099687bfc0056d08d75bb", + "036a07996051db70d0229e8f19b045d4498c07dac13a9ab4c40134e2f1f6430521", + "026003edb12a497984bfa6f1cbb78a54f2c0b149863358eb3bd35219d72f5dd67b", + "0350c67fec92c55a9df97e0e302788bc187ab0a254c172558338230e1fef65fa06", + "03b2680a3c5f68e51a275ac98180f737662fe3f45d3b74ce5d4ee1c70b908f345b", + "0390554e8aa0154cc528048ff44a593bf0b884a3bde14c8befe8ac37d61d51544f", + "030b5ab98a026781783130d6324e112abba9e2ac77a4a6914da8d62aa7b26501bd", + "03c831f8afad6c819c9bf04528a2e308f6ebee5e2a51d0168e73d9c74bff94fb68", + "035bd0e67734121fcb64c8f6c377c69fa2ca80403cd4efd3d8a55579086ace31c8", + "0383f6f4a90d2923709ee2b58660ad7c660af7451a7019c77b763c32b63ad7d622", + "02c69288357ad98bf076ae4ee330203036c82fa7c8ddcdfa1079f4bbd3076da048", + "02294a7a5ecf578915a64adb8ad8425d386d02d8a01a4b4b114a145a7ee714a8a6", + "02e6275c8bb15e040e20e43ddc8ca4fed9560332850c52f370437b9f0f340aef5b", + "0368cdbadf7533556e61b028f093965891c040b3ef2d4da4faa8bf4779f3c588c4", + "027f5510cb82d5c4131c2f87fd63f52242e74fb4deea6507b4ce79faa2975080b1", + "02ceb3b2c7cd93eafe804572d4c4a68f3e414550a76e463c277044bb907bd2ee2e", + "02b5f742bc0797e946d1116d7a7b307a5ab8ebd6dce5feeb0b0298c291cf9ef6cf", + "026c26c9269b0c2e80111041c697d94b3794e28c9ee16a23e227ece99a2080a44b", + "02c9770ef6fa5b37ae4fc9ba96cea5a01916f86534bf70fb825a9c8d0f1a5e7229", + "03aa7a0b46173bb065b18540531a322d867658c3d83f4f814463314e1bff49cf41", + "03bec2bc1aa51d7358e0f953a111d065e23199a412dcf1c2fca97dc8f31fc51e29", + "03dbcf1530095f02b2a6224dbbae374ff6c5530f23302520d9f0ec3212e3f86317", + "023420725706137fe7ee3c23075d89b85f1820f7b7705e6b07f70daa19e2c6220c", + "03f0670573266838a53b6ffdffa39ddc33434ddccf7586f5f6509d839d4afd6f2c", + "0219c926ccb4bb4951a1d135ad5026dee31cd9b8223ee4c856020fbc64f4e7bdee", + "03c0ab6f8eb36b632e2cd11e335047bd79509d24579203cee218ebec36a31424df", + "02b3c053a3062e080442934dd68e4af2d19ca7db4f20a540fa617a4720d80adf40", + "0334732e4dd68b76886a4946b45362e1215bfbf97022f7fb1c9c5dc3bd6620982c", + "03d503aca25df261584ae2f8301c5396e424c383c1579669fb8948067bb4bf2d92", + "03dc78d61786ba913ad9fb092affc2e5bb8c9116b46c9afaf447bc98234bb9dd53", + "03666dc73e06cacbfd5be9075c0b02f480bbf2704aa01fa0e7a8285d35691017af", + "033c2b09193fabc90899a3544689f7c9c87e63e09d4c885fcf5a741e570f7b6831", + "03ae62b23c656554382b575be8769cfe50d3aadf032defaa4e211fc7c4b360a34d", + "03ece0b9796b140486fb9e575b036ff60d83137d2655c521ba4a0f782636473a2b" + ] +} diff --git a/bindex-lib/src/tests/signet/bip352_tweaks_0000007d60f5ffc47975418ac8331c0ea52cf551730ef7ead7ff9082a536f13c.json b/bindex-lib/src/tests/signet/bip352_tweaks_0000007d60f5ffc47975418ac8331c0ea52cf551730ef7ead7ff9082a536f13c.json new file mode 100644 index 0000000..00790bf --- /dev/null +++ b/bindex-lib/src/tests/signet/bip352_tweaks_0000007d60f5ffc47975418ac8331c0ea52cf551730ef7ead7ff9082a536f13c.json @@ -0,0 +1,137 @@ +{ + "bip352_tweaks": [ + "02c9c9bb0f8fa49b5764f270422167efd670b8b04136197fa63ebe4373368c00c4", + "0366e880f4692ede6566623f5b281e42383eb5b95e320daff3553f96d57f2a3feb", + "030704f3420f149449c560579504638f939447027eaa6d4ff5da29a97bd27dfb17", + "03c2d2819be65672792877837979a7d96e36d5cadb3265c7673c240f88a9a4ddaf", + "02ce9b5b1964cf1aa4ed3d90976a61410d54247eaeba84198546597eed510372e7", + "03e4f93bb98c65e9883993ab79a043900b8f53a1b42b071bda8baf7f06c753c6c4", + "0316f0e50d31b9de603f49d6885b6f0dcdb8d3665142eb56829afa43e39fb7597d", + "03ddc2f478ffa0c68d36a127fd849be7b6e8a4c6e90f01a81b89e306aa8fd04f78", + "026d2b50668aa7a984f15770cbf36d45e6ffb005b85ef0231e99d6a0883ed81a51", + "032a7bdb54702ac4de644d6ff8e4dce56614962e25bbd844df24235ccc45c8f5d3", + "02c2447bd83405c73d575652c98a657c5f6b13ba41310f7b4cac2702c32e48be45", + "036f6ed72058da780dd656985fe221e14baa6f60af04c7bdcaa0444a30c2a47a0d", + "0258f8eb1fc01e2d9a568a84b42abea378ac66a60988304da52d75732106021ad8", + "02b3bbdd0935f99b08131f6737919a3644b1bd03cbbb7733a64e7d1a437d020337", + "0251a6e4369d95b1e8405d5916d51dee2eb1aa0ece7180e181957e8b21e952f216", + "026323d754a42b9a83ece5fce2c8bddae7b852fe2c5ec5d548a49576c22ac9b71c", + "023a65e36c86441044a1caa8d2f34960d5c9e14d674a4899698b8b21567a6e2d46", + "0271beefbf630b67b6d70b7f0456577a184b0df31b47ff03920cdc1f6c5594c3c1", + "02ee54e6405635a6ca58b59ae97e42dca7257f75e08a9facd2940348b8681aad1f", + "0310c2ee7d47daae8c7c9473d3cbaf316b5340fb11dcf887a0c57b92e85ede2530", + "02dd4e48ad3b9614844a5fa4ab25b616bdf0ca1e301914b001b219154ddb9adb4f", + "022996a98ee8e3d479f1abd0a9049c3a33bd7339ba20f6f2494c5a394e4a46748d", + "030eff7ad74348ae7efb0988f6380424ecc8054b6106d0e76d09e59e4acbb92b0b", + "03b7574fb604bb47a3e0327beeff1c375acb80a263aabb88deebff62c312915293", + "02f8e7d7bc4e0c23e10a57777be27ceea8cd33a3ab7446b616c4aaff1dd403a69d", + "021016db808e78aba8a691962c94580441e260ba8ae21bbff255f81d34b7985f08", + "022759558e4ba8028237822539f76356282170f969d2d876b1aef69c4ae32528f2", + "02433cb67b8072170ead3aad80882fa8786b62f6ee26bedf8ff8863f2cc16d967d", + "038000615515c9478b79d3c7122c60494b747306812639d57f1bdce9b133970c48", + "037093fde98aad30bd59413c92fdb1b751662ba71148fe41e06b8f32f1b4802e48", + "03fd2c80ceda3ca4917498db0a3f0f80e96933873673b4cbe8f9486295b5f1d4db", + "03cb187bbfaa0f32ff89539db03d58a3f295befce6081fc0dd858dc293b25d0a47", + "02222d381b3803cac0eb58f11dca602342ea836650a7d64640d4c4aac388c811ab", + "03e5457796bf604d9a7e21499506169b3232af52aa80e8c7aa01e512fd2d5db9c4", + "02084685528b2e03452eefb2225e8a177c739fb52ec331ba0e7e135146af283fe4", + "0202c6acd88b9f1695cc65a3ef870e0f1628279d4fce54baece651b530b05db9e1", + "03e7be9884e4ca3818b8db65b6f50d31a5fdb9a201817d879fb3e6718aa32f0d91", + "038138a86c5f194d77f9177d28979286bee590f69a8d446208fd7347d304fd278f", + "0256192ca85ae444b54b89dd3bf86141d0c3c5277ccd6f6a09ed539b7e27603084", + "03fb42e4e7cf46ad7526257b6fae07f0af543b83bbf90b6c4ff05b190bb5c034bf", + "028b9a21fdd6d6ce29d41afa87ccd153b0b26996dfde85d3cdbb7831aa7654395e", + "02e3489bd491018a4eba5be626bf4f2bd2734343fc3d985f844ac3e2bed2f1df02", + "027ed731c43a0caecbf8708e0dad0b273fb3851ee2008fa2cf0319b0d2f86ca624", + "02c117f84d69b6b09882dd4e3e5f1b45097a450caeeaa1fc3e6317b591a5481ee5", + "0331a1ad83bacd4f30619dab34bae33784b4a0296071e361b6bf6477f019f86abf", + "03a8d8c12b513fb7e65e119526ce36439688f8c96fb3b5afe73210a6ba2a386593", + "03972fd270e295591a2167aaf6eacc560cc856b5f5154c1878a44b624d4c8e6238", + "032c247b76f7b70f70d2923ba10a865e8a1044fc01f44c1d9f5c11664e27737523", + "02a0014937d01335b5d2b5cfd0fd6f9d0eedfec43dbd20da03b5cb6b86521dde43", + "023e84f37a4a6a95ada3e01b1fa927472522db22b651427b240f4d283a79980403", + "03bab083cf584dcd91891b939de6cdbe7bef4f0ec195001a3603c24b3a4751147a", + "0237e648dd9aee98c2f382b71c4394b678d78e650593b075ac7a2ff6adc0bcda99", + "0260f174ae6d7f60d7bd546a35e6f8d1b7eef26f85b58cd42ded3a7f04d6219e4b", + "02bb0a19ec30c25d0848c44b7a32b278fc8763905399182a9a3e1dd726ae68e9fb", + "03ec88e79fb4862d55cfac97321d19d6d9b1ea046919b96eb1a3e7b8d29465ed40", + "03f033a2c645ed20d3ecd71a7fbf3da56b5618a5e171c94061a4fa3d365d2fd663", + "035fdd2fbb3d9a22f5b9cea7d3e328d2fd2eb66c3d2ac5a9d24118db2355a129f5", + "036cdcb77a6d06a9cee0565dafe926c8b498c4bab7483615c4027e9c15f70e3bc0", + "03cdd4899fd3d66b4828773c17eb525c945c854b6b18eb113017196a09fe708470", + "0289b5e8948d23459c8f922922d056cef4fe2e5bb0a0a28ed7f3ceb76084644892", + "0235e7a9de22616072730eb42a02868eb536a2453a8dc2993ccb177777ba0eb5f2", + "0201c1c7326a046c478e381d247eeeb2d5e2c98a54fdbb31cd0feaba26af9d1c8d", + "0265498ad722424c660764452f3951de5e2787a505fcfbfd56cc154bbf7a4e3bd3", + "03e234a3ec81e65ea5dce6e4c4f24abc4129b5aecb03576f7e438c1ab53b3c3105", + "03fab2ac486726cce36bdfeac029cfecb78728f9c40ee3b62a66bb459533b99406", + "030f95f733365b4258301b363228a396db32b09a5e240450154306d24cdd7eee82", + "0398f913843fd319bc6514e48174a542eefeeed7d27ab7d89266ef1551ad55c5ab", + "03705b4f5518e09eca1ab5c0b7887b9054233b75e3d2b64d8e657d88fb76660fe4", + "025c3556997b7a14387d2365c3840c642063e066dc21be4b8317aed889e1e73921", + "03d502d6ae7428752e621c44fa47f6eb4ae40a3759a50f2e1519d64d0e90db3423", + "02881f8c5d3333b275083aeb4abe53c6d1a27828de7f55b13a6c749fe2bb3b54ea", + "022f4a2268d81b24aaaea54b7dc0bc15caa2b9bc449cd1943aee9df0e26ee17fa5", + "02eb8c6cf5efa49038d0e860b7222099d314c8b19f99003cdf5df43dfa80bf147d", + "034cc01ddfecc2da162186f3a05d36b137ce96b270a1bfd84a9f1147d0991fab1d", + "0353daeec86093fafb2c6c96c35e24d38677cd8e098a5eb9aadfdca97088cc49c3", + "034a279ab495321bb5e18b9c30fd3251bf2b9bf0161e6b019ea689b3853ecea319", + "030ce42b735439577e5ad1224c46125730f913862b47aa4443904e313431f01663", + "028a2a492bfbbc79feba2f8901edc009a85d8586bedb5fa1388e51d92302b648c1", + "03761483ed7ebc1a74c0a5fd141990fee076913d5272534f46f26c66cbcd81c23c", + "03858c3bf6cfcaea514729ed3a5f22aedcf09df3682271f9e4fafc8eec193c8e09", + "025ddaf0f80d24c052b27eb780c4ba826d8fc3cb26e432be9b0f1086247af8487f", + "0303eb410d05d5ef78f3a7d586f13a2d6e8170a24dc4429162642ccdbdaed33288", + "02d7ad98021f6cd989fa3877972cce6d7b87218a3bfb35aab7f07024eb97a5db3d", + "02f15aedd02bd340c2794b39cbcf670647ca17cbfe4afb258ffd36bcddbae3411a", + "03b8857c65024d4d1b8ad66bea0d2799b81ccec0e950bb2c9da0cb009e07e184d9", + "039941f4d7fb5f0df2c51e89d6e25b2e7f45740d6999ee93fae7714b687b09fd1c", + "033d588c745f0ffcfd32cc071d5a10c6b6b29280a2b5cf812eea8df17a7324a39c", + "0271a5b1f152953958da45f07dffe4822a223c54a1805d6e87d6e75d0b9bd970a1", + "03f2c6db54cb3253ba608fc56cde630790f30f1c5c323ee4516b51c11ab39adaa1", + "03c65e00a4fba30534370846006c2257bad216109f17dd1f3815ae4e0d4022159e", + "038c8d0fedb9181665dfe3fb12650f79f368e5dfe7a93c843bf0bbe5925b044195", + "02ee2f8ee415bdffeae56611ae3c7f726fb0dfaf1fc57d9853b6e88c6ab77ec75e", + "0246681b89eb8cf62381007275667095ea9a935a90a654196921f49426b8659c6a", + "0259cf283026e962be0cd2d13d7c0211309fe3aeccc3e61bb792da08ef3bf46fdb", + "0357db4f7881743ad33e2f5b861957fae024e872dc94a3a08cf148424b01102786", + "0263a32a727dec6f97cda1043473dd243a92e377bacc244ad09c9958373dce82bb", + "02a11406abb94316f1ee3fee837af6c6b97486e3b938f712ac87aec030e16c4907", + "02869df18b8720a62c0e1e62792622c142d065613379ab093cc240047e6d4b08a3", + "0325fc187462b2186d3905fc8d60892ce2ce0b9dc4a1bb055590a5f08ec7cffbd1", + "030d210c4ac195d760e1dbbfea93abda6376b78a3202aaa33541bfe41300191381", + "02555073a3269162a2d5249ebd1c4c86048e72be8966c74157fb573fcd7c5b7100", + "02e95bfb6b13d086da146621275588b5baacf477d299672fc876b9f52bb8fd651c", + "0318cd2021b62599653aaf1835efa935672b9d40cf2be4998e062f9db58b37e8c2", + "021a957fe1b7d92bd21b3e512140e281f10d6665f9b52c601124e9e5b656a0ca8c", + "0241db6680bde1daf76066d48921a7f96a58944d6cbafa732650cd3e4f5b67169f", + "02d91743e0096a27170433d467b07da9e31460d1a780c3a873a06c9b21113fa01c", + "03368cbca3f6ac55b918075ae9b099a800039c30c6586b5dd1164475a6e1335c3f", + "03d990de185a4d92d95d93f53d2edd267a9d0e9a1dcf26f39daeee417517c8b468", + "029f422caaa27b89a3503f294cc562ca2022e7a56e47c12dd712dea4cb6a9ce434", + "0243c2f5fabb2f780feae7b7b2541aea5992a8e0e16d35e610e66fc7ee3cca6a23", + "03a7afb1d1f17f2412bcd3695e76b33cbaa8691916bc6dd975c13586ace80deaed", + "03be0b834d17ac5544fc1aed69d05558c43c7683538289ed7afa3ff851ceba9b40", + "035effa2318e1903e7a6dc91b48c33f15923da1f6a76bd90e9099e6b7596e74b37", + "039041f0d5ff6c5cd622b82c5646f47365d086fc039113d3b2c88a1a8c5df9cb1f", + "020013dc06002d3e29329d77d499e21fa6f91acab357d37fd584c6e504c4072fcc", + "03f95fcb3f028f9a442b40a6e9fac648f6991dfdf74fbe960ff603e557bb70afff", + "03b2a6821fd7fd922d2d1b01a86943682a6dfc09e138ceb5487b96f3b8206be174", + "02c9db11e0f7fbed82494850251a5fc4c6a4b1d6c7e20f2b4e62587058231dd0bb", + "03734286dfa6ab9d49ab62f32e62d40efb62a20e21c1556ce769b2e9435b0b8622", + "0237bf777276886f535301404bd6e66f2c20bfb24f118da63dd3a3c8087fa1e5ca", + "034a76a1e0402993e08f17f93c5f3aa55234f6aca41d2a7466037be2b6787707b9", + "027f9aebed86b24ba9aa98ee1db7cb08088cbc51bd3d0783f5fdfba108224ee87b", + "027e0bac3567ed4fae71c5801f6b9dff875cce37eada972180318b32cc2043dd6d", + "03ce9091820e60cd67e9e83cb8fa68f082159f965bde3dae6c817ae4894e18d611", + "03fe6c26f0bb4556a1859f94258d0ee4567a7c9ac3b93578de34108e089b0b61c0", + "039c0ac051541b22a75e85b32d77734c5d87e42caa45b20aeaddd47914806e3202", + "02e2fbfb9ac5200da2a639fa124589761169c4db6274dec4b177b8f9bc057ca182", + "03b4a78783c0e78e1eeff3da6a5f5686f69fadd816fce0b2ccdde797842f9d4c51", + "03905bf5fb95ff41ef7eb16bf2c44f254031067f729ed8f324acad092f16982d48", + "034c1eab5dc472b0ad1427282e250755682b73a1d2d370549e493ccefb2ef460b0", + "036d2bc52b826a63691ec72a6c28699c97be64e2e6ea7a53f017e4099a1d22a08a", + "0305e2a46486e45e9b603388a25ed27d42ae899ebf2867537de337672e1db41254", + "028963170ea21fea0494210b48a93f046e71a40451cbe159e83e2d17de6a56866f" + ] +} diff --git a/bindex-lib/src/tests/signet/block_00000012c5a8005cd56f2cb97334b038fc6dc442c1c1682ba9065e4402b3eaa0.bin b/bindex-lib/src/tests/signet/block_00000012c5a8005cd56f2cb97334b038fc6dc442c1c1682ba9065e4402b3eaa0.bin new file mode 100644 index 0000000000000000000000000000000000000000..b93d3f6a1ac26e04b550398a5cb38857de77c1e1 GIT binary patch literal 26529 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

#rhPi(Ps<{lVg^3fIDsUs^@nSM5%!O6&Qm=hVr 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 literal 0 HcmV?d00001 diff --git a/bindex-lib/src/tests/signet/spent_0000007d60f5ffc47975418ac8331c0ea52cf551730ef7ead7ff9082a536f13c.bin b/bindex-lib/src/tests/signet/spent_0000007d60f5ffc47975418ac8331c0ea52cf551730ef7ead7ff9082a536f13c.bin new file mode 100644 index 0000000000000000000000000000000000000000..86d29472c373d260deff5d2cf80d8dec46cc8320 GIT binary patch literal 4707 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 F{{eeFWF!Cp literal 0 HcmV?d00001 diff --git a/bindex.sh b/bindex.sh index 7698a63..dd7f084 100755 --- a/bindex.sh +++ b/bindex.sh @@ -4,6 +4,6 @@ set -eux export RUST_LOG=${RUST_LOG:-info} cargo +stable build --release --all --locked -ARGS="--db-path ./db" +ARGS="--db-path ./__db_sptweaks" ulimit -n 8192 target/release/bindex-cli $ARGS $*