Skip to content

Commit f5deb41

Browse files
sipadarosior
authored andcommitted
Various additional explanations of the satisfaction logic from Pieter
Cherry-picked and squashed from https://github.com/sipa/bitcoin/commits/202302_miniscript_improve. - Explain thresh() and multi() satisfaction algorithms - Comment on and_v dissatisfaction - Mark overcomplete thresh() dissats as malleable and explain - Add comment on unnecessity of Malleable() in and_b dissat
1 parent 22c5b00 commit f5deb41

File tree

1 file changed

+38
-2
lines changed

1 file changed

+38
-2
lines changed

src/script/miniscript.h

+38-2
Original file line numberDiff line numberDiff line change
@@ -865,36 +865,61 @@ struct Node {
865865
return {ZERO + InputStack(key), (InputStack(std::move(sig)).SetWithSig() + InputStack(key)).SetAvailable(avail)};
866866
}
867867
case Fragment::MULTI: {
868+
// sats[j] represents the best stack containing j valid signatures (out of the first i keys).
869+
// In the loop below, these stacks are built up using a dynamic programming approach.
870+
// sats[0] starts off being {0}, due to the CHECKMULTISIG bug that pops off one element too many.
868871
std::vector<InputStack> sats = Vector(ZERO);
869872
for (size_t i = 0; i < node.keys.size(); ++i) {
870873
std::vector<unsigned char> sig;
871874
Availability avail = ctx.Sign(node.keys[i], sig);
875+
// Compute signature stack for just the i'th key.
872876
auto sat = InputStack(std::move(sig)).SetWithSig().SetAvailable(avail);
877+
// Compute the next sats vector: next_sats[0] is a copy of sats[0] (no signatures). All further
878+
// next_sats[j] are equal to either the existing sats[j], or sats[j-1] plus a signature for the
879+
// current (i'th) key. The very last element needs all signatures filled.
873880
std::vector<InputStack> next_sats;
874881
next_sats.push_back(sats[0]);
875882
for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back(sats[j] | (std::move(sats[j - 1]) + sat));
876883
next_sats.push_back(std::move(sats[sats.size() - 1]) + std::move(sat));
884+
// Switch over.
877885
sats = std::move(next_sats);
878886
}
887+
// The dissatisfaction consists of k+1 stack elements all equal to 0.
879888
InputStack nsat = ZERO;
880889
for (size_t i = 0; i < node.k; ++i) nsat = std::move(nsat) + ZERO;
881890
assert(node.k <= sats.size());
882891
return {std::move(nsat), std::move(sats[node.k])};
883892
}
884893
case Fragment::THRESH: {
894+
// sats[k] represents the best stack that satisfies k out of the *last* i subexpressions.
895+
// In the loop below, these stacks are built up using a dynamic programming approach.
896+
// sats[0] starts off empty.
885897
std::vector<InputStack> sats = Vector(EMPTY);
886898
for (size_t i = 0; i < subres.size(); ++i) {
899+
// Introduce an alias for the i'th last satisfaction/dissatisfaction.
887900
auto& res = subres[subres.size() - i - 1];
901+
// Compute the next sats vector: next_sats[0] is sats[0] plus res.nsat (thus containing all dissatisfactions
902+
// so far. next_sats[j] is either sats[j] + res.nsat (reusing j earlier satisfactions) or sats[j-1] + res.sat
903+
// (reusing j-1 earlier satisfactions plus a new one). The very last next_sats[j] is all satisfactions.
888904
std::vector<InputStack> next_sats;
889905
next_sats.push_back(sats[0] + res.nsat);
890906
for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back((sats[j] + res.nsat) | (std::move(sats[j - 1]) + res.sat));
891907
next_sats.push_back(std::move(sats[sats.size() - 1]) + std::move(res.sat));
908+
// Switch over.
892909
sats = std::move(next_sats);
893910
}
911+
// At this point, sats[k].sat is the best satisfaction for the overall thresh() node. The best dissatisfaction
912+
// is computed by gathering all sats[i].nsat for i != k.
894913
InputStack nsat = INVALID;
895914
for (size_t i = 0; i < sats.size(); ++i) {
896-
// i==k is the satisfaction; i==0 is the canonical dissatisfaction; the rest are non-canonical.
897-
if (i != 0 && i != node.k) sats[i].SetNonCanon();
915+
// i==k is the satisfaction; i==0 is the canonical dissatisfaction;
916+
// the rest are non-canonical (a no-signature dissatisfaction - the i=0
917+
// form - is always available) and malleable (due to overcompleteness).
918+
// Marking the solutions malleable here is not strictly necessary, as they
919+
// should already never be picked in non-malleable solutions due to the
920+
// availability of the i=0 form.
921+
if (i != 0 && i != node.k) sats[i].SetMalleable().SetNonCanon();
922+
// Include all dissatisfactions (even these non-canonical ones) in nsat.
898923
if (i != node.k) nsat = std::move(nsat) | std::move(sats[i]);
899924
}
900925
assert(node.k <= sats.size());
@@ -928,10 +953,21 @@ struct Node {
928953
}
929954
case Fragment::AND_V: {
930955
auto& x = subres[0], &y = subres[1];
956+
// As the dissatisfaction here only consist of a single option, it doesn't
957+
// actually need to be listed (it's not required for reasoning about malleability of
958+
// other options), and is never required (no valid miniscript relies on the ability
959+
// to satisfy the type V left subexpression). It's still listed here for
960+
// completeness, as a hypothetical (not currently implemented) satisfier that doesn't
961+
// care about malleability might in some cases prefer it still.
931962
return {(y.nsat + x.sat).SetNonCanon(), y.sat + x.sat};
932963
}
933964
case Fragment::AND_B: {
934965
auto& x = subres[0], &y = subres[1];
966+
// Note that it is not strictly necessary to mark the 2nd and 3rd dissatisfaction here
967+
// as malleable. While they are definitely malleable, they are also non-canonical due
968+
// to the guaranteed existence of a no-signature other dissatisfaction (the 1st)
969+
// option. Because of that, the 2nd and 3rd option will never be chosen, even if they
970+
// weren't marked as malleable.
935971
return {(y.nsat + x.nsat) | (y.sat + x.nsat).SetMalleable().SetNonCanon() | (y.nsat + x.sat).SetMalleable().SetNonCanon(), y.sat + x.sat};
936972
}
937973
case Fragment::OR_B: {

0 commit comments

Comments
 (0)