use crate::{
bitcoin::{secp256k1::Secp256k1, ScriptBuf},
miniscript::{Descriptor, DescriptorPublicKey},
Indexed,
};
use core::{borrow::Borrow, ops::Bound, ops::RangeBounds};
pub const BIP32_MAX_INDEX: u32 = (1 << 31) - 1;
#[derive(Clone)]
pub struct SpkIterator<D> {
next_index: u32,
end: u32,
descriptor: D,
secp: Secp256k1<bitcoin::secp256k1::VerifyOnly>,
}
impl<D> SpkIterator<D>
where
D: Borrow<Descriptor<DescriptorPublicKey>>,
{
pub fn new(descriptor: D) -> Self {
SpkIterator::new_with_range(descriptor, 0..=BIP32_MAX_INDEX)
}
pub fn new_with_range<R>(descriptor: D, range: R) -> Self
where
R: RangeBounds<u32>,
{
let start = match range.start_bound() {
Bound::Included(start) => *start,
Bound::Excluded(start) => *start + 1,
Bound::Unbounded => u32::MIN,
};
let mut end = match range.end_bound() {
Bound::Included(end) => *end + 1,
Bound::Excluded(end) => *end,
Bound::Unbounded => u32::MAX,
};
end = end.min(BIP32_MAX_INDEX + 1);
Self {
next_index: start,
end,
descriptor,
secp: Secp256k1::verification_only(),
}
}
pub fn descriptor(&self) -> &D {
&self.descriptor
}
}
impl<D> Iterator for SpkIterator<D>
where
D: Borrow<Descriptor<DescriptorPublicKey>>,
{
type Item = Indexed<ScriptBuf>;
fn next(&mut self) -> Option<Self::Item> {
if self.next_index >= self.end {
return None;
}
if !self.descriptor.borrow().has_wildcard() && self.next_index != 0 {
return None;
}
let script = self
.descriptor
.borrow()
.derived_descriptor(&self.secp, self.next_index)
.expect("the descriptor cannot need hardened derivation")
.script_pubkey();
let output = (self.next_index, script);
self.next_index += 1;
Some(output)
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.next_index = self
.next_index
.saturating_add(u32::try_from(n).unwrap_or(u32::MAX));
self.next()
}
}
#[cfg(test)]
mod test {
use crate::{
bitcoin::secp256k1::Secp256k1,
indexer::keychain_txout::KeychainTxOutIndex,
miniscript::{Descriptor, DescriptorPublicKey},
spk_iter::{SpkIterator, BIP32_MAX_INDEX},
};
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
enum TestKeychain {
External,
Internal,
}
fn init_txout_index() -> (
KeychainTxOutIndex<TestKeychain>,
Descriptor<DescriptorPublicKey>,
Descriptor<DescriptorPublicKey>,
) {
let mut txout_index = KeychainTxOutIndex::<TestKeychain>::new(0);
let secp = Secp256k1::signing_only();
let (external_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
let (internal_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/*)").unwrap();
let _ = txout_index
.insert_descriptor(TestKeychain::External, external_descriptor.clone())
.unwrap();
let _ = txout_index
.insert_descriptor(TestKeychain::Internal, internal_descriptor.clone())
.unwrap();
(txout_index, external_descriptor, internal_descriptor)
}
#[test]
#[allow(clippy::iter_nth_zero)]
#[rustfmt::skip]
fn test_spkiterator_wildcard() {
let (_, external_desc, _) = init_txout_index();
let external_spk_0 = external_desc.at_derivation_index(0).unwrap().script_pubkey();
let external_spk_16 = external_desc.at_derivation_index(16).unwrap().script_pubkey();
let external_spk_20 = external_desc.at_derivation_index(20).unwrap().script_pubkey();
let external_spk_21 = external_desc.at_derivation_index(21).unwrap().script_pubkey();
let external_spk_max = external_desc.at_derivation_index(BIP32_MAX_INDEX).unwrap().script_pubkey();
let mut external_spk = SpkIterator::new(&external_desc);
let max_index = BIP32_MAX_INDEX - 22;
assert_eq!(external_spk.next(), Some((0, external_spk_0)));
assert_eq!(external_spk.nth(15), Some((16, external_spk_16)));
assert_eq!(external_spk.nth(3), Some((20, external_spk_20.clone())));
assert_eq!(external_spk.next(), Some((21, external_spk_21)));
assert_eq!(
external_spk.nth(max_index as usize),
Some((BIP32_MAX_INDEX, external_spk_max))
);
assert_eq!(external_spk.nth(0), None);
let mut external_spk = SpkIterator::new_with_range(&external_desc, 0..21);
assert_eq!(external_spk.nth(20), Some((20, external_spk_20)));
assert_eq!(external_spk.next(), None);
let mut external_spk = SpkIterator::new_with_range(&external_desc, 0..21);
assert_eq!(external_spk.nth(21), None);
}
#[test]
#[allow(clippy::iter_nth_zero)]
fn test_spkiterator_non_wildcard() {
let secp = bitcoin::secp256k1::Secp256k1::signing_only();
let (no_wildcard_descriptor, _) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "wpkh([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/0)").unwrap();
let external_spk_0 = no_wildcard_descriptor
.at_derivation_index(0)
.unwrap()
.script_pubkey();
let mut external_spk = SpkIterator::new(&no_wildcard_descriptor);
assert_eq!(external_spk.next(), Some((0, external_spk_0.clone())));
assert_eq!(external_spk.next(), None);
let mut external_spk = SpkIterator::new(&no_wildcard_descriptor);
assert_eq!(external_spk.nth(0), Some((0, external_spk_0.clone())));
assert_eq!(external_spk.nth(0), None);
let mut external_spk = SpkIterator::new_with_range(&no_wildcard_descriptor, 0..0);
assert_eq!(external_spk.next(), None);
let mut external_spk = SpkIterator::new_with_range(&no_wildcard_descriptor, 0..1);
assert_eq!(external_spk.nth(0), Some((0, external_spk_0.clone())));
assert_eq!(external_spk.next(), None);
let mut external_spk = SpkIterator::new_with_range(&no_wildcard_descriptor, 0..10);
assert_eq!(external_spk.nth(0), Some((0, external_spk_0)));
assert_eq!(external_spk.nth(0), None);
assert_eq!(
SpkIterator::new_with_range(&no_wildcard_descriptor, 1..1).next(),
None
);
assert_eq!(
SpkIterator::new_with_range(&no_wildcard_descriptor, 1..=1).next(),
None
);
assert_eq!(
SpkIterator::new_with_range(&no_wildcard_descriptor, 1..2).next(),
None
);
assert_eq!(
SpkIterator::new_with_range(&no_wildcard_descriptor, 1..=2).next(),
None
);
assert_eq!(
SpkIterator::new_with_range(&no_wildcard_descriptor, 10..11).next(),
None
);
assert_eq!(
SpkIterator::new_with_range(&no_wildcard_descriptor, 10..=10).next(),
None
);
}
}
#[test]
fn spk_iterator_is_send_and_static() {
fn is_send_and_static<A: Send + 'static>() {}
is_send_and_static::<SpkIterator<Descriptor<DescriptorPublicKey>>>()
}