Merge bitcoin/bitcoin#24149: Signing support for Miniscript Descriptors

6c7a17a8e0 psbt: support externally provided preimages for Miniscript satisfaction (Antoine Poinsot)
840a396029 qa: add a "smart" Miniscript fuzz target (Antoine Poinsot)
17e3547241 qa: add a fuzz target generating random nodes from a binary encoding (Antoine Poinsot)
611e12502a qa: functional test Miniscript signing with key and timelocks (Antoine Poinsot)
d57b7f2021 refactor: make descriptors in Miniscript functional test more readable (Antoine Poinsot)
0a8fc9e200 wallet: check solvability using descriptor in AvailableCoins (Antoine Poinsot)
560e62b1e2 script/sign: signing support for Miniscripts with hash preimage challenges (Antoine Poinsot)
a2f81b6a8f script/sign: signing support for Miniscript with timelocks (Antoine Poinsot)
61c6d1a844 script/sign: basic signing support for Miniscript descriptors (Antoine Poinsot)
4242c1c521 Align 'e' property of or_d and andor with website spec (Pieter Wuille)
f5deb41780 Various additional explanations of the satisfaction logic from Pieter (Pieter Wuille)
22c5b00345 miniscript: satisfaction support (Antoine Poinsot)

Pull request description:

  This makes the Miniscript descriptors solvable.

  Note this introduces signing support for much more complex scripts than the wallet was previously able to solve, and the whole tooling isn't provided for a complete Miniscript integration in the wallet. Particularly, the PSBT<->Miniscript integration isn't entirely covered in this PR.

ACKs for top commit:
  achow101:
    ACK 6c7a17a8e0
  sipa:
    utACK 6c7a17a8e0 (to the extent that it's not my own code).

Tree-SHA512: a71ec002aaf66bd429012caa338fc58384067bcd2f453a46e21d381ed1bacc8e57afb9db57c0fb4bf40de43b30808815e9ebc0ae1fbd9e61df0e7b91a17771cc
This commit is contained in:
fanquake
2023-02-16 09:55:21 +00:00
17 changed files with 1879 additions and 58 deletions

View File

@@ -1014,7 +1014,7 @@ public:
return false;
}
bool IsSolvable() const override { return false; } // For now, mark these descriptors as non-solvable (as we don't have signing logic for them).
bool IsSolvable() const override { return true; }
bool IsSingleType() const final { return true; }
};

View File

@@ -172,8 +172,8 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Ty
(y & "B"_mst).If(x << "Bdu"_mst) | // B=B_y*B_x*d_x*u_x
(x & "o"_mst).If(y << "z"_mst) | // o=o_x*z_y
(x & y & "m"_mst).If(x << "e"_mst && (x | y) << "s"_mst) | // m=m_x*m_y*e_x*(s_x+s_y)
(x & y & "zes"_mst) | // z=z_x*z_y, e=e_x*e_y, s=s_x*s_y
(y & "ufd"_mst) | // u=u_y, f=f_y, d=d_y
(x & y & "zs"_mst) | // z=z_x*z_y, s=s_x*s_y
(y & "ufde"_mst) | // u=u_y, f=f_y, d=d_y, e=e_y
"x"_mst | // x
((x | y) & "ghij"_mst) | // g=g_x+g_y, h=h_x+h_y, i=i_x+i_y, j=j_x+j_y
(x & y & "k"_mst); // k=k_x*k_y
@@ -201,7 +201,7 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Ty
(y & z & "u"_mst) | // u=u_y*u_z
(z & "f"_mst).If((x << "s"_mst) || (y << "f"_mst)) | // f=(s_x+f_y)*f_z
(z & "d"_mst) | // d=d_z
(x & z & "e"_mst).If(x << "s"_mst || y << "f"_mst) | // e=e_x*e_z*(s_x+f_y)
(z & "e"_mst).If(x << "s"_mst || y << "f"_mst) | // e=e_z*(s_x+f_y)
(x & y & z & "m"_mst).If(x << "e"_mst && (x | y | z) << "s"_mst) | // m=m_x*m_y*m_z*e_x*(s_x+s_y+s_z)
(z & (x | y) & "s"_mst) | // s=s_z*(s_x+s_y)
"x"_mst | // x
@@ -279,6 +279,76 @@ size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_
assert(false);
}
InputStack& InputStack::SetAvailable(Availability avail) {
available = avail;
if (avail == Availability::NO) {
stack.clear();
size = std::numeric_limits<size_t>::max();
has_sig = false;
malleable = false;
non_canon = false;
}
return *this;
}
InputStack& InputStack::SetWithSig() {
has_sig = true;
return *this;
}
InputStack& InputStack::SetNonCanon() {
non_canon = true;
return *this;
}
InputStack& InputStack::SetMalleable(bool x) {
malleable = x;
return *this;
}
InputStack operator+(InputStack a, InputStack b) {
a.stack = Cat(std::move(a.stack), std::move(b.stack));
if (a.available != Availability::NO && b.available != Availability::NO) a.size += b.size;
a.has_sig |= b.has_sig;
a.malleable |= b.malleable;
a.non_canon |= b.non_canon;
if (a.available == Availability::NO || b.available == Availability::NO) {
a.SetAvailable(Availability::NO);
} else if (a.available == Availability::MAYBE || b.available == Availability::MAYBE) {
a.SetAvailable(Availability::MAYBE);
}
return a;
}
InputStack operator|(InputStack a, InputStack b) {
// If only one is invalid, pick the other one. If both are invalid, pick an arbitrary one.
if (a.available == Availability::NO) return b;
if (b.available == Availability::NO) return a;
// If only one of the solutions has a signature, we must pick the other one.
if (!a.has_sig && b.has_sig) return a;
if (!b.has_sig && a.has_sig) return b;
if (!a.has_sig && !b.has_sig) {
// If neither solution requires a signature, the result is inevitably malleable.
a.malleable = true;
b.malleable = true;
} else {
// If both options require a signature, prefer the non-malleable one.
if (b.malleable && !a.malleable) return a;
if (a.malleable && !b.malleable) return b;
}
// Between two malleable or two non-malleable solutions, pick the smaller one between
// YESes, and the bigger ones between MAYBEs. Prefer YES over MAYBE.
if (a.available == Availability::YES && b.available == Availability::YES) {
return std::move(a.size <= b.size ? a : b);
} else if (a.available == Availability::MAYBE && b.available == Availability::MAYBE) {
return std::move(a.size >= b.size ? a : b);
} else if (a.available == Availability::YES) {
return a;
} else {
return b;
}
}
std::optional<std::vector<Opcode>> DecomposeScript(const CScript& script)
{
std::vector<Opcode> out;

View File

@@ -223,6 +223,11 @@ enum class Fragment {
// WRAP_U(X) is represented as OR_I(X,0)
};
enum class Availability {
NO,
YES,
MAYBE,
};
namespace internal {
@@ -235,6 +240,62 @@ size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_
//! 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).
* The MAYBE value is used for size estimation, when keys/preimages may actually be unavailable,
* but may be available at signing time. This makes the InputStack structure and signing logic,
* filled with dummy signatures/preimages usable for witness size estimation.
*/
Availability available = Availability::YES;
//! 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).
//! Note that this flag does not affect the satisfaction algorithm; it is only used for sanity checking.
bool non_canon = false;
//! Serialized witness size.
size_t size = 0;
//! Data elements.
std::vector<std::vector<unsigned char>> stack;
//! Construct an empty stack (valid).
InputStack() {}
//! Construct a valid single-element stack (with an element up to 75 bytes).
InputStack(std::vector<unsigned char> in) : size(in.size() + 1), stack(Vector(std::move(in))) {}
//! Change availability
InputStack& SetAvailable(Availability avail);
//! Mark this input stack as having a signature.
InputStack& SetWithSig();
//! Mark this input stack as non-canonical (known to not be necessary in non-malleable satisfactions).
InputStack& SetNonCanon();
//! Mark this input stack as malleable.
InputStack& SetMalleable(bool x = true);
//! Concatenate two input stacks.
friend InputStack operator+(InputStack a, InputStack b);
//! Choose between two potential input stacks.
friend InputStack operator|(InputStack a, InputStack b);
};
/** A stack consisting of a single zero-length element (interpreted as 0 by the script interpreter in numeric context). */
static const auto ZERO = InputStack(std::vector<unsigned char>());
/** A stack consisting of a single malleable 32-byte 0x0000...0000 element (for dissatisfying hash challenges). */
static const auto ZERO32 = InputStack(std::vector<unsigned char>(32, 0)).SetMalleable();
/** A stack consisting of a single 0x01 element (interpreted as 1 by the script interpreted in numeric context). */
static const auto ONE = InputStack(Vector((unsigned char)1));
/** The empty stack. */
static const auto EMPTY = InputStack();
/** A stack representing the lack of any (dis)satisfactions. */
static const auto INVALID = InputStack().SetAvailable(Availability::NO);
//! A pair of a satisfaction and a dissatisfaction InputStack.
struct InputResult {
InputStack nsat, sat;
template<typename A, typename B>
InputResult(A&& in_nsat, B&& in_sat) : nsat(std::forward<A>(in_nsat)), sat(std::forward<B>(in_sat)) {}
};
//! Class whose objects represent the maximum of a list of integers.
template<typename I>
struct MaxInt {
@@ -785,6 +846,226 @@ private:
assert(false);
}
template<typename Ctx>
internal::InputResult ProduceInput(const Ctx& ctx) const {
using namespace internal;
// Internal function which is invoked for every tree node, constructing satisfaction/dissatisfactions
// given those of its subnodes.
auto helper = [&ctx](const Node& node, Span<InputResult> subres) -> InputResult {
switch (node.fragment) {
case Fragment::PK_K: {
std::vector<unsigned char> sig;
Availability avail = ctx.Sign(node.keys[0], sig);
return {ZERO, InputStack(std::move(sig)).SetWithSig().SetAvailable(avail)};
}
case Fragment::PK_H: {
std::vector<unsigned char> key = ctx.ToPKBytes(node.keys[0]), sig;
Availability avail = ctx.Sign(node.keys[0], sig);
return {ZERO + InputStack(key), (InputStack(std::move(sig)).SetWithSig() + InputStack(key)).SetAvailable(avail)};
}
case Fragment::MULTI: {
// sats[j] represents the best stack containing j valid signatures (out of the first i keys).
// In the loop below, these stacks are built up using a dynamic programming approach.
// sats[0] starts off being {0}, due to the CHECKMULTISIG bug that pops off one element too many.
std::vector<InputStack> sats = Vector(ZERO);
for (size_t i = 0; i < node.keys.size(); ++i) {
std::vector<unsigned char> sig;
Availability avail = ctx.Sign(node.keys[i], sig);
// Compute signature stack for just the i'th key.
auto sat = InputStack(std::move(sig)).SetWithSig().SetAvailable(avail);
// Compute the next sats vector: next_sats[0] is a copy of sats[0] (no signatures). All further
// next_sats[j] are equal to either the existing sats[j], or sats[j-1] plus a signature for the
// current (i'th) key. The very last element needs all signatures filled.
std::vector<InputStack> next_sats;
next_sats.push_back(sats[0]);
for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back(sats[j] | (std::move(sats[j - 1]) + sat));
next_sats.push_back(std::move(sats[sats.size() - 1]) + std::move(sat));
// Switch over.
sats = std::move(next_sats);
}
// The dissatisfaction consists of k+1 stack elements all equal to 0.
InputStack nsat = ZERO;
for (size_t i = 0; i < node.k; ++i) nsat = std::move(nsat) + ZERO;
assert(node.k <= sats.size());
return {std::move(nsat), std::move(sats[node.k])};
}
case Fragment::THRESH: {
// sats[k] represents the best stack that satisfies k out of the *last* i subexpressions.
// In the loop below, these stacks are built up using a dynamic programming approach.
// sats[0] starts off empty.
std::vector<InputStack> sats = Vector(EMPTY);
for (size_t i = 0; i < subres.size(); ++i) {
// Introduce an alias for the i'th last satisfaction/dissatisfaction.
auto& res = subres[subres.size() - i - 1];
// Compute the next sats vector: next_sats[0] is sats[0] plus res.nsat (thus containing all dissatisfactions
// so far. next_sats[j] is either sats[j] + res.nsat (reusing j earlier satisfactions) or sats[j-1] + res.sat
// (reusing j-1 earlier satisfactions plus a new one). The very last next_sats[j] is all satisfactions.
std::vector<InputStack> next_sats;
next_sats.push_back(sats[0] + res.nsat);
for (size_t j = 1; j < sats.size(); ++j) next_sats.push_back((sats[j] + res.nsat) | (std::move(sats[j - 1]) + res.sat));
next_sats.push_back(std::move(sats[sats.size() - 1]) + std::move(res.sat));
// Switch over.
sats = std::move(next_sats);
}
// At this point, sats[k].sat is the best satisfaction for the overall thresh() node. The best dissatisfaction
// is computed by gathering all sats[i].nsat for i != k.
InputStack nsat = INVALID;
for (size_t i = 0; i < sats.size(); ++i) {
// i==k is the satisfaction; i==0 is the canonical dissatisfaction;
// the rest are non-canonical (a no-signature dissatisfaction - the i=0
// form - is always available) and malleable (due to overcompleteness).
// Marking the solutions malleable here is not strictly necessary, as they
// should already never be picked in non-malleable solutions due to the
// availability of the i=0 form.
if (i != 0 && i != node.k) sats[i].SetMalleable().SetNonCanon();
// Include all dissatisfactions (even these non-canonical ones) in nsat.
if (i != node.k) nsat = std::move(nsat) | std::move(sats[i]);
}
assert(node.k <= sats.size());
return {std::move(nsat), std::move(sats[node.k])};
}
case Fragment::OLDER: {
return {INVALID, ctx.CheckOlder(node.k) ? EMPTY : INVALID};
}
case Fragment::AFTER: {
return {INVALID, ctx.CheckAfter(node.k) ? EMPTY : INVALID};
}
case Fragment::SHA256: {
std::vector<unsigned char> preimage;
Availability avail = ctx.SatSHA256(node.data, preimage);
return {ZERO32, InputStack(std::move(preimage)).SetAvailable(avail)};
}
case Fragment::RIPEMD160: {
std::vector<unsigned char> preimage;
Availability avail = ctx.SatRIPEMD160(node.data, preimage);
return {ZERO32, InputStack(std::move(preimage)).SetAvailable(avail)};
}
case Fragment::HASH256: {
std::vector<unsigned char> preimage;
Availability avail = ctx.SatHASH256(node.data, preimage);
return {ZERO32, InputStack(std::move(preimage)).SetAvailable(avail)};
}
case Fragment::HASH160: {
std::vector<unsigned char> preimage;
Availability avail = ctx.SatHASH160(node.data, preimage);
return {ZERO32, InputStack(std::move(preimage)).SetAvailable(avail)};
}
case Fragment::AND_V: {
auto& x = subres[0], &y = subres[1];
// As the dissatisfaction here only consist of a single option, it doesn't
// actually need to be listed (it's not required for reasoning about malleability of
// other options), and is never required (no valid miniscript relies on the ability
// to satisfy the type V left subexpression). It's still listed here for
// completeness, as a hypothetical (not currently implemented) satisfier that doesn't
// care about malleability might in some cases prefer it still.
return {(y.nsat + x.sat).SetNonCanon(), y.sat + x.sat};
}
case Fragment::AND_B: {
auto& x = subres[0], &y = subres[1];
// Note that it is not strictly necessary to mark the 2nd and 3rd dissatisfaction here
// as malleable. While they are definitely malleable, they are also non-canonical due
// to the guaranteed existence of a no-signature other dissatisfaction (the 1st)
// option. Because of that, the 2nd and 3rd option will never be chosen, even if they
// weren't marked as malleable.
return {(y.nsat + x.nsat) | (y.sat + x.nsat).SetMalleable().SetNonCanon() | (y.nsat + x.sat).SetMalleable().SetNonCanon(), y.sat + x.sat};
}
case Fragment::OR_B: {
auto& x = subres[0], &z = subres[1];
// The (sat(Z) sat(X)) solution is overcomplete (attacker can change either into dsat).
return {z.nsat + x.nsat, (z.nsat + x.sat) | (z.sat + x.nsat) | (z.sat + x.sat).SetMalleable().SetNonCanon()};
}
case Fragment::OR_C: {
auto& x = subres[0], &z = subres[1];
return {INVALID, std::move(x.sat) | (z.sat + x.nsat)};
}
case Fragment::OR_D: {
auto& x = subres[0], &z = subres[1];
return {z.nsat + x.nsat, std::move(x.sat) | (z.sat + x.nsat)};
}
case Fragment::OR_I: {
auto& x = subres[0], &z = subres[1];
return {(x.nsat + ONE) | (z.nsat + ZERO), (x.sat + ONE) | (z.sat + ZERO)};
}
case Fragment::ANDOR: {
auto& x = subres[0], &y = subres[1], &z = subres[2];
return {(y.nsat + x.sat).SetNonCanon() | (z.nsat + x.nsat), (y.sat + x.sat) | (z.sat + x.nsat)};
}
case Fragment::WRAP_A:
case Fragment::WRAP_S:
case Fragment::WRAP_C:
case Fragment::WRAP_N:
return std::move(subres[0]);
case Fragment::WRAP_D: {
auto &x = subres[0];
return {ZERO, x.sat + ONE};
}
case Fragment::WRAP_J: {
auto &x = subres[0];
// If a dissatisfaction with a nonzero top stack element exists, an alternative dissatisfaction exists.
// As the dissatisfaction logic currently doesn't keep track of this nonzeroness property, and thus even
// if a dissatisfaction with a top zero element is found, we don't know whether another one with a
// nonzero top stack element exists. Make the conservative assumption that whenever the subexpression is weakly
// dissatisfiable, this alternative dissatisfaction exists and leads to malleability.
return {InputStack(ZERO).SetMalleable(x.nsat.available != Availability::NO && !x.nsat.has_sig), std::move(x.sat)};
}
case Fragment::WRAP_V: {
auto &x = subres[0];
return {INVALID, std::move(x.sat)};
}
case Fragment::JUST_0: return {EMPTY, INVALID};
case Fragment::JUST_1: return {INVALID, EMPTY};
}
assert(false);
return {INVALID, INVALID};
};
auto tester = [&helper](const Node& node, Span<InputResult> subres) -> InputResult {
auto ret = helper(node, subres);
// Do a consistency check between the satisfaction code and the type checker
// (the actual satisfaction code in ProduceInputHelper does not use GetType)
// For 'z' nodes, available satisfactions/dissatisfactions must have stack size 0.
if (node.GetType() << "z"_mst && ret.nsat.available != Availability::NO) assert(ret.nsat.stack.size() == 0);
if (node.GetType() << "z"_mst && ret.sat.available != Availability::NO) assert(ret.sat.stack.size() == 0);
// For 'o' nodes, available satisfactions/dissatisfactions must have stack size 1.
if (node.GetType() << "o"_mst && ret.nsat.available != Availability::NO) assert(ret.nsat.stack.size() == 1);
if (node.GetType() << "o"_mst && ret.sat.available != Availability::NO) assert(ret.sat.stack.size() == 1);
// For 'n' nodes, available satisfactions/dissatisfactions must have stack size 1 or larger. For satisfactions,
// the top element cannot be 0.
if (node.GetType() << "n"_mst && ret.sat.available != Availability::NO) assert(ret.sat.stack.size() >= 1);
if (node.GetType() << "n"_mst && ret.nsat.available != Availability::NO) assert(ret.nsat.stack.size() >= 1);
if (node.GetType() << "n"_mst && ret.sat.available != Availability::NO) assert(!ret.sat.stack.back().empty());
// For 'd' nodes, a dissatisfaction must exist, and they must not need a signature. If it is non-malleable,
// it must be canonical.
if (node.GetType() << "d"_mst) assert(ret.nsat.available != Availability::NO);
if (node.GetType() << "d"_mst) assert(!ret.nsat.has_sig);
if (node.GetType() << "d"_mst && !ret.nsat.malleable) assert(!ret.nsat.non_canon);
// For 'f'/'s' nodes, dissatisfactions/satisfactions must have a signature.
if (node.GetType() << "f"_mst && ret.nsat.available != Availability::NO) assert(ret.nsat.has_sig);
if (node.GetType() << "s"_mst && ret.sat.available != Availability::NO) assert(ret.sat.has_sig);
// For non-malleable 'e' nodes, a non-malleable dissatisfaction must exist.
if (node.GetType() << "me"_mst) assert(ret.nsat.available != Availability::NO);
if (node.GetType() << "me"_mst) assert(!ret.nsat.malleable);
// For 'm' nodes, if a satisfaction exists, it must be non-malleable.
if (node.GetType() << "m"_mst && ret.sat.available != Availability::NO) assert(!ret.sat.malleable);
// If a non-malleable satisfaction exists, it must be canonical.
if (ret.sat.available != Availability::NO && !ret.sat.malleable) assert(!ret.sat.non_canon);
return ret;
};
return TreeEval<InputResult>(tester);
}
public:
/** Update duplicate key information in this Node.
*
@@ -877,6 +1158,47 @@ public:
});
}
//! Determine whether a Miniscript node is satisfiable. fn(node) will be invoked for all
//! key, time, and hashing nodes, and should return their satisfiability.
template<typename F>
bool IsSatisfiable(F fn) const
{
// TreeEval() doesn't support bool as NodeType, so use int instead.
return TreeEval<int>([&fn](const Node& node, Span<int> subs) -> bool {
switch (node.fragment) {
case Fragment::JUST_0:
return false;
case Fragment::JUST_1:
return true;
case Fragment::PK_K:
case Fragment::PK_H:
case Fragment::MULTI:
case Fragment::AFTER:
case Fragment::OLDER:
case Fragment::HASH256:
case Fragment::HASH160:
case Fragment::SHA256:
case Fragment::RIPEMD160:
return bool{fn(node)};
case Fragment::ANDOR:
return (subs[0] && subs[1]) || subs[2];
case Fragment::AND_V:
case Fragment::AND_B:
return subs[0] && subs[1];
case Fragment::OR_B:
case Fragment::OR_C:
case Fragment::OR_D:
case Fragment::OR_I:
return subs[0] || subs[1];
case Fragment::THRESH:
return std::count(subs.begin(), subs.end(), true) >= node.k;
default: // wrappers
assert(subs.size() == 1);
return subs[0];
}
});
}
//! Check whether this node is valid at all.
bool IsValid() const { return !(GetType() == ""_mst) && ScriptSize() <= MAX_STANDARD_P2WSH_SCRIPT_SIZE; }
@@ -904,6 +1226,18 @@ public:
//! Check whether this node is safe as a script on its own.
bool IsSane() const { return IsValidTopLevel() && IsSaneSubexpression() && NeedsSignature(); }
//! Produce a witness for this script, if possible and given the information available in the context.
//! The non-malleable satisfaction is guaranteed to be valid if it exists, and ValidSatisfaction()
//! is true. If IsSane() holds, this satisfaction is guaranteed to succeed in case the node's
//! conditions are satisfied (private keys and hash preimages available, locktimes satsified).
template<typename Ctx>
Availability Satisfy(const Ctx& ctx, std::vector<std::vector<unsigned char>>& stack, bool nonmalleable = true) const {
auto ret = ProduceInput(ctx);
if (nonmalleable && (ret.sat.malleable || !ret.sat.has_sig)) return Availability::NO;
stack = std::move(ret.sat.stack);
return ret.sat.available;
}
//! Equality testing.
bool operator==(const Node<Key>& arg) const { return Compare(*this, arg) == 0; }

View File

@@ -10,6 +10,7 @@
#include <policy/policy.h>
#include <primitives/transaction.h>
#include <script/keyorigin.h>
#include <script/miniscript.h>
#include <script/signingprovider.h>
#include <script/standard.h>
#include <uint256.h>
@@ -380,6 +381,92 @@ static CScript PushAll(const std::vector<valtype>& values)
return result;
}
template<typename M, typename K, typename V>
miniscript::Availability MsLookupHelper(const M& map, const K& key, V& value)
{
auto it = map.find(key);
if (it != map.end()) {
value = it->second;
return miniscript::Availability::YES;
}
return miniscript::Availability::NO;
}
/**
* Context for solving a Miniscript.
* If enough material (access to keys, hash preimages, ..) is given, produces a valid satisfaction.
*/
struct Satisfier {
typedef CPubKey Key;
const SigningProvider& m_provider;
SignatureData& m_sig_data;
const BaseSignatureCreator& m_creator;
const CScript& m_witness_script;
explicit Satisfier(const SigningProvider& provider LIFETIMEBOUND, SignatureData& sig_data LIFETIMEBOUND,
const BaseSignatureCreator& creator LIFETIMEBOUND,
const CScript& witscript LIFETIMEBOUND) : m_provider(provider),
m_sig_data(sig_data),
m_creator(creator),
m_witness_script(witscript) {}
static bool KeyCompare(const Key& a, const Key& b) {
return a < b;
}
//! Conversion from a raw public key.
template <typename I>
std::optional<Key> FromPKBytes(I first, I last) const
{
Key pubkey{first, last};
if (pubkey.IsValid()) return pubkey;
return {};
}
//! Conversion from a raw public key hash.
template<typename I>
std::optional<Key> FromPKHBytes(I first, I last) const {
assert(last - first == 20);
Key pubkey;
CKeyID key_id;
std::copy(first, last, key_id.begin());
if (GetPubKey(m_provider, m_sig_data, key_id, pubkey)) return pubkey;
m_sig_data.missing_pubkeys.push_back(key_id);
return {};
}
//! Conversion to raw public key.
std::vector<unsigned char> ToPKBytes(const CPubKey& key) const { return {key.begin(), key.end()}; }
//! Satisfy a signature check.
miniscript::Availability Sign(const CPubKey& key, std::vector<unsigned char>& sig) const {
if (CreateSig(m_creator, m_sig_data, m_provider, sig, key, m_witness_script, SigVersion::WITNESS_V0)) {
return miniscript::Availability::YES;
}
return miniscript::Availability::NO;
}
//! Time lock satisfactions.
bool CheckAfter(uint32_t value) const { return m_creator.Checker().CheckLockTime(CScriptNum(value)); }
bool CheckOlder(uint32_t value) const { return m_creator.Checker().CheckSequence(CScriptNum(value)); }
//! Hash preimage satisfactions.
miniscript::Availability SatSHA256(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const {
return MsLookupHelper(m_sig_data.sha256_preimages, hash, preimage);
}
miniscript::Availability SatRIPEMD160(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const {
return MsLookupHelper(m_sig_data.ripemd160_preimages, hash, preimage);
}
miniscript::Availability SatHASH256(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const {
return MsLookupHelper(m_sig_data.hash256_preimages, hash, preimage);
}
miniscript::Availability SatHASH160(const std::vector<unsigned char>& hash, std::vector<unsigned char>& preimage) const {
return MsLookupHelper(m_sig_data.hash160_preimages, hash, preimage);
}
};
bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& fromPubKey, SignatureData& sigdata)
{
if (sigdata.complete) return true;
@@ -415,9 +502,21 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato
{
CScript witnessscript(result[0].begin(), result[0].end());
sigdata.witness_script = witnessscript;
TxoutType subType;
TxoutType subType{TxoutType::NONSTANDARD};
solved = solved && SignStep(provider, creator, witnessscript, result, subType, SigVersion::WITNESS_V0, sigdata) && subType != TxoutType::SCRIPTHASH && subType != TxoutType::WITNESS_V0_SCRIPTHASH && subType != TxoutType::WITNESS_V0_KEYHASH;
// If we couldn't find a solution with the legacy satisfier, try satisfying the script using Miniscript.
// Note we need to check if the result stack is empty before, because it might be used even if the Script
// isn't fully solved. For instance the CHECKMULTISIG satisfaction in SignStep() pushes partial signatures
// and the extractor relies on this behaviour to combine witnesses.
if (!solved && result.empty()) {
Satisfier ms_satisfier{provider, sigdata, creator, witnessscript};
const auto ms = miniscript::FromScript(witnessscript, ms_satisfier);
solved = ms && ms->Satisfy(ms_satisfier, result) == miniscript::Availability::YES;
}
result.push_back(std::vector<unsigned char>(witnessscript.begin(), witnessscript.end()));
sigdata.scriptWitness.stack = result;
sigdata.witness = true;
result.clear();
@@ -563,26 +662,25 @@ void SignatureData::MergeSignatureData(SignatureData sigdata)
signatures.insert(std::make_move_iterator(sigdata.signatures.begin()), std::make_move_iterator(sigdata.signatures.end()));
}
bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType)
bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType, SignatureData& sig_data)
{
assert(nIn < txTo.vin.size());
MutableTransactionSignatureCreator creator(txTo, nIn, amount, nHashType);
SignatureData sigdata;
bool ret = ProduceSignature(provider, creator, fromPubKey, sigdata);
UpdateInput(txTo.vin.at(nIn), sigdata);
bool ret = ProduceSignature(provider, creator, fromPubKey, sig_data);
UpdateInput(txTo.vin.at(nIn), sig_data);
return ret;
}
bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType)
bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType, SignatureData& sig_data)
{
assert(nIn < txTo.vin.size());
const CTxIn& txin = txTo.vin[nIn];
assert(txin.prevout.n < txFrom.vout.size());
const CTxOut& txout = txFrom.vout[txin.prevout.n];
return SignSignature(provider, txout.scriptPubKey, txTo, nIn, txout.nValue, nHashType);
return SignSignature(provider, txout.scriptPubKey, txTo, nIn, txout.nValue, nHashType, sig_data);
}
namespace {
@@ -591,8 +689,10 @@ class DummySignatureChecker final : public BaseSignatureChecker
{
public:
DummySignatureChecker() = default;
bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { return true; }
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror) const override { return true; }
bool CheckECDSASignature(const std::vector<unsigned char>& sig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override { return sig.size() != 0; }
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* serror) const override { return sig.size() != 0; }
bool CheckLockTime(const CScriptNum& nLockTime) const override { return true; }
bool CheckSequence(const CScriptNum& nSequence) const override { return true; }
};
}

View File

@@ -83,6 +83,10 @@ struct SignatureData {
std::vector<CKeyID> missing_sigs; ///< KeyIDs of pubkeys for signatures which could not be found
uint160 missing_redeem_script; ///< ScriptID of the missing redeemScript (if any)
uint256 missing_witness_script; ///< SHA256 of the missing witnessScript (if any)
std::map<std::vector<uint8_t>, std::vector<uint8_t>> sha256_preimages; ///< Mapping from a SHA256 hash to its preimage provided to solve a Script
std::map<std::vector<uint8_t>, std::vector<uint8_t>> hash256_preimages; ///< Mapping from a HASH256 hash to its preimage provided to solve a Script
std::map<std::vector<uint8_t>, std::vector<uint8_t>> ripemd160_preimages; ///< Mapping from a RIPEMD160 hash to its preimage provided to solve a Script
std::map<std::vector<uint8_t>, std::vector<uint8_t>> hash160_preimages; ///< Mapping from a HASH160 hash to its preimage provided to solve a Script
SignatureData() {}
explicit SignatureData(const CScript& script) : scriptSig(script) {}
@@ -92,9 +96,24 @@ struct SignatureData {
/** Produce a script signature using a generic signature creator. */
bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreator& creator, const CScript& scriptPubKey, SignatureData& sigdata);
/** Produce a script signature for a transaction. */
bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo, unsigned int nIn, const CAmount& amount, int nHashType);
bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom, CMutableTransaction& txTo, unsigned int nIn, int nHashType);
/**
* Produce a satisfying script (scriptSig or witness).
*
* @param provider Utility containing the information necessary to solve a script.
* @param fromPubKey The script to produce a satisfaction for.
* @param txTo The spending transaction.
* @param nIn The index of the input in `txTo` refering the output being spent.
* @param amount The value of the output being spent.
* @param nHashType Signature hash type.
* @param sig_data Additional data provided to solve a script. Filled with the resulting satisfying
* script and whether the satisfaction is complete.
*
* @return True if the produced script is entirely satisfying `fromPubKey`.
**/
bool SignSignature(const SigningProvider &provider, const CScript& fromPubKey, CMutableTransaction& txTo,
unsigned int nIn, const CAmount& amount, int nHashType, SignatureData& sig_data);
bool SignSignature(const SigningProvider &provider, const CTransaction& txFrom, CMutableTransaction& txTo,
unsigned int nIn, int nHashType, SignatureData& sig_data);
/** Extract signature data from a transaction input, and insert it. */
SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nIn, const CTxOut& txout);