miniscript: introduce a helper to get the maximum witness size

Similarly to how we compute the maximum stack size.

Also note how it would be quite expensive to recompute it recursively
by accounting for different ECDSA signature sizes. So we just assume
high-R everywhere. It's only a trivial difference anyways.
This commit is contained in:
Antoine Poinsot
2023-04-28 13:55:08 +02:00
parent 4ab382c2cd
commit bdba7667d2
2 changed files with 107 additions and 38 deletions

View File

@@ -337,6 +337,15 @@ struct StackSize {
StackSize(MaxInt<uint32_t> in_sat, MaxInt<uint32_t> in_dsat) : sat(in_sat), dsat(in_dsat) {};
};
struct WitnessSize {
//! Maximum witness size to satisfy;
MaxInt<uint32_t> sat;
//! Maximum witness size to dissatisfy;
MaxInt<uint32_t> dsat;
WitnessSize(MaxInt<uint32_t> in_sat, MaxInt<uint32_t> in_dsat) : sat(in_sat), dsat(in_dsat) {};
};
struct NoDupCheck {};
} // namespace internal
@@ -360,6 +369,8 @@ private:
const internal::Ops ops;
//! Cached stack size bounds.
const internal::StackSize ss;
//! Cached witness size bounds.
const internal::WitnessSize ws;
//! Cached expression type (computed by CalcType and fed through SanitizeType).
const Type typ;
//! Cached script length (computed by CalcScriptLen).
@@ -846,6 +857,56 @@ private:
assert(false);
}
internal::WitnessSize CalcWitnessSize() const {
switch (fragment) {
case Fragment::JUST_0: return {{}, 0};
case Fragment::JUST_1:
case Fragment::OLDER:
case Fragment::AFTER: return {0, {}};
case Fragment::PK_K: return {1 + 72, 1};
case Fragment::PK_H: return {1 + 72 + 1 + 33, 1 + 1 + 33};
case Fragment::SHA256:
case Fragment::RIPEMD160:
case Fragment::HASH256:
case Fragment::HASH160: return {1 + 32, {}};
case Fragment::ANDOR: {
const auto sat{(subs[0]->ws.sat + subs[1]->ws.sat) | (subs[0]->ws.dsat + subs[2]->ws.sat)};
const auto dsat{subs[0]->ws.dsat + subs[2]->ws.dsat};
return {sat, dsat};
}
case Fragment::AND_V: return {subs[0]->ws.sat + subs[1]->ws.sat, {}};
case Fragment::AND_B: return {subs[0]->ws.sat + subs[1]->ws.sat, subs[0]->ws.dsat + subs[1]->ws.dsat};
case Fragment::OR_B: {
const auto sat{(subs[0]->ws.dsat + subs[1]->ws.sat) | (subs[0]->ws.sat + subs[1]->ws.dsat)};
const auto dsat{subs[0]->ws.dsat + subs[1]->ws.dsat};
return {sat, dsat};
}
case Fragment::OR_C: return {subs[0]->ws.sat | (subs[0]->ws.dsat + subs[1]->ws.sat), {}};
case Fragment::OR_D: return {subs[0]->ws.sat | (subs[0]->ws.dsat + subs[1]->ws.sat), subs[0]->ws.dsat + subs[1]->ws.dsat};
case Fragment::OR_I: return {(subs[0]->ws.sat + 1 + 1) | (subs[1]->ws.sat + 1), (subs[0]->ws.dsat + 1 + 1) | (subs[1]->ws.dsat + 1)};
case Fragment::MULTI: return {k * (1 + 72) + 1, k + 1};
case Fragment::WRAP_A:
case Fragment::WRAP_N:
case Fragment::WRAP_S:
case Fragment::WRAP_C: return subs[0]->ws;
case Fragment::WRAP_D: return {1 + 1 + subs[0]->ws.sat, 1};
case Fragment::WRAP_V: return {subs[0]->ws.sat, {}};
case Fragment::WRAP_J: return {subs[0]->ws.sat, 1};
case Fragment::THRESH: {
auto sats = Vector(internal::MaxInt<uint32_t>(0));
for (const auto& sub : subs) {
auto next_sats = Vector(sats[0] + sub->ws.dsat);
for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back((sats[j] + sub->ws.dsat) | (sats[j - 1] + sub->ws.sat));
next_sats.push_back(sats[sats.size() - 1] + sub->ws.sat);
sats = std::move(next_sats);
}
assert(k <= sats.size());
return {sats[k], sats[0]};
}
}
assert(false);
}
template<typename Ctx>
internal::InputResult ProduceInput(const Ctx& ctx) const {
using namespace internal;
@@ -1164,6 +1225,13 @@ public:
//! Whether no satisfaction exists for this node.
bool IsNotSatisfiable() const { return !GetStackSize(); }
/** Return the maximum size in bytes of a witness to satisfy this script non-malleably. Note this does
* not include the witness script push. */
std::optional<uint32_t> GetWitnessSize() const {
if (!ws.sat.valid) return {};
return ws.sat.value;
}
//! Return the expression type.
Type GetType() const { return typ; }
@@ -1260,12 +1328,12 @@ public:
bool operator==(const Node<Key>& arg) const { return Compare(*this, arg) == 0; }
// Constructors with various argument combinations, which bypass the duplicate key check.
Node(internal::NoDupCheck, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(internal::NoDupCheck, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(internal::NoDupCheck, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(internal::NoDupCheck, Fragment nt, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(internal::NoDupCheck, Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : fragment(nt), k(val), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(internal::NoDupCheck, Fragment nt, uint32_t val = 0) : fragment(nt), k(val), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(internal::NoDupCheck, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(internal::NoDupCheck, Fragment nt, std::vector<unsigned char> arg, uint32_t val = 0) : fragment(nt), k(val), data(std::move(arg)), ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(internal::NoDupCheck, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(internal::NoDupCheck, Fragment nt, std::vector<Key> key, uint32_t val = 0) : fragment(nt), k(val), keys(std::move(key)), ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(internal::NoDupCheck, Fragment nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : fragment(nt), k(val), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(internal::NoDupCheck, Fragment nt, uint32_t val = 0) : fragment(nt), k(val), ops(CalcOps()), ss(CalcStackSize()), ws(CalcWitnessSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
// Constructors with various argument combinations, which do perform the duplicate key check.
template <typename Ctx> Node(const Ctx& ctx, Fragment nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : Node(internal::NoDupCheck{}, nt, std::move(sub), std::move(arg), val) { DuplicateKeyCheck(ctx); }