From f51f395f4da5f23afccc1e2aa792e4ded4ff4b1b Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Tue, 27 Aug 2019 18:22:26 -0700 Subject: [PATCH] Comments and move more to cpp --- bitcoin/script/miniscript.cpp | 99 ++++++++++++++++++++++- bitcoin/script/miniscript.h | 147 +++++++--------------------------- 2 files changed, 124 insertions(+), 122 deletions(-) diff --git a/bitcoin/script/miniscript.cpp b/bitcoin/script/miniscript.cpp index 48b5e65..ef9729d 100644 --- a/bitcoin/script/miniscript.cpp +++ b/bitcoin/script/miniscript.cpp @@ -31,7 +31,50 @@ Type SanitizeType(Type e) { return e; } -Type CalcSimpleType(NodeType nodetype, Type x, Type y, Type z) { +Type CalcSimpleType(NodeType nodetype, Type x, Type y, Type z, const std::vector& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys) { + // Sanity check on sigops +// if (GetOps() > 201) return ""_mst; + + // Sanity check on data + if (nodetype == NodeType::SHA256 || nodetype == NodeType::HASH256) { + assert(data_size == 32); + } else if (nodetype == NodeType::RIPEMD160 || nodetype == NodeType::HASH160) { + assert(data_size == 20); + } else { + assert(data_size == 0); + } + // Sanity check on k + if (nodetype == NodeType::OLDER || nodetype == NodeType::AFTER) { + assert(k >= 1 && k < 0x80000000UL); + } else if (nodetype == NodeType::THRESH_M) { + assert(k >= 1 && k <= n_keys); + } else if (nodetype == NodeType::THRESH) { + assert(k > 1 && k < n_subs); + } else { + assert(k == 0); + } + // Sanity check on subs + if (nodetype == NodeType::AND_V || nodetype == NodeType::AND_B || nodetype == NodeType::OR_B || + nodetype == NodeType::OR_C || nodetype == NodeType::OR_I || nodetype == NodeType::OR_D) { + assert(n_subs == 2); + } else if (nodetype == NodeType::ANDOR) { + assert(n_subs == 3); + } else if (nodetype == NodeType::WRAP_A || nodetype == NodeType::WRAP_S || nodetype == NodeType::WRAP_C || + nodetype == NodeType::WRAP_D || nodetype == NodeType::WRAP_V || nodetype == NodeType::WRAP_J || + nodetype == NodeType::WRAP_N) { + assert(n_subs == 1); + } else if (nodetype != NodeType::THRESH) { + assert(n_subs == 0); + } + // Sanity check on keys + if (nodetype == NodeType::PK || nodetype == NodeType::PK_H) { + assert(n_keys == 1); + } else if (nodetype == NodeType::THRESH_M) { + assert(n_keys >= 1 && n_keys <= 20); + } else { + assert(n_keys == 0); + } + // Below is the per-nodetype logic for computing the expression types. // It heavily relies on Type's << operator (where "X << a_mst" means // "X has all properties listed in a"). @@ -130,12 +173,64 @@ Type CalcSimpleType(NodeType nodetype, Type x, Type y, Type z) { (z & (x | y) & "s"_mst) | // s=s_z*(s_x+s_y) "x"_mst; // x case NodeType::THRESH_M: return "Bnudems"_mst; - case NodeType::THRESH: break; + case NodeType::THRESH: { + bool all_e = true; + bool all_m = true; + uint32_t args = 0; + uint32_t num_s = 0; + for (size_t i = 0; i < sub_types.size(); ++i) { + Type t = sub_types[i]; + if (!(t << (i ? "Wdu"_mst : "Bdu"_mst))) return ""_mst; // Require Bdu, Wdu, Wdu, ... + if (!(t << "e"_mst)) all_e = false; + if (!(t << "m"_mst)) all_m = false; + if (t << "s"_mst) num_s += 1; + args += (t << "z"_mst) ? 0 : (t << "o"_mst) ? 1 : 2; + } + return "Bdu"_mst | + "z"_mst.If(args == 0) | // z=all z + "o"_mst.If(args == 1) | // o=all z except one o + "e"_mst.If(all_e && num_s == n_subs) | // e=all e and all s + "m"_mst.If(all_e && all_m && num_s >= n_subs - k) | // m=all e, >=(n-k) s + "s"_mst.If(num_s >= n_subs - k + 1); // s= >=(n-k+1) s + } } assert(false); return ""_mst; } +size_t ComputeScriptLen(NodeType nodetype, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys) { + switch (nodetype) { + case NodeType::PK: return subsize + 34; + case NodeType::PK_H: return subsize + 3 + 21; + case NodeType::OLDER: return subsize + 1 + (CScript() << k).size(); + case NodeType::AFTER: return subsize + 1 + (CScript() << k).size(); + case NodeType::HASH256: return subsize + 4 + 2 + 33; + case NodeType::HASH160: return subsize + 4 + 2 + 21; + case NodeType::SHA256: return subsize + 4 + 2 + 33; + case NodeType::RIPEMD160: return subsize + 4 + 2 + 21; + case NodeType::WRAP_A: return subsize + 2; + case NodeType::WRAP_S: return subsize + 1; + case NodeType::WRAP_C: return subsize + 1; + case NodeType::WRAP_D: return subsize + 3; + case NodeType::WRAP_V: return subsize + (sub0typ << "x"_mst); + case NodeType::WRAP_J: return subsize + 4; + case NodeType::WRAP_N: return subsize + 1; + case NodeType::TRUE: return 1; + case NodeType::FALSE: return 1; + case NodeType::AND_V: return subsize; + case NodeType::AND_B: return subsize + 1; + case NodeType::OR_B: return subsize + 1; + case NodeType::OR_D: return subsize + 3; + case NodeType::OR_C: return subsize + 2; + case NodeType::OR_I: return subsize + 3; + case NodeType::ANDOR: return subsize + 3; + case NodeType::THRESH: return subsize + n_subs + 1; + case NodeType::THRESH_M: return subsize + 3 + (n_keys > 16) + (k > 16) + 34 * n_keys; + } + assert(false); + return 0; +} + InputStack& InputStack::WithSig() { has_sig = true; return *this; diff --git a/bitcoin/script/miniscript.h b/bitcoin/script/miniscript.h index f08a7f7..3fe5d6b 100644 --- a/bitcoin/script/miniscript.h +++ b/bitcoin/script/miniscript.h @@ -194,49 +194,50 @@ enum class NodeType { namespace internal { -//! Helper function for Node::CalcType for everything except `thresh` nodes. -Type CalcSimpleType(NodeType nodetype, Type x, Type y, Type z); +//! Helper function for Node::CalcType. +Type CalcSimpleType(NodeType nodetype, Type x, Type y, Type z, const std::vector& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys); + +//! Helper function for Node::CalcScriptLen. +size_t ComputeScriptLen(NodeType nodetype, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys); //! A helper sanitizer/checker for the output of CalcType. Type SanitizeType(Type x); +//! An object representing a sequence of witness stack elements. struct InputStack { + //! Whether this stack is valid for its intended purpose (satisfaction or dissatisfaction of a Node). bool valid = false; + //! Whether this stack contains a digital signature. bool has_sig = false; + //! Whether this stack is malleable (can be turned into an equally valid other stack by a third party). bool malleable = false; + //! Whether this stack is non-canonical (using a construction known to be unnecessary for satisfaction). bool non_canon = false; + //! Serialized witness size. size_t size = 0; + //! Data elements. std::vector> stack; - - InputStack(InputStack&& x) = default; - InputStack(const InputStack& x) = default; - InputStack& operator=(InputStack&& x) = default; - InputStack& operator=(const InputStack& x) = default; - - explicit InputStack(bool val) : valid(val), size(valid ? 0 : std::numeric_limits::max()) {} + //! Construct an empty stack (valid or invalid). + explicit InputStack(bool val) : valid(val), size(0) {} + //! Construct a valid single-element stack (with an element up to 75 bytes). InputStack(std::vector in) : valid(true), size(in.size() + 1), stack(Vector(std::move(in))) {} - //! Mark this input stack as having a signature. InputStack& WithSig(); - //! Mark this input stack as non-canonical (known to not be necessary in non-malleable satisfactions). InputStack& NonCanon(); - //! Mark this input stack as malleable. InputStack& Malleable(bool x = true); - + //! Compare two InputStack objects. friend bool operator<(const InputStack& a, const InputStack& b); - //! Concatenate two input stacks. friend InputStack operator+(InputStack a, InputStack b); - //! Choose between two potential input stacks. friend InputStack Choose(InputStack a, InputStack b, bool nonmalleable); }; +//! A pair of a satisfaction and a dissatisfaction InputStack. struct InputResult { InputStack nsat, sat; - InputResult(InputStack in_nsat, InputStack in_sat) : nsat(std::move(in_nsat)), sat(std::move(in_sat)) {} }; @@ -247,142 +248,48 @@ template struct Node { //! What node type this node is. const NodeType nodetype; - //! The k parameter (time for OLDER/AFTER, threshold for THRESH(_M)) const uint32_t k = 0; - //! The keys used by this expression (only for PK/PK_H/THRESH_M) const std::vector keys; - //! The data bytes in this expression (only for HASH160/HASH256/SHA256/RIPEMD10). const std::vector data; - //! Subexpressions (for WRAP_*/AND_*/OR_*/ANDOR/THRESH) const std::vector> subs; private: //! Non-push opcodes in the corresponding script (static, non-sat, sat) const int ops, nops, sops; - //! Cached expression type (computed by CalcType and fed through SanitizeType). const Type typ; - //! Cached script length (computed by CalcScriptLen). const size_t scriptlen; //! Compute the length of the script for this miniscript (including children). size_t CalcScriptLen() const { - size_t ret = 0; + size_t subsize = 0; for (const auto& sub : subs) { - ret += sub->ScriptSize(); - } - switch (nodetype) { - case NodeType::PK: return ret + 34; - case NodeType::PK_H: return ret + 3 + 21; - case NodeType::OLDER: return ret + 1 + (CScript() << k).size(); - case NodeType::AFTER: return ret + 1 + (CScript() << k).size(); - case NodeType::HASH256: return ret + 4 + 2 + 33; - case NodeType::HASH160: return ret + 4 + 2 + 21; - case NodeType::SHA256: return ret + 4 + 2 + 33; - case NodeType::RIPEMD160: return ret + 4 + 2 + 21; - case NodeType::WRAP_A: return ret + 2; - case NodeType::WRAP_S: return ret + 1; - case NodeType::WRAP_C: return ret + 1; - case NodeType::WRAP_D: return ret + 3; - case NodeType::WRAP_V: return ret + (subs[0]->GetType() << "x"_mst); - case NodeType::WRAP_J: return ret + 4; - case NodeType::WRAP_N: return ret + 1; - case NodeType::TRUE: return 1; - case NodeType::FALSE: return 1; - case NodeType::AND_V: return ret; - case NodeType::AND_B: return ret + 1; - case NodeType::OR_B: return ret + 1; - case NodeType::OR_D: return ret + 3; - case NodeType::OR_C: return ret + 2; - case NodeType::OR_I: return ret + 3; - case NodeType::ANDOR: return ret + 3; - case NodeType::THRESH: return ret + subs.size() + 1; - case NodeType::THRESH_M: return ret + 3 + (keys.size() > 16) + (k > 16) + 34 * keys.size(); + subsize += sub->ScriptSize(); } - assert(false); - return 0; + Type sub0type = subs.size() > 0 ? subs[0]->GetType() : ""_mst; + return internal::ComputeScriptLen(nodetype, sub0type, subsize, k, subs.size(), keys.size()); } //! Compute the type for this miniscript. Type CalcType() const { using namespace internal; - // Sanity check on sigops - if (GetOps() > 201) return ""_mst; - - // Sanity check on data - if (nodetype == NodeType::SHA256 || nodetype == NodeType::HASH256) { - assert(data.size() == 32); - } else if (nodetype == NodeType::RIPEMD160 || nodetype == NodeType::HASH160) { - assert(data.size() == 20); - } else { - assert(data.size() == 0); - } - // Sanity check on k - if (nodetype == NodeType::OLDER || nodetype == NodeType::AFTER) { - assert(k >= 1 && k < 0x80000000UL); - } else if (nodetype == NodeType::THRESH_M) { - assert(k >= 1 && k <= keys.size()); - } else if (nodetype == NodeType::THRESH) { - assert(k > 1 && k < subs.size()); - } else { - assert(k == 0); - } - // Sanity check on subs - if (nodetype == NodeType::AND_V || nodetype == NodeType::AND_B || nodetype == NodeType::OR_B || - nodetype == NodeType::OR_C || nodetype == NodeType::OR_I || nodetype == NodeType::OR_D) { - assert(subs.size() == 2); - } else if (nodetype == NodeType::ANDOR) { - assert(subs.size() == 3); - } else if (nodetype == NodeType::WRAP_A || nodetype == NodeType::WRAP_S || nodetype == NodeType::WRAP_C || - nodetype == NodeType::WRAP_D || nodetype == NodeType::WRAP_V || nodetype == NodeType::WRAP_J || - nodetype == NodeType::WRAP_N) { - assert(subs.size() == 1); - } else if (nodetype != NodeType::THRESH) { - assert(subs.size() == 0); - } - // Sanity check on keys - if (nodetype == NodeType::PK || nodetype == NodeType::PK_H) { - assert(keys.size() == 1); - } else if (nodetype == NodeType::THRESH_M) { - assert(keys.size() >= 1 && keys.size() <= 20); - } else { - assert(keys.size() == 0); - } - - // THRESH has a variable number of subexpression; perform all typing logic here. + // THRESH has a variable number of subexpression + std::vector sub_types; if (nodetype == NodeType::THRESH) { - uint32_t n = subs.size(); - bool all_e = true; - bool all_m = true; - uint32_t args = 0; - uint32_t num_s = 0; - for (uint32_t i = 0; i < n; ++i) { - Type t = subs[i]->GetType(); - if (!(t << (i ? "Wdu"_mst : "Bdu"_mst))) return ""_mst; // Require Bdu, Wdu, Wdu, ... - if (!(t << "e"_mst)) all_e = false; - if (!(t << "m"_mst)) all_m = false; - if (t << "s"_mst) num_s += 1; - args += (t << "z"_mst) ? 0 : (t << "o"_mst) ? 1 : 2; - } - return "Bdu"_mst | - "z"_mst.If(args == 0) | // z=all z - "o"_mst.If(args == 1) | // o=all z except one o - "e"_mst.If(all_e && num_s == n) | // e=all e and all s - "m"_mst.If(all_e && all_m && num_s >= n - k) | // m=all e, >=(n-k) s - "s"_mst.If(num_s >= n - k + 1); // s= >=(n-k+1) s + for (const auto& sub : subs) sub_types.push_back(sub->GetType()); } - - // All other nodes than THRESH can be computed just from the types of the subexpexpressions. + // All other nodes than THRESH can be computed just from the types of the 0-3 subexpexpressions. Type x = subs.size() > 0 ? subs[0]->GetType() : ""_mst; Type y = subs.size() > 1 ? subs[1]->GetType() : ""_mst; Type z = subs.size() > 2 ? subs[2]->GetType() : ""_mst; - return SanitizeType(CalcSimpleType(nodetype, x, y, z)); + + return SanitizeType(CalcSimpleType(nodetype, x, y, z, sub_types, k, data.size(), subs.size(), keys.size())); } //! Internal code for ToScript.