Repository: rust-bitcoin/rust-bitcoin
Pull request #1244 (2022-09-06 08:32:04)

BIP 34: Check last minimal encoding accoriding of heights
See: https://github.com/bitcoin/bips/blob/master/bip-0034.mediawiki

The BIP was updated with commit: https://github.com/bitcoin/bips/commit/08844fd6eff55e756c453c6d2e16de40e8f80e73

Found while reviewing #1240 

Also cleanly deals with negative heights as errors. 

dpc: I'm confused if "accoriding of heights" is just a mistake, or there's some meaning there that I'm missing.

Patch:
diff --git a/src/blockdata/block.rs b/src/blockdata/block.rs
index 94ab2c20..e11fbd6c 100644
--- a/src/blockdata/block.rs
+++ b/src/blockdata/block.rs
@@ -317,7 +317,7 @@ pub fn bip34_block_height(&self) -> Result<u64, Bip34Error> {
         // Citing the spec:
         // Add height as the first item in the coinbase transaction's scriptSig,
         // and increase block version to 2. The format of the height is
-        // "serialized CScript" -- first byte is number of bytes in the number
+        // "minimally encoded serialized CScript"" -- first byte is number of bytes in the number
         // (will be 0x03 on main net for the next 150 or so years with 2^23-1
         // blocks), following bytes are little-endian representation of the
         // number (including a sign bit). Height is the height of the mined
@@ -331,14 +331,14 @@ pub fn bip34_block_height(&self) -> Result<u64, Bip34Error> {
         let input = cb.input.first().ok_or(Bip34Error::NotPresent)?;
         let push = input.script_sig.instructions_minimal().next().ok_or(Bip34Error::NotPresent)?;
         match push.map_err(|_| Bip34Error::NotPresent)? {
-            script::Instruction::PushBytes(b) if b.len() <= 8 => {
-                // Expand the push to exactly 8 bytes (LE).
-                let mut full = [0; 8];
-                full[0..b.len()].copy_from_slice(b);
-                Ok(util::endian::slice_to_u64_le(&full))
-            }
-            script::Instruction::PushBytes(b) if b.len() > 8 => {
-                Err(Bip34Error::UnexpectedPush(b.to_vec()))
+            script::Instruction::PushBytes(b) => {
+                // Check that the number is encoded in the minimal way.
+                let h = script::read_scriptint(b).map_err(|_e| Bip34Error::UnexpectedPush(b.to_vec()))?;
+                if h < 0 {
+                    Err(Bip34Error::NegativeHeight)
+                } else {
+                    Ok(h as u64)
+                }
             }
             _ => Err(Bip34Error::NotPresent),
         }
@@ -355,6 +355,8 @@ pub enum Bip34Error {
     NotPresent,
     /// The BIP34 push was larger than 8 bytes.
     UnexpectedPush(Vec<u8>),
+    /// The BIP34 push was negative.
+    NegativeHeight,
 }
 
 impl fmt::Display for Bip34Error {
@@ -365,6 +367,7 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
             Bip34Error::UnexpectedPush(ref p) => {
                 write!(f, "unexpected byte push of > 8 bytes: {:?}", p)
             }
+            Bip34Error::NegativeHeight => write!(f, "negative BIP34 height"),
         }
     }
 }
@@ -376,7 +379,10 @@ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
         use self::Bip34Error::*;
 
         match self {
-            Unsupported | NotPresent | UnexpectedPush(_) => None,
+            Unsupported |
+            NotPresent |
+            UnexpectedPush(_) |
+            NegativeHeight => None,
         }
     }
 }
diff --git a/src/blockdata/constants.rs b/src/blockdata/constants.rs
index 907e20b3..27de8845 100644
--- a/src/blockdata/constants.rs
+++ b/src/blockdata/constants.rs
@@ -305,3 +305,4 @@ fn mainnet_chain_hash_test_vector() {
         assert_eq!(got, want);
     }
 }
+
diff --git a/src/blockdata/mod.rs b/src/blockdata/mod.rs
index 2edb7883..15d44c24 100644
--- a/src/blockdata/mod.rs
+++ b/src/blockdata/mod.rs
@@ -14,3 +14,4 @@
 pub mod transaction;
 pub mod block;
 pub mod witness;
+
diff --git a/src/blockdata/script.rs b/src/blockdata/script.rs
index 5d4e7c8e..cc1155c1 100644
--- a/src/blockdata/script.rs
+++ b/src/blockdata/script.rs
@@ -225,9 +225,9 @@ fn from(err: bitcoinconsensus::Error) -> Error {
     }
 }
 
-/// Helper to encode an integer in script format.
+/// Helper to encode an integer in script(minimal CScriptNum) format.
 /// Writes bytes into the buffer and returns the number of bytes written.
-fn write_scriptint(out: &mut [u8; 8], n: i64) -> usize {
+pub fn write_scriptint(out: &mut [u8; 8], n: i64) -> usize {
     let mut len = 0;
     if n == 0 { return len; }
 
@@ -256,7 +256,7 @@ fn from(err: bitcoinconsensus::Error) -> Error {
     len
 }
 
-/// Helper to decode an integer in script format
+/// Helper to decode an integer in script(minimal CScriptNum) format
 /// Notice that this fails on overflow: the result is the same as in
 /// bitcoind, that only 4-byte signed-magnitude values may be read as
 /// numbers. They can be added or subtracted (and a long time ago,
@@ -272,8 +272,26 @@ fn from(err: bitcoinconsensus::Error) -> Error {
 /// This is basically a ranged type implementation.
 pub fn read_scriptint(v: &[u8]) -> Result<i64, Error> {
     let len = v.len();
-    if len == 0 { return Ok(0); }
     if len > 4 { return Err(Error::NumericOverflow); }
+    let last = match v.last() {
+        Some(last) => last,
+        None => return Ok(0),
+    };
+    // Comment and code copied from bitcoin core:
+    // https://github.com/bitcoin/bitcoin/blob/447f50e4aed9a8b1d80e1891cda85801aeb80b4e/src/script/script.h#L247-L262
+    // If the most-significant-byte - excluding the sign bit - is zero
+    // then we're not minimal. Note how this test also rejects the
+    // negative-zero encoding, 0x80.
+    if (last & 0x7f) == 0 {
+        // One exception: if there's more than one byte and the most
+        // significant bit of the second-most-significant-byte is set
+        // it would conflict with the sign bit. An example of this case
+        // is +-255, which encode to 0xff00 and 0xff80 respectively.
+        // (big-endian).
+        if v.len() <= 1 || (v[v.len() - 2] & 0x80) == 0 {
+            return Err(Error::NonMinimalPush);
+        }
+    }
 
     let (mut ret, sh) = v.iter()
                          .fold((0, 0), |(acc, sh), n| (acc + ((*n as i64) << sh), sh + 8));
@@ -1300,6 +1318,14 @@ fn build_scriptint(n: i64) -> Vec<u8> {
         assert!(read_scriptint(&build_scriptint(-(1 << 31))).is_err());
     }
 
+    #[test]
+    fn non_minimal_scriptints() {
+        assert_eq!(read_scriptint(&[0x80, 0x00]), Ok(0x80));
+        assert_eq!(read_scriptint(&[0xff, 0x00]), Ok(0xff));
+        assert_eq!(read_scriptint(&[0x8f, 0x00, 0x00]), Err(Error::NonMinimalPush));
+        assert_eq!(read_scriptint(&[0x7f, 0x00]), Err(Error::NonMinimalPush));
+    }
+
     #[test]
     fn script_hashes() {
         let script = hex_script!("410446ef0102d1ec5240f0d061a4246c1bdef63fc3dbab7733052fbbf0ecd8f41fc26bf049ebb4f9527f374280259e7cfa99c48b0e3f39c51347a19a5819651503a5ac");
@@ -1538,3 +1564,4 @@ fn read_scriptbool_non_zero_is_true() {
         assert!(read_scriptbool(&v));
     }
 }
+
diff --git a/src/consensus/encode.rs b/src/consensus/encode.rs
index 362a8c54..04b0fe01 100644
--- a/src/consensus/encode.rs
+++ b/src/consensus/encode.rs
@@ -1170,3 +1170,4 @@ fn test_read_bytes_from_finite_reader() {
         }
     }
 }
+
diff --git a/src/lib.rs b/src/lib.rs
index c617f095..8ea10b6c 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -80,7 +80,6 @@
 #[macro_use]
 pub mod network;
 pub mod address;
-pub mod bip158;
 pub mod blockdata;
 pub mod consensus;
 pub mod error;
diff --git a/src/network/message_blockdata.rs b/src/network/message_blockdata.rs
index 0e51748f..98e5bbc8 100644
--- a/src/network/message_blockdata.rs
+++ b/src/network/message_blockdata.rs
@@ -179,3 +179,4 @@ fn getheaders_message_test() {
         assert_eq!(serialize(&real_decode), from_sat);
     }
 }
+
diff --git a/src/util/amount.rs b/src/util/amount.rs
index 1c0f5c55..88fab77b 100644
--- a/src/util/amount.rs
+++ b/src/util/amount.rs
@@ -2238,3 +2238,4 @@ fn disallow_unknown_denomination() {
         }
     }
 }
+
diff --git a/src/util/base58.rs b/src/util/base58.rs
index d548f116..eb955469 100644
--- a/src/util/base58.rs
+++ b/src/util/base58.rs
@@ -334,3 +334,4 @@ fn test_base58_roundtrip() {
 
     }
 }
+
diff --git a/src/bip158.rs b/src/util/bip158.rs
similarity index 83%
rename from src/bip158.rs
rename to src/util/bip158.rs
index 9a954814..de3492b0 100644
--- a/src/bip158.rs
+++ b/src/util/bip158.rs
@@ -39,20 +39,22 @@
 //!  ```
 //!
 
-use core::cmp::{self, Ordering};
+use crate::prelude::*;
+use crate::io;
+
 use core::fmt::{self, Display, Formatter};
+use core::cmp::{self, Ordering};
+
+use crate::hashes::{Hash, siphash24};
+use crate::hash_types::{BlockHash, FilterHash, FilterHeader};
 
 use crate::blockdata::block::Block;
 use crate::blockdata::script::Script;
 use crate::blockdata::transaction::OutPoint;
-use crate::consensus::encode::VarInt;
 use crate::consensus::{Decodable, Encodable};
-use crate::hash_types::{BlockHash, FilterHash, FilterHeader};
-use crate::hashes::{siphash24, Hash};
-use crate::internal_macros::write_err;
-use crate::io;
-use crate::prelude::*;
+use crate::consensus::encode::VarInt;
 use crate::util::endian;
+use crate::internal_macros::write_err;
 
 /// Golomb encoding parameter as in BIP-158, see also https://gist.github.com/sipa/576d5f09c3b86c3b1b75598d799fc845
 const P: u8 = 19;
@@ -91,14 +93,16 @@ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
 }
 
 impl From<io::Error> for Error {
-    fn from(io: io::Error) -> Self { Error::Io(io) }
+    fn from(io: io::Error) -> Self {
+        Error::Io(io)
+    }
 }
 
 /// A block filter, as described by BIP 158.
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct BlockFilter {
     /// Golomb encoded filter
-    pub content: Vec<u8>,
+    pub content: Vec<u8>
 }
 
 impl FilterHash {
@@ -113,12 +117,14 @@ pub fn filter_header(&self, previous_filter_header: &FilterHeader) -> FilterHead
 
 impl BlockFilter {
     /// Creates a new filter from pre-computed data.
-    pub fn new(content: &[u8]) -> BlockFilter { BlockFilter { content: content.to_vec() } }
+    pub fn new (content: &[u8]) -> BlockFilter {
+        BlockFilter { content: content.to_vec() }
+    }
 
     /// Computes a SCRIPT_FILTER that contains spent and output scripts.
     pub fn new_script_filter<M>(block: &Block, script_for_coin: M) -> Result<BlockFilter, Error>
     where
-        M: Fn(&OutPoint) -> Result<Script, Error>,
+        M: Fn(&OutPoint) -> Result<Script, Error>
     {
         let mut out = Vec::new();
         let mut writer = BlockFilterWriter::new(&mut out, block);
@@ -186,35 +192,33 @@ pub fn add_output_scripts(&mut self) {
 
     /// Adds consumed output scripts of a block to filter.
     pub fn add_input_scripts<M>(&mut self, script_for_coin: M) -> Result<(), Error>
-    where
-        M: Fn(&OutPoint) -> Result<Script, Error>,
-    {
-        for script in self
-            .block
-            .txdata
-            .iter()
+        where M: Fn(&OutPoint) -> Result<Script, Error> {
+        for script in self.block.txdata.iter()
             .skip(1) // skip coinbase
             .flat_map(|t| t.input.iter().map(|i| &i.previous_output))
-            .map(script_for_coin)
-        {
+            .map(script_for_coin) {
             match script {
                 Ok(script) => self.add_element(script.as_bytes()),
-                Err(e) => return Err(e),
+                Err(e) => return Err(e)
             }
         }
         Ok(())
     }
 
     /// Adds an arbitrary element to filter.
-    pub fn add_element(&mut self, data: &[u8]) { self.writer.add_element(data); }
+    pub fn add_element(&mut self, data: &[u8]) {
+        self.writer.add_element(data);
+    }
 
     /// Writes the block filter.
-    pub fn finish(&mut self) -> Result<usize, io::Error> { self.writer.finish() }
+    pub fn finish(&mut self) -> Result<usize, io::Error> {
+        self.writer.finish()
+    }
 }
 
 /// Reads and interprets a block filter.
 pub struct BlockFilterReader {
-    reader: GcsFilterReader,
+    reader: GcsFilterReader
 }
 
 impl BlockFilterReader {
@@ -248,7 +252,7 @@ pub fn match_all<'a, I, R>(&self, reader: &mut R, query: I) -> Result<bool, Erro
 /// Golomb-Rice encoded filter reader.
 pub struct GcsFilterReader {
     filter: GcsFilter,
-    m: u64,
+    m: u64
 }
 
 impl GcsFilterReader {
@@ -286,13 +290,14 @@ pub fn match_any<'a, I, R>(&self, reader: &mut R, query: I) -> Result<bool, Erro
             loop {
                 match data.cmp(&p) {
                     Ordering::Equal => return Ok(true),
-                    Ordering::Less =>
+                    Ordering::Less => {
                         if remaining > 0 {
                             data += self.filter.golomb_rice_decode(&mut reader)?;
                             remaining -= 1;
                         } else {
                             return Ok(false);
-                        },
+                        }
+                    }
                     Ordering::Greater => break,
                 }
             }
@@ -330,13 +335,14 @@ pub fn match_all<'a, I, R>(&self, reader: &mut R, query: I) -> Result<bool, Erro
             loop {
                 match data.cmp(&p) {
                     Ordering::Equal => break,
-                    Ordering::Less =>
+                    Ordering::Less => {
                         if remaining > 0 {
                             data += self.filter.golomb_rice_decode(&mut reader)?;
                             remaining -= 1;
                         } else {
                             return Ok(false);
-                        },
+                        }
+                    },
                     Ordering::Greater => return Ok(false),
                 }
             }
@@ -346,20 +352,27 @@ pub fn match_all<'a, I, R>(&self, reader: &mut R, query: I) -> Result<bool, Erro
 }
 
 /// Fast reduction of hash to [0, nm) range.
-fn map_to_range(hash: u64, nm: u64) -> u64 { ((hash as u128 * nm as u128) >> 64) as u64 }
+fn map_to_range(hash: u64, nm: u64) -> u64 {
+    ((hash as u128 * nm as u128) >> 64) as u64
+}
 
 /// Golomb-Rice encoded filter writer.
 pub struct GcsFilterWriter<'a, W> {
     filter: GcsFilter,
     writer: &'a mut W,
     elements: HashSet<Vec<u8>>,
-    m: u64,
+    m: u64
 }
 
 impl<'a, W: io::Write> GcsFilterWriter<'a, W> {
     /// Creates a new [`GcsFilterWriter`] wrapping a generic writer, with specific seed to siphash.
     pub fn new(writer: &'a mut W, k0: u64, k1: u64, m: u64, p: u8) -> GcsFilterWriter<'a, W> {
-        GcsFilterWriter { filter: GcsFilter::new(k0, k1, p), writer, elements: HashSet::new(), m }
+        GcsFilterWriter {
+            filter: GcsFilter::new(k0, k1, p),
+            writer,
+            elements: HashSet::new(),
+            m
+        }
     }
 
     /// Adds data to the filter.
@@ -374,11 +387,8 @@ pub fn finish(&mut self) -> Result<usize, io::Error> {
         let nm = self.elements.len() as u64 * self.m;
 
         // map hashes to [0, n_elements * M)
-        let mut mapped: Vec<_> = self
-            .elements
-            .iter()
-            .map(|e| map_to_range(self.filter.hash(e.as_slice()), nm))
-            .collect();
+        let mut mapped: Vec<_> = self.elements.iter()
+            .map(|e| map_to_range(self.filter.hash(e.as_slice()), nm)).collect();
         mapped.sort_unstable();
 
         // write number of elements as varint
@@ -400,19 +410,17 @@ pub fn finish(&mut self) -> Result<usize, io::Error> {
 struct GcsFilter {
     k0: u64, // sip hash key
     k1: u64, // sip hash key
-    p: u8,
+    p: u8
 }
 
 impl GcsFilter {
     /// Creates a new [`GcsFilter`].
-    fn new(k0: u64, k1: u64, p: u8) -> GcsFilter { GcsFilter { k0, k1, p } }
+    fn new(k0: u64, k1: u64, p: u8) -> GcsFilter {
+        GcsFilter { k0, k1, p }
+    }
 
     /// Golomb-Rice encodes a number `n` to a bit stream (parameter 2^k).
-    fn golomb_rice_encode<'a, W>(
-        &self,
-        writer: &mut BitStreamWriter<'a, W>,
-        n: u64,
-    ) -> Result<usize, io::Error>
+    fn golomb_rice_encode<'a, W>(&self, writer: &mut BitStreamWriter<'a, W>, n: u64) -> Result<usize, io::Error>
     where
         W: io::Write,
     {
@@ -431,7 +439,7 @@ fn golomb_rice_encode<'a, W>(
     /// Golomb-Rice decodes a number from a bit stream (parameter 2^k).
     fn golomb_rice_decode<R>(&self, reader: &mut BitStreamReader<R>) -> Result<u64, io::Error>
     where
-        R: io::Read,
+        R: io::Read
     {
         let mut q = 0u64;
         while reader.read(1)? == 1 {
@@ -457,7 +465,11 @@ pub struct BitStreamReader<'a, R> {
 impl<'a, R: io::Read> BitStreamReader<'a, R> {
     /// Creates a new [`BitStreamReader`] that reads bitwise from a given `reader`.
     pub fn new(reader: &'a mut R) -> BitStreamReader<'a, R> {
-        BitStreamReader { buffer: [0u8], reader, offset: 8 }
+        BitStreamReader {
+            buffer: [0u8],
+            reader,
+            offset: 8,
+        }
     }
 
     /// Reads nbit bits, returning the bits in a `u64` starting with the rightmost bit.
@@ -473,10 +485,7 @@ pub fn new(reader: &'a mut R) -> BitStreamReader<'a, R> {
     /// ```
     pub fn read(&mut self, mut nbits: u8) -> Result<u64, io::Error> {
         if nbits > 64 {
-            return Err(io::Error::new(
-                io::ErrorKind::Other,
-                "can not read more than 64 bits at once",
-            ));
+            return Err(io::Error::new(io::ErrorKind::Other, "can not read more than 64 bits at once"));
         }
         let mut data = 0u64;
         while nbits > 0 {
@@ -504,16 +513,17 @@ pub struct BitStreamWriter<'a, W> {
 impl<'a, W: io::Write> BitStreamWriter<'a, W> {
     /// Creates a new [`BitStreamWriter`] that writes bitwise to a given `writer`.
     pub fn new(writer: &'a mut W) -> BitStreamWriter<'a, W> {
-        BitStreamWriter { buffer: [0u8], writer, offset: 0 }
+        BitStreamWriter {
+            buffer: [0u8],
+            writer,
+            offset: 0,
+        }
     }
 
     /// Writes nbits bits from data.
     pub fn write(&mut self, data: u64, mut nbits: u8) -> Result<usize, io::Error> {
         if nbits > 64 {
-            return Err(io::Error::new(
-                io::ErrorKind::Other,
-                "can not write more than 64 bits at once",
-            ));
+            return Err(io::Error::new(io::ErrorKind::Other, "can not write more than 64 bits at once"));
         }
         let mut wrote = 0;
         while nbits > 0 {
@@ -543,75 +553,60 @@ pub fn flush(&mut self) -> Result<usize, io::Error> {
 
 #[cfg(test)]
 mod test {
+    use super::*;
+
     use std::collections::HashMap;
 
     use serde_json::Value;
 
-    use super::*;
     use crate::consensus::encode::deserialize;
     use crate::hash_types::BlockHash;
     use crate::hashes::hex::FromHex;
 
     #[test]
     fn test_blockfilters() {
+
         // test vectors from: https://github.com/jimpo/bitcoin/blob/c7efb652f3543b001b4dd22186a354605b14f47e/src/test/data/blockfilters.json
-        let data = include_str!("../test_data/blockfilters.json");
+        let data = include_str!("../../test_data/blockfilters.json");
 
         let testdata = serde_json::from_str::<Value>(data).unwrap().as_array().unwrap().clone();
         for t in testdata.iter().skip(1) {
             let block_hash = BlockHash::from_hex(t.get(1).unwrap().as_str().unwrap()).unwrap();
-            let block: Block =
-                deserialize(&Vec::from_hex(t.get(2).unwrap().as_str().unwrap()).unwrap()).unwrap();
+            let block: Block = deserialize(&Vec::from_hex(t.get(2).unwrap().as_str().unwrap()).unwrap()).unwrap();
             assert_eq!(block.block_hash(), block_hash);
             let scripts = t.get(3).unwrap().as_array().unwrap();
-            let previous_filter_header =
-                FilterHeader::from_hex(t.get(4).unwrap().as_str().unwrap()).unwrap();
+            let previous_filter_header = FilterHeader::from_hex(t.get(4).unwrap().as_str().unwrap()).unwrap();
             let filter_content = Vec::from_hex(t.get(5).unwrap().as_str().unwrap()).unwrap();
-            let filter_header =
-                FilterHeader::from_hex(t.get(6).unwrap().as_str().unwrap()).unwrap();
+            let filter_header = FilterHeader::from_hex(t.get(6).unwrap().as_str().unwrap()).unwrap();
 
             let mut txmap = HashMap::new();
             let mut si = scripts.iter();
             for tx in block.txdata.iter().skip(1) {
                 for input in tx.input.iter() {
-                    txmap.insert(
-                        input.previous_output,
-                        Script::from(Vec::from_hex(si.next().unwrap().as_str().unwrap()).unwrap()),
-                    );
+                    txmap.insert(input.previous_output, Script::from(Vec::from_hex(si.next().unwrap().as_str().unwrap()).unwrap()));
                 }
             }
 
-            let filter = BlockFilter::new_script_filter(&block, |o| {
-                if let Some(s) = txmap.get(o) {
-                    Ok(s.clone())
-                } else {
-                    Err(Error::UtxoMissing(*o))
-                }
-            })
-            .unwrap();
+            let filter = BlockFilter::new_script_filter(&block,
+                                        |o| if let Some(s) = txmap.get(o) {
+                                            Ok(s.clone())
+                                        } else {
+                                            Err(Error::UtxoMissing(*o))
+                                        }).unwrap();
 
             let test_filter = BlockFilter::new(filter_content.as_slice());
 
             assert_eq!(test_filter.content, filter.content);
 
             let block_hash = &block.block_hash();
-            assert!(filter
-                .match_all(
-                    block_hash,
-                    &mut txmap.iter().filter_map(|(_, s)| if !s.is_empty() {
-                        Some(s.as_bytes())
-                    } else {
-                        None
-                    })
-                )
-                .unwrap());
+            assert!(filter.match_all(block_hash, &mut txmap.iter()
+                .filter_map(|(_, s)| if !s.is_empty() { Some(s.as_bytes()) } else { None })).unwrap());
 
             for script in txmap.values() {
                 let query = vec![script];
-                if !script.is_empty() {
-                    assert!(filter
-                        .match_any(block_hash, &mut query.iter().map(|s| s.as_bytes()))
-                        .unwrap());
+                if !script.is_empty () {
+                    assert!(filter.match_any(block_hash, &mut query.iter()
+                        .map(|s| s.as_bytes())).unwrap());
                 }
             }
 
@@ -654,16 +649,12 @@ fn test_filter() {
         {
             let query = vec![Vec::from_hex("abcdef").unwrap(), Vec::from_hex("eeeeee").unwrap()];
             let reader = GcsFilterReader::new(0, 0, M, P);
-            assert!(reader
-                .match_any(&mut bytes.as_slice(), &mut query.iter().map(|v| v.as_slice()))
-                .unwrap());
+               assert!(reader.match_any(&mut bytes.as_slice(), &mut query.iter().map(|v| v.as_slice())).unwrap());
         }
         {
             let query = vec![Vec::from_hex("abcdef").unwrap(), Vec::from_hex("123456").unwrap()];
             let reader = GcsFilterReader::new(0, 0, M, P);
-            assert!(!reader
-                .match_any(&mut bytes.as_slice(), &mut query.iter().map(|v| v.as_slice()))
-                .unwrap());
+            assert!(!reader.match_any(&mut bytes.as_slice(), &mut query.iter().map(|v| v.as_slice())).unwrap());
         }
         {
             let reader = GcsFilterReader::new(0, 0, M, P);
@@ -671,9 +662,7 @@ fn test_filter() {
             for p in &patterns {
                 query.push(p.clone());
             }
-            assert!(reader
-                .match_all(&mut bytes.as_slice(), &mut query.iter().map(|v| v.as_slice()))
-                .unwrap());
+            assert!(reader.match_all(&mut bytes.as_slice(), &mut query.iter().map(|v| v.as_slice())).unwrap());
         }
         {
             let reader = GcsFilterReader::new(0, 0, M, P);
@@ -682,9 +671,7 @@ fn test_filter() {
                 query.push(p.clone());
             }
             query.push(Vec::from_hex("abcdef").unwrap());
-            assert!(!reader
-                .match_all(&mut bytes.as_slice(), &mut query.iter().map(|v| v.as_slice()))
-                .unwrap());
+            assert!(!reader.match_all(&mut bytes.as_slice(), &mut query.iter().map(|v| v.as_slice())).unwrap());
         }
     }
 
@@ -703,10 +690,7 @@ fn test_bit_stream() {
             writer.flush().unwrap();
         }
         let bytes = out;
-        assert_eq!(
-            "01011010110000110000000001110000",
-            format!("{:08b}{:08b}{:08b}{:08b}", bytes[0], bytes[1], bytes[2], bytes[3])
-        );
+        assert_eq!("01011010110000110000000001110000", format!("{:08b}{:08b}{:08b}{:08b}", bytes[0], bytes[1], bytes[2], bytes[3]));
         {
             let mut input = bytes.as_slice();
             let mut reader = BitStreamReader::new(&mut input);
diff --git a/src/util/bip32.rs b/src/util/bip32.rs
index 9f18916d..191e79f9 100644
--- a/src/util/bip32.rs
+++ b/src/util/bip32.rs
@@ -1177,3 +1177,4 @@ fn schnorr_broken_privkey_ffs() {
         ExtendedPrivKey::from_str(xpriv_str).unwrap();
     }
 }
+
diff --git a/src/util/mod.rs b/src/util/mod.rs
index 52f61ecd..36ed53d0 100644
--- a/src/util/mod.rs
+++ b/src/util/mod.rs
@@ -20,6 +20,7 @@
 pub mod psbt;
 pub mod taproot;
 pub mod uint;
+pub mod bip158;
 pub mod sighash;
 
 pub(crate) mod endian;
@@ -116,6 +117,3 @@ pub(crate) fn read_to_end<D: io::Read>(mut d: D) -> Result<Vec<u8>, io::Error> {
 pub mod address {
     pub use crate::address::*;
 }
-
-#[deprecated(since = "0.30.0", note = "Please use crate::bip158")]
-pub use crate::bip158;


Test patch:
diff --git a/contrib/test.sh b/contrib/test.sh
index b46e8917..cafce679 100755
--- a/contrib/test.sh
+++ b/contrib/test.sh
@@ -1,9 +1,13 @@
-#!/bin/sh
-
-set -ex
+#!/bin/sh -ex
 
 FEATURES="base64 bitcoinconsensus serde rand secp-recovery"
 
+# Use toolchain if explicitly specified
+if [ -n "$TOOLCHAIN" ]
+then
+    alias cargo="cargo +$TOOLCHAIN"
+fi
+
 if [ "$DO_COV" = true ]
 then
     export RUSTFLAGS="-C link-dead-code"
@@ -74,7 +78,7 @@ fi
 # Test each feature
 for feature in ${FEATURES}
 do
-    echo "********* Testing $feature *************"
+    echo "********* Testing "$feature" *************"
     cargo test --verbose --features="$feature"
 done
 
@@ -98,11 +102,11 @@ fi
 # Bench if told to, only works with non-stable toolchain (nightly, beta).
 if [ "$DO_BENCH" = true ]
 then
-    if [ "$NIGHTLY" = false ]
+    if [ "NIGHTLY" = false ]
     then
-        if [ -n "$RUSTUP_TOOLCHAIN" ]
+        if [ -n "TOOLCHAIN" ]
         then
-            echo "RUSTUP_TOOLCHAIN is set to a non-nightly toolchain but DO_BENCH requires a nightly toolchain"
+            echo "TOOLCHAIN is set to a non-nightly toolchain but DO_BENCH requires a nightly toolchain"
         else
             echo "DO_BENCH requires a nightly toolchain"
         fi