Skip to content

Commit 64a0976

Browse files
committed
fix(chain): check/sanitize descriptor has not hardened child steps before insert it
1 parent d3232f5 commit 64a0976

File tree

7 files changed

+42
-54
lines changed

7 files changed

+42
-54
lines changed

crates/chain/src/indexed_tx_graph.rs

+2-7
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use alloc::{sync::Arc, vec::Vec};
66
use bitcoin::{Block, OutPoint, Transaction, TxOut, Txid};
77

88
use crate::{
9-
keychain_txout::InsertDescriptorError,
109
tx_graph::{self, TxGraph},
1110
Anchor, BlockId, Indexer, Merge, TxPosInBlock,
1211
};
@@ -47,11 +46,8 @@ impl<A, I> IndexedTxGraph<A, I> {
4746

4847
impl<A: Anchor, I: Indexer> IndexedTxGraph<A, I> {
4948
/// Applies the [`ChangeSet`] to the [`IndexedTxGraph`].
50-
pub fn apply_changeset(
51-
&mut self,
52-
changeset: ChangeSet<A, I::ChangeSet>,
53-
) -> Result<(), InsertDescriptorError> {
54-
self.index.apply_changeset(changeset.indexer)?;
49+
pub fn apply_changeset(&mut self, changeset: ChangeSet<A, I::ChangeSet>) {
50+
self.index.apply_changeset(changeset.indexer);
5551

5652
for tx in &changeset.tx_graph.txs {
5753
self.index.index_tx(tx);
@@ -61,7 +57,6 @@ impl<A: Anchor, I: Indexer> IndexedTxGraph<A, I> {
6157
}
6258

6359
self.graph.apply_changeset(changeset.tx_graph);
64-
Ok(())
6560
}
6661

6762
/// Determines the [`ChangeSet`] between `self` and an empty [`IndexedTxGraph`].

crates/chain/src/indexer.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,7 @@ pub trait Indexer {
2323
fn index_tx(&mut self, tx: &Transaction) -> Self::ChangeSet;
2424

2525
/// Apply changeset to itself.
26-
fn apply_changeset(
27-
&mut self,
28-
changeset: Self::ChangeSet,
29-
) -> Result<(), keychain_txout::InsertDescriptorError>;
26+
fn apply_changeset(&mut self, changeset: Self::ChangeSet);
3027

3128
/// Determines the [`ChangeSet`](Indexer::ChangeSet) between `self` and an empty [`Indexer`].
3229
fn initial_changeset(&self) -> Self::ChangeSet;

crates/chain/src/indexer/keychain_txout.rs

+20-23
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
176176
}
177177
}
178178

179-
fn apply_changeset(&mut self, changeset: Self::ChangeSet) -> Result<(), InsertDescriptorError> {
179+
fn apply_changeset(&mut self, changeset: Self::ChangeSet) {
180180
self.apply_changeset(changeset)
181181
}
182182

@@ -361,6 +361,23 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
361361
keychain: K,
362362
descriptor: Descriptor<DescriptorPublicKey>,
363363
) -> Result<bool, InsertDescriptorError<K>> {
364+
// Ensure the keys don't contain any hardened derivation steps or hardened wildcards
365+
let descriptor_contains_hardened_steps = descriptor.for_any_key(|k| {
366+
if let DescriptorPublicKey::XPub(DescriptorXKey {
367+
derivation_path,
368+
wildcard,
369+
..
370+
}) = k
371+
{
372+
return *wildcard == Wildcard::Hardened
373+
|| derivation_path.into_iter().any(ChildNumber::is_hardened);
374+
}
375+
false
376+
});
377+
if descriptor_contains_hardened_steps {
378+
return Err(InsertDescriptorError::HardenedDerivationXpub);
379+
}
380+
descriptor.sanity_check()?;
364381
let did = descriptor.descriptor_id();
365382
if !self.keychain_to_descriptor_id.contains_key(&keychain)
366383
&& !self.descriptor_id_to_keychain.contains_key(&did)
@@ -773,32 +790,12 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
773790
}
774791

775792
/// Applies the `ChangeSet<K>` to the [`KeychainTxOutIndex<K>`]
776-
pub fn apply_changeset(&mut self, changeset: ChangeSet) -> Result<(), InsertDescriptorError> {
793+
pub fn apply_changeset(&mut self, changeset: ChangeSet) {
777794
for (&desc_id, &index) in &changeset.last_revealed {
778-
let descriptor = self.descriptors.get(&desc_id).expect("invariant");
779-
// Ensure the keys don't contain any hardened derivation steps or hardened wildcards
780-
let descriptor_contains_hardened_steps = descriptor.for_any_key(|k| {
781-
if let DescriptorPublicKey::XPub(DescriptorXKey {
782-
derivation_path,
783-
wildcard,
784-
..
785-
}) = k
786-
{
787-
return *wildcard == Wildcard::Hardened
788-
|| derivation_path.into_iter().any(ChildNumber::is_hardened);
789-
}
790-
791-
false
792-
});
793-
if descriptor_contains_hardened_steps {
794-
return Err(InsertDescriptorError::HardenedDerivationXpub);
795-
}
796-
descriptor.sanity_check()?;
797795
let v = self.last_revealed.entry(desc_id).or_default();
798796
*v = index.max(*v);
799797
self.replenish_inner_index_did(desc_id, self.lookahead);
800798
}
801-
Ok(())
802799
}
803800
}
804801

@@ -825,7 +822,7 @@ pub enum InsertDescriptorError<K = ()> {
825822
HardenedDerivationXpub,
826823
}
827824

828-
impl From<miniscript::Error> for InsertDescriptorError {
825+
impl<K> From<miniscript::Error> for InsertDescriptorError<K> {
829826
fn from(err: miniscript::Error) -> Self {
830827
InsertDescriptorError::Miniscript(err)
831828
}

crates/chain/src/indexer/spk_txout.rs

+1-7
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ use crate::{
88
};
99
use bitcoin::{Amount, OutPoint, ScriptBuf, SignedAmount, Transaction, TxOut, Txid};
1010

11-
use super::keychain_txout::InsertDescriptorError;
12-
1311
/// An index storing [`TxOut`]s that have a script pubkey that matches those in a list.
1412
///
1513
/// The basic idea is that you insert script pubkeys you care about into the index with
@@ -71,12 +69,8 @@ impl<I: Clone + Ord + core::fmt::Debug> Indexer for SpkTxOutIndex<I> {
7169

7270
fn initial_changeset(&self) -> Self::ChangeSet {}
7371

74-
fn apply_changeset(
75-
&mut self,
76-
_changeset: Self::ChangeSet,
77-
) -> Result<(), InsertDescriptorError> {
72+
fn apply_changeset(&mut self, _changeset: Self::ChangeSet) {
7873
// This applies nothing.
79-
Ok(())
8074
}
8175

8276
fn is_tx_relevant(&self, tx: &Transaction) -> bool {

crates/chain/tests/test_keychain_txout_index.rs

+15-6
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,19 @@ fn lookahead_to_target() {
600600
}
601601
}
602602

603+
#[test]
604+
fn insert_descriptor_should_reject_hardened_steps() {
605+
use bdk_chain::keychain_txout::KeychainTxOutIndex;
606+
607+
// This descriptor has hardened child steps
608+
let s = "wpkh([e273fe42/84h/1h/0h/0h]tpubDEBY7DLZ5kK6pMC2qdtVvKpe6CiAmVDx1SiLtLS3V4GAGyRvsuVbCYReJ9oQz1rfMapghJyUAYLqkqJ3Xadp3GSTCtdAFcKPgzAXC1hzz8a/*h)";
609+
let (desc, _) =
610+
<Descriptor<DescriptorPublicKey>>::parse_descriptor(&Secp256k1::new(), s).unwrap();
611+
612+
let mut indexer = KeychainTxOutIndex::<&str>::new(10);
613+
assert!(indexer.insert_descriptor("keychain", desc).is_err())
614+
}
615+
603616
#[test]
604617
fn applying_changesets_one_by_one_vs_aggregate_must_have_same_result() {
605618
let desc = parse_descriptor(DESCRIPTORS[0]);
@@ -617,9 +630,7 @@ fn applying_changesets_one_by_one_vs_aggregate_must_have_same_result() {
617630
.insert_descriptor(TestKeychain::External, desc.clone())
618631
.expect("must insert keychain");
619632
for changeset in changesets {
620-
indexer_a
621-
.apply_changeset(changeset.clone())
622-
.expect("must apply changeset");
633+
indexer_a.apply_changeset(changeset.clone());
623634
}
624635

625636
let mut indexer_b = KeychainTxOutIndex::<TestKeychain>::new(0);
@@ -634,9 +645,7 @@ fn applying_changesets_one_by_one_vs_aggregate_must_have_same_result() {
634645
agg
635646
})
636647
.expect("must aggregate changesets");
637-
indexer_b
638-
.apply_changeset(aggregate_changesets)
639-
.expect("must apply changeset");
648+
indexer_b.apply_changeset(aggregate_changesets);
640649

641650
assert_eq!(
642651
indexer_a.keychains().collect::<Vec<_>>(),

crates/wallet/src/wallet/mod.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -608,12 +608,8 @@ impl Wallet {
608608
.map_err(LoadError::Descriptor)?;
609609

610610
let mut indexed_graph = IndexedTxGraph::new(index);
611-
indexed_graph
612-
.apply_changeset(changeset.indexer.into())
613-
.map_err(|err| LoadError::Descriptor(err.into()))?;
614-
indexed_graph
615-
.apply_changeset(changeset.tx_graph.into())
616-
.map_err(|err| LoadError::Descriptor(err.into()))?;
611+
indexed_graph.apply_changeset(changeset.indexer.into());
612+
indexed_graph.apply_changeset(changeset.tx_graph.into());
617613

618614
let stage = ChangeSet::default();
619615

example-crates/example_cli/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -827,7 +827,7 @@ pub fn init_or_load<CS: clap::Subcommand, S: clap::Args>(
827827
graph.apply_changeset(indexed_tx_graph::ChangeSet {
828828
tx_graph: changeset.tx_graph,
829829
indexer: changeset.indexer,
830-
})?;
830+
});
831831
graph
832832
});
833833

0 commit comments

Comments
 (0)