I don’t mind removing the const from the privates in Node, but I’m not a fan of making the publics mutable…
It seems to me we could make them private if we move FindChallenges over.
 0diff --git a/src/script/miniscript.h b/src/script/miniscript.h
 1index f99c9a8036..181a6614cf 100644
 2--- a/src/script/miniscript.h
 3+++ b/src/script/miniscript.h
 4@@ -495,19 +495,6 @@ struct NoDupCheck {};
 5 //! A node in a miniscript expression.
 6 template<typename Key>
 7 struct Node {
 8-    //! What node type this node is.
 9-    Fragment fragment;
10-    //! The k parameter (time for OLDER/AFTER, threshold for THRESH(_M))
11-    uint32_t k = 0;
12-    //! The keys used by this expression (only for PK_K/PK_H/MULTI)
13-    std::vector<Key> keys;
14-    //! The data bytes in this expression (only for HASH160/HASH256/SHA256/RIPEMD160).
15-    std::vector<unsigned char> data;
16-    //! Subexpressions (for WRAP_*/AND_*/OR_*/ANDOR/THRESH)
17-    std::vector<Node> subs;
18-    //! The Script context for this node. Either P2WSH or Tapscript.
19-    MiniscriptContext m_script_ctx;
20-
21     // Permit 1 level deep recursion since we own instances of our own type.
22     // NOLINTBEGIN(misc-no-recursion)
23     ~Node()
24@@ -541,7 +528,26 @@ struct Node {
25         return TreeEval<Node>(upfn);
26     }
27 
28+    Fragment GetFragment() const noexcept { return fragment; }
29+    uint32_t GetK() const noexcept { return k; }
30+    std::span<const Key> GetKeys() const noexcept { return keys; }
31+    std::span<const unsigned char> GetData() const noexcept { return data; }
32+    std::span<const Node> GetSubs() const noexcept { return subs; }
33+
34 private:
35+    //! What node type this node is.
36+    Fragment fragment;
37+    //! The k parameter (time for OLDER/AFTER, threshold for THRESH(_M))
38+    uint32_t k = 0;
39+    //! The keys used by this expression (only for PK_K/PK_H/MULTI)
40+    std::vector<Key> keys;
41+    //! The data bytes in this expression (only for HASH160/HASH256/SHA256/RIPEMD160).
42+    std::vector<unsigned char> data;
43+    //! Subexpressions (for WRAP_*/AND_*/OR_*/ANDOR/THRESH)
44+    std::vector<Node> subs;
45+    //! The Script context for this node. Either P2WSH or Tapscript.
46+    MiniscriptContext m_script_ctx;
47+
48     //! Cached ops counts.
49     internal::Ops ops;
50     //! Cached stack size bounds.
51diff --git a/src/test/miniscript_tests.cpp b/src/test/miniscript_tests.cpp
52index cee88373c4..8a5a54b0bf 100644
53--- a/src/test/miniscript_tests.cpp
54+++ b/src/test/miniscript_tests.cpp
55@@ -121,7 +121,7 @@ enum class ChallengeType {
56  * For hashes it's just the first 4 bytes of the hash. For pubkeys, it's the last 4 bytes.
57  */
58 uint32_t ChallengeNumber(const CPubKey& pubkey) { return ReadLE32(pubkey.data() + 29); }
59-uint32_t ChallengeNumber(const std::vector<unsigned char>& hash) { return ReadLE32(hash.data()); }
60+uint32_t ChallengeNumber(std::span<const unsigned char> hash) { return ReadLE32(hash.data()); }
61 
62 //! A Challenge is a combination of type of leaf condition and its challenge number.
63 typedef std::pair<ChallengeType, uint32_t> Challenge;
64@@ -304,19 +304,19 @@ std::set<Challenge> FindChallenges(const Node& root)
65         const auto* ref{stack.back()};
66         stack.pop_back();
67 
68-        for (const auto& key : ref->keys) {
69+        for (const auto& key : ref->GetKeys()) {
70             chal.emplace(ChallengeType::PK, ChallengeNumber(key));
71         }
72-        switch (ref->fragment) {
73-        case Fragment::OLDER: chal.emplace(ChallengeType::OLDER, ref->k); break;
74-        case Fragment::AFTER: chal.emplace(ChallengeType::AFTER, ref->k); break;
75-        case Fragment::SHA256: chal.emplace(ChallengeType::SHA256, ChallengeNumber(ref->data)); break;
76-        case Fragment::RIPEMD160: chal.emplace(ChallengeType::RIPEMD160, ChallengeNumber(ref->data)); break;
77-        case Fragment::HASH256: chal.emplace(ChallengeType::HASH256, ChallengeNumber(ref->data)); break;
78-        case Fragment::HASH160: chal.emplace(ChallengeType::HASH160, ChallengeNumber(ref->data)); break;
79+        switch (ref->GetFragment()) {
80+        case Fragment::OLDER: chal.emplace(ChallengeType::OLDER, ref->GetK()); break;
81+        case Fragment::AFTER: chal.emplace(ChallengeType::AFTER, ref->GetK()); break;
82+        case Fragment::SHA256: chal.emplace(ChallengeType::SHA256, ChallengeNumber(ref->GetData())); break;
83+        case Fragment::RIPEMD160: chal.emplace(ChallengeType::RIPEMD160, ChallengeNumber(ref->GetData())); break;
84+        case Fragment::HASH256: chal.emplace(ChallengeType::HASH256, ChallengeNumber(ref->GetData())); break;
85+        case Fragment::HASH160: chal.emplace(ChallengeType::HASH160, ChallengeNumber(ref->GetData())); break;
86         default: break;
87         }
88-        for (const auto& sub : ref->subs) {
89+        for (const auto& sub : ref->GetSubs()) {
90             stack.push_back(&sub);
91         }
92     }