This is a 2-part Review Club for PR by Antoine Poinsot which introduces Miniscript support for Output Descriptors. In this second part participants focused on the Miniscript Output Descriptor implementation.
Which function is responsible for parsing the output descriptor strings? How does it determine whether the string represents a MiniscriptDescriptor
, instead of any other type (including a WSHDescriptor
) 🔗
- The
ParseScript
function which in turn callsminiscript::FromString
. - It tries to parse all the other descriptor functions first, and if all fail then moves on to
MiniscriptDescriptor
. - For example a
wsh(pk(...))
descriptor would be parsed aspk
, as first we would remove thewsh()
wrapper and then since the very first check we do inParseScript
is forpk()
it would be parsed as pk and not as Miniscript.
bonus: What's the behaviour of Func("func_name", expr)
that is used quite frequently in ParseScript
? Are there side effects? 🔗
- If the
func_name
and a parenthesis wrapper is found inexpr
, they get removed fromexpr
andFunc
returnstrue
. If it is not found,Func
returnsfalse
andexpr
remains unchanged. - This is usefull to remove wrappers, e.g.
expr="wsh(pk(...))"
,Func("wsh", expr)
would changeexpr
to(pk(...))
.
Does MiniscriptDescriptor
accept Miniscript policy or Miniscript or both? 🔗
- The PR just doesn't include a policy compiler.
Node<Key>::ContainsDuplicateKey
returns a bool
. What is the return type of TreeEvalMaybe<std::set<Key>>(upfn)
, and how does it get cast to a bool
?
TreeEvalMaybe
returns anstd::optional<Result>
, whereResult
is a templated typename. Per https://en.cppreference.com/w/cpp/utility/optional: when cast to a bool,std::optional
becomestrue
if the object contains a value andfalse
if it does not contain a value (std::nullopt
).
When choosing between two available satisfactions, why should the one that involves less or no signatures be preferred? For example, consider the policy or(and(older(21), pk(B)), thresh(2, pk(A), pk(B)))
which can always be spent when both A and B sign, and can be spent after 21 blocks when just B signs. After 21 blocks, both satisfactions are available, but why would the satisfaction that involves just B’s signature be preferable? 🔗
- An initial thought could be less signatures -> less witness data -> smaller tx -> less fees to pay, but that's not always the case. Imagine a situation where you have the choice between two satisfaction paths. One involves no signatures, but is bigger. One involves signature but is smaller. You should still prefer the bigger, no signatures one. Why?
- It has to do with third-party malleability, the ability for someone not participating in the tx (not holding the required keys to sign) to modify the witness of a transaction. Witness data is not covered by signatures, so third parties (e.g. when relaying a tx) can change this at will. In most cases, this will make the tx invalid because the witness doesn't satisfy the scriptPubKey anymore, but there are cases where the witness remains valid even after it is modified.
- So you need third parties not being able to change which satisfaction path was used. In our scenario, if the second branch is satisfied
thresh(2, pk(A), pk(B))
, a third party can malleate the satisfaction by removing the signature for A and force the script to go through the first path since having the signature for B is enough. However, the other way around is not possible. If you only announce A, the third party couldn't possibly drop B. - A third party can't add signatures but can drop them. So if the honest signers use a construction with a "redundant" signature, even if it is smaller, third parties can mutate it into the bigger no-sig variant.