]> Untitled Git - bdk/commitdiff
fix(chain): clamp derivation index before apply_changeset
authorbusayo-OD <toyinoluwabusayo@gmail.com>
Sun, 6 Sep 2026 15:20:22 +0000 (16:20 +0100)
committerbusayo-OD <toyinoluwabusayo@gmail.com>
Sun, 6 Sep 2026 15:20:22 +0000 (16:20 +0100)
KeychainTxOutIndex::apply_changeset accepts ChangeSet::last_revealed
values above BIP32_MAX_INDEX, violating an invariant relied on by
the indexer.

Clamp each last_revealed value to BIP32_MAX_INDEX before storing
it, keeping changeset application infallible and monotone.

crates/chain/src/indexer/keychain_txout.rs

index 33b5c97270cbde3822e2644b12f88fee48998084..e436ad7ad5521e393a3729d161aa5cef56712f51 100644 (file)
@@ -992,6 +992,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
             }
         }
         for (did, index) in changeset.last_revealed {
+            let index = index.min(BIP32_MAX_INDEX);
             let v = self.last_revealed.entry(did).or_default();
             *v = index.max(*v);
             self.replenish_inner_index_did(did, self.lookahead);
@@ -1226,4 +1227,35 @@ mod test {
         let index = KeychainTxOutIndex::<i32>::from_changeset(lookahead, false, init_cs);
         assert!(index.spk_cache.is_empty());
     }
+
+    #[test]
+    fn apply_changeset_clamps_out_of_range_index_and_derives_correct_spk() {
+        let s = DESCRIPTORS[0];
+        let desc = Descriptor::parse_descriptor(&Secp256k1::new(), s)
+            .unwrap()
+            .0;
+        let mut index = KeychainTxOutIndex::new(0, false);
+        let did = desc.descriptor_id();
+        let _ = index.insert_descriptor(0i32, desc.clone());
+        let seed_index = BIP32_MAX_INDEX - 1;
+        let spk = desc
+            .at_derivation_index(seed_index)
+            .unwrap()
+            .script_pubkey();
+        index.inner.insert_spk((0i32, seed_index), spk);
+        let changeset = ChangeSet {
+            last_revealed: [(did, BIP32_MAX_INDEX + 1)].into(),
+            ..Default::default()
+        };
+        index.apply_changeset(changeset);
+        assert_eq!(index.last_revealed_index(0i32), Some(BIP32_MAX_INDEX));
+        assert_eq!(
+            index.spk_at_index(0i32, BIP32_MAX_INDEX),
+            Some(
+                desc.at_derivation_index(BIP32_MAX_INDEX)
+                    .unwrap()
+                    .script_pubkey()
+            )
+        );
+    }
 }