Browse Source

Add stack size calculation

analysis
Pieter Wuille 5 years ago
parent
commit
5d5c0bfd71
  1. 68
      bitcoin/script/miniscript.h
  2. 3
      bitcoin/test/miniscript_tests.cpp
  3. 3
      compiler.cpp
  4. 2
      index.html
  5. 3
      js_bindings.cpp

68
bitcoin/script/miniscript.h

@ -301,6 +301,8 @@ struct Node {
private:
//! Cached ops counts.
const internal::Ops ops;
//! Cached stack size bounds.
const internal::StackSize ss;
//! Cached expression type (computed by CalcType and fed through SanitizeType).
const Type typ;
//! Cached script length (computed by CalcScriptLen).
@ -492,6 +494,51 @@ private:
return {0, {}, {}};
}
internal::StackSize CalcStackSize() const {
switch (nodetype) {
case NodeType::PK: return {1, 1};
case NodeType::PK_H: return {2, 2};
case NodeType::OLDER: return {0, {}};
case NodeType::AFTER: return {0, {}};
case NodeType::SHA256: return {1, {}};
case NodeType::RIPEMD160: return {1, {}};
case NodeType::HASH256: return {1, {}};
case NodeType::HASH160: return {1, {}};
case NodeType::ANDOR: return {Choose(subs[0]->ss.sat + subs[1]->ss.sat, subs[0]->ss.dsat + subs[2]->ss.sat), subs[0]->ss.dsat + subs[2]->ss.dsat};
case NodeType::AND_V: return {subs[0]->ss.sat + subs[1]->ss.sat, {}};
case NodeType::AND_B: return {subs[0]->ss.sat + subs[1]->ss.sat, subs[0]->ss.dsat + subs[1]->ss.dsat};
case NodeType::OR_B: return {Choose(subs[0]->ss.dsat + subs[1]->ss.sat, subs[0]->ss.sat + subs[1]->ss.dsat), subs[0]->ss.dsat + subs[1]->ss.dsat};
case NodeType::OR_C: return {Choose(subs[0]->ss.sat, subs[0]->ss.dsat + subs[1]->ss.sat), {}};
case NodeType::OR_D: return {Choose(subs[0]->ss.sat, subs[0]->ss.dsat + subs[1]->ss.sat), subs[0]->ss.dsat + subs[1]->ss.dsat};
case NodeType::OR_I: return {Choose(subs[0]->ss.sat + 1, subs[1]->ss.sat + 1), Choose(subs[0]->ss.dsat + 1, subs[1]->ss.dsat + 1)};
case NodeType::THRESH_M: return {(uint32_t)keys.size() + 1, (uint32_t)keys.size() + 1};
case NodeType::WRAP_A: return subs[0]->ss;
case NodeType::WRAP_S: return subs[0]->ss;
case NodeType::WRAP_C: return subs[0]->ss;
case NodeType::WRAP_D: return {1 + subs[0]->ss.sat, 1};
case NodeType::WRAP_V: return {subs[0]->ss.sat, {}};
case NodeType::WRAP_J: return {subs[0]->ss.sat, 1};
case NodeType::WRAP_N: return subs[0]->ss;
case NodeType::TRUE: return {0, {}};
case NodeType::FALSE: return {{}, 0};
case NodeType::THRESH: {
uint32_t dsat = 0;
int32_t sat_sum = 0;
std::vector<int32_t> diffs;
for (const auto& sub : subs) {
dsat += sub->ss.dsat.value; // The type system requires "d" for thresh, so dsat must always be valid.
if (sub->ss.sat.valid) diffs.push_back((int32_t)sub->ss.sat.value - sub->ss.dsat.value);
}
if (diffs.size() < k) return {{}, dsat};
std::sort(diffs.begin(), diffs.end());
for (size_t i = diffs.size() - k; i < diffs.size(); ++i) sat_sum += diffs[i];
return {sat_sum + dsat, dsat};
}
}
assert(false);
return {{}, {}};
}
template<typename Ctx>
internal::InputResult ProduceInput(const Ctx& ctx, bool nonmal) const {
auto ret = ProduceInputHelper(ctx, nonmal);
@ -695,11 +742,14 @@ public:
//! Return the size of the script for this expression (faster than ToString().size()).
size_t ScriptSize() const { return scriptlen; }
//! Return the number of non-push opcodes in this script.
//! Return the maximum number of ops needed to satisfy this script non-malleably.
uint32_t GetOps() const { return ops.stat + ops.sat.value; }
//! Return the number of non-push opcodes in this script.
bool CheckOpsLimit() const { return GetOps() <= 201; }
//! Check the ops limit of this script.
bool CheckOpsLimit() const { return GetOps() <= MAX_OPS_PER_SCRIPT; }
//! Return the maximum number of stack elements needed to satisfy this script non-malleably.
uint32_t GetStackSize() const { return ss.sat.value; }
//! Return the expression type.
Type GetType() const { return typ; }
@ -737,12 +787,12 @@ public:
}
// Constructors with various argument combinations.
Node(NodeType nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : nodetype(nt), k(val), data(std::move(arg)), subs(std::move(sub)), ops(CalcOps()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(NodeType nt, std::vector<unsigned char> arg, uint32_t val = 0) : nodetype(nt), k(val), data(std::move(arg)), ops(CalcOps()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(NodeType nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : nodetype(nt), k(val), keys(std::move(key)), subs(std::move(sub)), ops(CalcOps()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(NodeType nt, std::vector<Key> key, uint32_t val = 0) : nodetype(nt), k(val), keys(std::move(key)), ops(CalcOps()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(NodeType nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : nodetype(nt), k(val), subs(std::move(sub)), ops(CalcOps()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(NodeType nt, uint32_t val = 0) : nodetype(nt), k(val), ops(CalcOps()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(NodeType nt, std::vector<NodeRef<Key>> sub, std::vector<unsigned char> arg, uint32_t val = 0) : nodetype(nt), k(val), data(std::move(arg)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(NodeType nt, std::vector<unsigned char> arg, uint32_t val = 0) : nodetype(nt), k(val), data(std::move(arg)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(NodeType nt, std::vector<NodeRef<Key>> sub, std::vector<Key> key, uint32_t val = 0) : nodetype(nt), k(val), keys(std::move(key)), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(NodeType nt, std::vector<Key> key, uint32_t val = 0) : nodetype(nt), k(val), keys(std::move(key)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(NodeType nt, std::vector<NodeRef<Key>> sub, uint32_t val = 0) : nodetype(nt), k(val), subs(std::move(sub)), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
Node(NodeType nt, uint32_t val = 0) : nodetype(nt), k(val), ops(CalcOps()), ss(CalcStackSize()), typ(CalcType()), scriptlen(CalcScriptLen()) {}
};
namespace internal {

3
bitcoin/test/miniscript_tests.cpp

@ -360,7 +360,7 @@ NodeRef RandomNode(miniscript::Type typ, int complexity) {
NodeRef ret;
do {
ret = GenNode(typ, complexity);
} while (!ret || !(ret->GetType() << typ) || !ret->CheckOpsLimit());
} while (!ret || !(ret->GetType() << typ) || !ret->CheckOpsLimit() || ret->GetStackSize() > MAX_STANDARD_P2WSH_STACK_ITEMS);
return ret;
}
@ -512,6 +512,7 @@ void Verify(const std::string& testcase, const NodeRef& node, const TestContext&
// Use a test signature checker aware of which afters/olders we made valid.
TestSignatureChecker checker(&ctx);
ScriptError serror;
if (nonmal) BOOST_CHECK(stack.size() <= node->GetStackSize());
if (!VerifyScript(CScript(), spk, &witness, STANDARD_SCRIPT_VERIFY_FLAGS, checker, &serror)) {
if (nonmal || serror != SCRIPT_ERR_OP_COUNT) { // Only the nonmalleable satisfier is guaranteed to stay below the ops limit
fprintf(stderr, "\nFAILURE: %s\n", testcase.c_str());

3
compiler.cpp

@ -16,6 +16,8 @@
const CompilerContext COMPILER_CTX;
static const unsigned int MAX_STANDARD_P2WSH_STACK_ITEMS = 100;
namespace {
using Node = miniscript::NodeRef<std::string>;
@ -395,6 +397,7 @@ struct Compilation {
auto new_typ = node->GetType();
double cost = Cost(pair, node);
if (!node->CheckOpsLimit()) return;
if (node->GetStackSize() > MAX_STANDARD_P2WSH_STACK_ITEMS) return;
if (!(new_typ << "m"_mst)) return;
if (cost > 10000) return;
for (const Result& x : results) {

2
index.html

@ -439,7 +439,7 @@ are listed for completeness, but marked in <span class="text-muted">[grey]</span
<tr><td><code>0</code></td><td></td><td>-</td></tr>
<tr><td><code>1</code></td><td>-</td><td></td></tr>
<tr><td><code>pk(key)</code></td><td>0</td><td>sig</td></tr>
<tr><td><code>pk_h(key)</code></td><td>0</td><td>sig key</td></tr>
<tr><td><code>pk_h(key)</code></td><td>0 key</td><td>sig key</td></tr>
<tr><td><code>older(n)</code></td><td>-</td><td></td></tr>
<tr><td><code>after(n)</code></td><td>-</td><td></td></tr>
<tr><td><code>sha256(h)</code></td><td>any 32-byte vector except the preimage</td><td>preimage</td></tr>

3
js_bindings.cpp

@ -34,7 +34,8 @@ std::string Props(const miniscript::NodeRef<std::string>& node, std::string in)
if (node->GetType() << "s"_mst) ret += 's';
}
ret += "&#13;scriptlen: " + std::to_string(node->ScriptSize());
ret += "&#13;maxop: " + std::to_string(node->GetOps());
ret += "&#13;max ops: " + std::to_string(node->GetOps());
ret += "&#13;max stack size: " + std::to_string(node->GetStackSize());
return std::move(ret) + "\">" + std::move(in) + "</span>";
}

Loading…
Cancel
Save