aboutsummaryrefslogtreecommitdiff
path: root/src/script
diff options
context:
space:
mode:
Diffstat (limited to 'src/script')
-rw-r--r--src/script/bitcoinconsensus.cpp2
-rw-r--r--src/script/bitcoinconsensus.h12
-rw-r--r--src/script/descriptor.cpp354
-rw-r--r--src/script/descriptor.h7
-rw-r--r--src/script/interpreter.cpp33
-rw-r--r--src/script/interpreter.h44
-rw-r--r--src/script/sigcache.cpp16
-rw-r--r--src/script/sigcache.h2
-rw-r--r--src/script/sign.cpp28
-rw-r--r--src/script/standard.cpp86
-rw-r--r--src/script/standard.h2
11 files changed, 400 insertions, 186 deletions
diff --git a/src/script/bitcoinconsensus.cpp b/src/script/bitcoinconsensus.cpp
index 76609f01a7..a9aa6a0060 100644
--- a/src/script/bitcoinconsensus.cpp
+++ b/src/script/bitcoinconsensus.cpp
@@ -92,7 +92,7 @@ static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptP
set_error(err, bitcoinconsensus_ERR_OK);
PrecomputedTransactionData txdata(tx);
- return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), &tx.vin[nIn].scriptWitness, flags, TransactionSignatureChecker(&tx, nIn, amount, txdata), nullptr);
+ return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), &tx.vin[nIn].scriptWitness, flags, TransactionSignatureChecker(&tx, nIn, amount, txdata, MissingDataBehavior::FAIL), nullptr);
} catch (const std::exception&) {
return set_error(err, bitcoinconsensus_ERR_TX_DESERIALIZE); // Error deserializing
}
diff --git a/src/script/bitcoinconsensus.h b/src/script/bitcoinconsensus.h
index c5dceac848..b6939127e1 100644
--- a/src/script/bitcoinconsensus.h
+++ b/src/script/bitcoinconsensus.h
@@ -11,14 +11,12 @@
#if defined(BUILD_BITCOIN_INTERNAL) && defined(HAVE_CONFIG_H)
#include <config/bitcoin-config.h>
#if defined(_WIN32)
- #if defined(DLL_EXPORT)
- #if defined(HAVE_FUNC_ATTRIBUTE_DLLEXPORT)
- #define EXPORT_SYMBOL __declspec(dllexport)
- #else
- #define EXPORT_SYMBOL
- #endif
+ #if defined(HAVE_DLLEXPORT_ATTRIBUTE)
+ #define EXPORT_SYMBOL __declspec(dllexport)
+ #else
+ #define EXPORT_SYMBOL
#endif
- #elif defined(HAVE_FUNC_ATTRIBUTE_VISIBILITY)
+ #elif defined(HAVE_DEFAULT_VISIBILITY_ATTRIBUTE)
#define EXPORT_SYMBOL __attribute__ ((visibility ("default")))
#endif
#elif defined(MSC_VER) && !defined(STATIC_LIBBITCOINCONSENSUS)
diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp
index 9e4b8a9dd6..b54ba204f0 100644
--- a/src/script/descriptor.cpp
+++ b/src/script/descriptor.cpp
@@ -17,6 +17,7 @@
#include <util/vector.h>
#include <memory>
+#include <optional>
#include <string>
#include <vector>
@@ -179,6 +180,9 @@ public:
/** Get the descriptor string form including private data (if available in arg). */
virtual bool ToPrivateString(const SigningProvider& arg, std::string& out) const = 0;
+ /** Get the descriptor string form with the xpub at the last hardened derivation */
+ virtual bool ToNormalizedString(const SigningProvider& arg, std::string& out, bool priv) const = 0;
+
/** Derive a private key, if private data is available in arg. */
virtual bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const = 0;
};
@@ -212,6 +216,21 @@ public:
ret = "[" + OriginString() + "]" + std::move(sub);
return true;
}
+ bool ToNormalizedString(const SigningProvider& arg, std::string& ret, bool priv) const override
+ {
+ std::string sub;
+ if (!m_provider->ToNormalizedString(arg, sub, priv)) return false;
+ // If m_provider is a BIP32PubkeyProvider, we may get a string formatted like a OriginPubkeyProvider
+ // In that case, we need to strip out the leading square bracket and fingerprint from the substring,
+ // and append that to our own origin string.
+ if (sub[0] == '[') {
+ sub = sub.substr(9);
+ ret = "[" + OriginString() + std::move(sub);
+ } else {
+ ret = "[" + OriginString() + "]" + std::move(sub);
+ }
+ return true;
+ }
bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override
{
return m_provider->GetPrivKey(pos, arg, key);
@@ -243,6 +262,12 @@ public:
ret = EncodeSecret(key);
return true;
}
+ bool ToNormalizedString(const SigningProvider& arg, std::string& ret, bool priv) const override
+ {
+ if (priv) return ToPrivateString(arg, ret);
+ ret = ToString();
+ return true;
+ }
bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override
{
return arg.GetKey(m_pubkey.GetID(), key);
@@ -386,6 +411,56 @@ public:
}
return true;
}
+ bool ToNormalizedString(const SigningProvider& arg, std::string& out, bool priv) const override
+ {
+ // For hardened derivation type, just return the typical string, nothing to normalize
+ if (m_derive == DeriveType::HARDENED) {
+ if (priv) return ToPrivateString(arg, out);
+ out = ToString();
+ return true;
+ }
+ // Step backwards to find the last hardened step in the path
+ int i = (int)m_path.size() - 1;
+ for (; i >= 0; --i) {
+ if (m_path.at(i) >> 31) {
+ break;
+ }
+ }
+ // Either no derivation or all unhardened derivation
+ if (i == -1) {
+ if (priv) return ToPrivateString(arg, out);
+ out = ToString();
+ return true;
+ }
+ // Derive the xpub at the last hardened step
+ CExtKey xprv;
+ if (!GetExtKey(arg, xprv)) return false;
+ KeyOriginInfo origin;
+ int k = 0;
+ for (; k <= i; ++k) {
+ // Derive
+ xprv.Derive(xprv, m_path.at(k));
+ // Add to the path
+ origin.path.push_back(m_path.at(k));
+ // First derivation element, get the fingerprint for origin
+ if (k == 0) {
+ std::copy(xprv.vchFingerprint, xprv.vchFingerprint + 4, origin.fingerprint);
+ }
+ }
+ // Build the remaining path
+ KeyPath end_path;
+ for (; k < (int)m_path.size(); ++k) {
+ end_path.push_back(m_path.at(k));
+ }
+ // Build the string
+ std::string origin_str = HexStr(origin.fingerprint) + FormatHDKeypath(origin.path);
+ out = "[" + origin_str + "]" + (priv ? EncodeExtKey(xprv) : EncodeExtPubKey(xprv.Neuter())) + FormatHDKeypath(end_path);
+ if (IsRange()) {
+ out += "/*";
+ assert(m_derive == DeriveType::UNHARDENED);
+ }
+ return true;
+ }
bool GetPrivKey(int pos, const SigningProvider& arg, CKey& key) const override
{
CExtKey extkey;
@@ -406,34 +481,35 @@ class DescriptorImpl : public Descriptor
const std::string m_name;
protected:
- //! The sub-descriptor argument (nullptr for everything but SH and WSH).
+ //! The sub-descriptor arguments (empty for everything but SH and WSH).
//! In doc/descriptors.m this is referred to as SCRIPT expressions sh(SCRIPT)
//! and wsh(SCRIPT), and distinct from KEY expressions and ADDR expressions.
- const std::unique_ptr<DescriptorImpl> m_subdescriptor_arg;
+ //! Subdescriptors can only ever generate a single script.
+ const std::vector<std::unique_ptr<DescriptorImpl>> m_subdescriptor_args;
//! Return a serialization of anything except pubkey and script arguments, to be prepended to those.
virtual std::string ToStringExtra() const { return ""; }
/** A helper function to construct the scripts for this descriptor.
*
- * This function is invoked once for every CScript produced by evaluating
- * m_subdescriptor_arg, or just once in case m_subdescriptor_arg is nullptr.
-
+ * This function is invoked once by ExpandHelper.
+ *
* @param pubkeys The evaluations of the m_pubkey_args field.
- * @param script The evaluation of m_subdescriptor_arg (or nullptr when m_subdescriptor_arg is nullptr).
+ * @param scripts The evaluations of m_subdescriptor_args (one for each m_subdescriptor_args element).
* @param out A FlatSigningProvider to put scripts or public keys in that are necessary to the solver.
- * The script arguments to this function are automatically added, as is the origin info of the provided pubkeys.
+ * The origin info of the provided pubkeys is automatically added.
* @return A vector with scriptPubKeys for this descriptor.
*/
- virtual std::vector<CScript> MakeScripts(const std::vector<CPubKey>& pubkeys, const CScript* script, FlatSigningProvider& out) const = 0;
+ virtual std::vector<CScript> MakeScripts(const std::vector<CPubKey>& pubkeys, Span<const CScript> scripts, FlatSigningProvider& out) const = 0;
public:
- DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, std::unique_ptr<DescriptorImpl> script, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_name(name), m_subdescriptor_arg(std::move(script)) {}
+ DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_name(name), m_subdescriptor_args() {}
+ DescriptorImpl(std::vector<std::unique_ptr<PubkeyProvider>> pubkeys, std::unique_ptr<DescriptorImpl> script, const std::string& name) : m_pubkey_args(std::move(pubkeys)), m_name(name), m_subdescriptor_args(Vector(std::move(script))) {}
bool IsSolvable() const override
{
- if (m_subdescriptor_arg) {
- if (!m_subdescriptor_arg->IsSolvable()) return false;
+ for (const auto& arg : m_subdescriptor_args) {
+ if (!arg->IsSolvable()) return false;
}
return true;
}
@@ -443,13 +519,25 @@ public:
for (const auto& pubkey : m_pubkey_args) {
if (pubkey->IsRange()) return true;
}
- if (m_subdescriptor_arg) {
- if (m_subdescriptor_arg->IsRange()) return true;
+ for (const auto& arg : m_subdescriptor_args) {
+ if (arg->IsRange()) return true;
}
return false;
}
- bool ToStringHelper(const SigningProvider* arg, std::string& out, bool priv) const
+ virtual bool ToStringSubScriptHelper(const SigningProvider* arg, std::string& ret, bool priv, bool normalized) const
+ {
+ size_t pos = 0;
+ for (const auto& scriptarg : m_subdescriptor_args) {
+ if (pos++) ret += ",";
+ std::string tmp;
+ if (!scriptarg->ToStringHelper(arg, tmp, priv, normalized)) return false;
+ ret += std::move(tmp);
+ }
+ return true;
+ }
+
+ bool ToStringHelper(const SigningProvider* arg, std::string& out, bool priv, bool normalized) const
{
std::string extra = ToStringExtra();
size_t pos = extra.size() > 0 ? 1 : 0;
@@ -457,33 +545,39 @@ public:
for (const auto& pubkey : m_pubkey_args) {
if (pos++) ret += ",";
std::string tmp;
- if (priv) {
+ if (normalized) {
+ if (!pubkey->ToNormalizedString(*arg, tmp, priv)) return false;
+ } else if (priv) {
if (!pubkey->ToPrivateString(*arg, tmp)) return false;
} else {
tmp = pubkey->ToString();
}
ret += std::move(tmp);
}
- if (m_subdescriptor_arg) {
- if (pos++) ret += ",";
- std::string tmp;
- if (!m_subdescriptor_arg->ToStringHelper(arg, tmp, priv)) return false;
- ret += std::move(tmp);
- }
- out = std::move(ret) + ")";
+ std::string subscript;
+ if (!ToStringSubScriptHelper(arg, subscript, priv, normalized)) return false;
+ if (pos && subscript.size()) ret += ',';
+ out = std::move(ret) + std::move(subscript) + ")";
return true;
}
std::string ToString() const final
{
std::string ret;
- ToStringHelper(nullptr, ret, false);
+ ToStringHelper(nullptr, ret, false, false);
return AddChecksum(ret);
}
bool ToPrivateString(const SigningProvider& arg, std::string& out) const final
{
- bool ret = ToStringHelper(&arg, out, true);
+ bool ret = ToStringHelper(&arg, out, true, false);
+ out = AddChecksum(out);
+ return ret;
+ }
+
+ bool ToNormalizedString(const SigningProvider& arg, std::string& out, bool priv) const override final
+ {
+ bool ret = ToStringHelper(&arg, out, priv, true);
out = AddChecksum(out);
return ret;
}
@@ -493,17 +587,20 @@ public:
std::vector<std::pair<CPubKey, KeyOriginInfo>> entries;
entries.reserve(m_pubkey_args.size());
- // Construct temporary data in `entries` and `subscripts`, to avoid producing output in case of failure.
+ // Construct temporary data in `entries`, `subscripts`, and `subprovider` to avoid producing output in case of failure.
for (const auto& p : m_pubkey_args) {
entries.emplace_back();
if (!p->GetPubKey(pos, arg, entries.back().first, entries.back().second, read_cache, write_cache)) return false;
}
std::vector<CScript> subscripts;
- if (m_subdescriptor_arg) {
- FlatSigningProvider subprovider;
- if (!m_subdescriptor_arg->ExpandHelper(pos, arg, read_cache, subscripts, subprovider, write_cache)) return false;
- out = Merge(out, subprovider);
+ FlatSigningProvider subprovider;
+ for (const auto& subarg : m_subdescriptor_args) {
+ std::vector<CScript> outscripts;
+ if (!subarg->ExpandHelper(pos, arg, read_cache, outscripts, subprovider, write_cache)) return false;
+ assert(outscripts.size() == 1);
+ subscripts.emplace_back(std::move(outscripts[0]));
}
+ out = Merge(std::move(out), std::move(subprovider));
std::vector<CPubKey> pubkeys;
pubkeys.reserve(entries.size());
@@ -511,17 +608,8 @@ public:
pubkeys.push_back(entry.first);
out.origins.emplace(entry.first.GetID(), std::make_pair<CPubKey, KeyOriginInfo>(CPubKey(entry.first), std::move(entry.second)));
}
- if (m_subdescriptor_arg) {
- for (const auto& subscript : subscripts) {
- out.scripts.emplace(CScriptID(subscript), subscript);
- std::vector<CScript> addscripts = MakeScripts(pubkeys, &subscript, out);
- for (auto& addscript : addscripts) {
- output_scripts.push_back(std::move(addscript));
- }
- }
- } else {
- output_scripts = MakeScripts(pubkeys, nullptr, out);
- }
+
+ output_scripts = MakeScripts(pubkeys, MakeSpan(subscripts), out);
return true;
}
@@ -542,14 +630,12 @@ public:
if (!p->GetPrivKey(pos, provider, key)) continue;
out.keys.emplace(key.GetPubKey().GetID(), key);
}
- if (m_subdescriptor_arg) {
- FlatSigningProvider subprovider;
- m_subdescriptor_arg->ExpandPrivate(pos, provider, subprovider);
- out = Merge(out, subprovider);
+ for (const auto& arg : m_subdescriptor_args) {
+ arg->ExpandPrivate(pos, provider, out);
}
}
- Optional<OutputType> GetOutputType() const override { return nullopt; }
+ std::optional<OutputType> GetOutputType() const override { return std::nullopt; }
};
/** A parsed addr(A) descriptor. */
@@ -558,12 +644,12 @@ class AddressDescriptor final : public DescriptorImpl
const CTxDestination m_destination;
protected:
std::string ToStringExtra() const override { return EncodeDestination(m_destination); }
- std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, const CScript*, FlatSigningProvider&) const override { return Vector(GetScriptForDestination(m_destination)); }
+ std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, Span<const CScript>, FlatSigningProvider&) const override { return Vector(GetScriptForDestination(m_destination)); }
public:
- AddressDescriptor(CTxDestination destination) : DescriptorImpl({}, {}, "addr"), m_destination(std::move(destination)) {}
+ AddressDescriptor(CTxDestination destination) : DescriptorImpl({}, "addr"), m_destination(std::move(destination)) {}
bool IsSolvable() const final { return false; }
- Optional<OutputType> GetOutputType() const override
+ std::optional<OutputType> GetOutputType() const override
{
switch (m_destination.index()) {
case 1 /* PKHash */:
@@ -572,7 +658,7 @@ public:
case 4 /* WitnessV0KeyHash */:
case 5 /* WitnessUnknown */: return OutputType::BECH32;
case 0 /* CNoDestination */:
- default: return nullopt;
+ default: return std::nullopt;
}
}
bool IsSingleType() const final { return true; }
@@ -584,12 +670,12 @@ class RawDescriptor final : public DescriptorImpl
const CScript m_script;
protected:
std::string ToStringExtra() const override { return HexStr(m_script); }
- std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, const CScript*, FlatSigningProvider&) const override { return Vector(m_script); }
+ std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, Span<const CScript>, FlatSigningProvider&) const override { return Vector(m_script); }
public:
- RawDescriptor(CScript script) : DescriptorImpl({}, {}, "raw"), m_script(std::move(script)) {}
+ RawDescriptor(CScript script) : DescriptorImpl({}, "raw"), m_script(std::move(script)) {}
bool IsSolvable() const final { return false; }
- Optional<OutputType> GetOutputType() const override
+ std::optional<OutputType> GetOutputType() const override
{
CTxDestination dest;
ExtractDestination(m_script, dest);
@@ -600,7 +686,7 @@ public:
case 4 /* WitnessV0KeyHash */:
case 5 /* WitnessUnknown */: return OutputType::BECH32;
case 0 /* CNoDestination */:
- default: return nullopt;
+ default: return std::nullopt;
}
}
bool IsSingleType() const final { return true; }
@@ -610,9 +696,9 @@ public:
class PKDescriptor final : public DescriptorImpl
{
protected:
- std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, const CScript*, FlatSigningProvider&) const override { return Vector(GetScriptForRawPubKey(keys[0])); }
+ std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider&) const override { return Vector(GetScriptForRawPubKey(keys[0])); }
public:
- PKDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), {}, "pk") {}
+ PKDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "pk") {}
bool IsSingleType() const final { return true; }
};
@@ -620,15 +706,15 @@ public:
class PKHDescriptor final : public DescriptorImpl
{
protected:
- std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, const CScript*, FlatSigningProvider& out) const override
+ std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider& out) const override
{
CKeyID id = keys[0].GetID();
out.pubkeys.emplace(id, keys[0]);
return Vector(GetScriptForDestination(PKHash(id)));
}
public:
- PKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), {}, "pkh") {}
- Optional<OutputType> GetOutputType() const override { return OutputType::LEGACY; }
+ PKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "pkh") {}
+ std::optional<OutputType> GetOutputType() const override { return OutputType::LEGACY; }
bool IsSingleType() const final { return true; }
};
@@ -636,15 +722,15 @@ public:
class WPKHDescriptor final : public DescriptorImpl
{
protected:
- std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, const CScript*, FlatSigningProvider& out) const override
+ std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider& out) const override
{
CKeyID id = keys[0].GetID();
out.pubkeys.emplace(id, keys[0]);
return Vector(GetScriptForDestination(WitnessV0KeyHash(id)));
}
public:
- WPKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), {}, "wpkh") {}
- Optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
+ WPKHDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "wpkh") {}
+ std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
bool IsSingleType() const final { return true; }
};
@@ -652,7 +738,7 @@ public:
class ComboDescriptor final : public DescriptorImpl
{
protected:
- std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, const CScript*, FlatSigningProvider& out) const override
+ std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider& out) const override
{
std::vector<CScript> ret;
CKeyID id = keys[0].GetID();
@@ -668,7 +754,7 @@ protected:
return ret;
}
public:
- ComboDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), {}, "combo") {}
+ ComboDescriptor(std::unique_ptr<PubkeyProvider> prov) : DescriptorImpl(Vector(std::move(prov)), "combo") {}
bool IsSingleType() const final { return false; }
};
@@ -679,7 +765,7 @@ class MultisigDescriptor final : public DescriptorImpl
const bool m_sorted;
protected:
std::string ToStringExtra() const override { return strprintf("%i", m_threshold); }
- std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, const CScript*, FlatSigningProvider&) const override {
+ std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript>, FlatSigningProvider&) const override {
if (m_sorted) {
std::vector<CPubKey> sorted_keys(keys);
std::sort(sorted_keys.begin(), sorted_keys.end());
@@ -688,7 +774,7 @@ protected:
return Vector(GetScriptForMultisig(m_threshold, keys));
}
public:
- MultisigDescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers, bool sorted = false) : DescriptorImpl(std::move(providers), {}, sorted ? "sortedmulti" : "multi"), m_threshold(threshold), m_sorted(sorted) {}
+ MultisigDescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers, bool sorted = false) : DescriptorImpl(std::move(providers), sorted ? "sortedmulti" : "multi"), m_threshold(threshold), m_sorted(sorted) {}
bool IsSingleType() const final { return true; }
};
@@ -696,14 +782,19 @@ public:
class SHDescriptor final : public DescriptorImpl
{
protected:
- std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, const CScript* script, FlatSigningProvider&) const override { return Vector(GetScriptForDestination(ScriptHash(*script))); }
+ std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, Span<const CScript> scripts, FlatSigningProvider& out) const override
+ {
+ auto ret = Vector(GetScriptForDestination(ScriptHash(scripts[0])));
+ if (ret.size()) out.scripts.emplace(CScriptID(scripts[0]), scripts[0]);
+ return ret;
+ }
public:
SHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "sh") {}
- Optional<OutputType> GetOutputType() const override
+ std::optional<OutputType> GetOutputType() const override
{
- assert(m_subdescriptor_arg);
- if (m_subdescriptor_arg->GetOutputType() == OutputType::BECH32) return OutputType::P2SH_SEGWIT;
+ assert(m_subdescriptor_args.size() == 1);
+ if (m_subdescriptor_args[0]->GetOutputType() == OutputType::BECH32) return OutputType::P2SH_SEGWIT;
return OutputType::LEGACY;
}
bool IsSingleType() const final { return true; }
@@ -713,10 +804,15 @@ public:
class WSHDescriptor final : public DescriptorImpl
{
protected:
- std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, const CScript* script, FlatSigningProvider&) const override { return Vector(GetScriptForDestination(WitnessV0ScriptHash(*script))); }
+ std::vector<CScript> MakeScripts(const std::vector<CPubKey>&, Span<const CScript> scripts, FlatSigningProvider& out) const override
+ {
+ auto ret = Vector(GetScriptForDestination(WitnessV0ScriptHash(scripts[0])));
+ if (ret.size()) out.scripts.emplace(CScriptID(scripts[0]), scripts[0]);
+ return ret;
+ }
public:
WSHDescriptor(std::unique_ptr<DescriptorImpl> desc) : DescriptorImpl({}, std::move(desc), "wsh") {}
- Optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
+ std::optional<OutputType> GetOutputType() const override { return OutputType::BECH32; }
bool IsSingleType() const final { return true; }
};
@@ -725,9 +821,10 @@ public:
////////////////////////////////////////////////////////////////////////////
enum class ParseScriptContext {
- TOP,
- P2SH,
- P2WSH,
+ TOP, //!< Top-level context (script goes directly in scriptPubKey)
+ P2SH, //!< Inside sh() (script becomes P2SH redeemScript)
+ P2WPKH, //!< Inside wpkh() (no script, pubkey only)
+ P2WSH, //!< Inside wsh() (script becomes v0 witness script)
};
/** Parse a key path, being passed a split list of elements (the first element is ignored). */
@@ -754,10 +851,11 @@ enum class ParseScriptContext {
}
/** Parse a public key that excludes origin information. */
-std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out, std::string& error)
+std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error)
{
using namespace spanparsing;
+ bool permit_uncompressed = ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH;
auto split = Split(sp, '/');
std::string str(split[0].begin(), split[0].end());
if (str.size() == 0) {
@@ -770,7 +868,7 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S
CPubKey pubkey(data);
if (pubkey.IsFullyValid()) {
if (permit_uncompressed || pubkey.IsCompressed()) {
- return MakeUnique<ConstPubkeyProvider>(key_exp_index, pubkey);
+ return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey);
} else {
error = "Uncompressed keys are not allowed";
return nullptr;
@@ -784,7 +882,7 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S
if (permit_uncompressed || key.IsCompressed()) {
CPubKey pubkey = key.GetPubKey();
out.keys.emplace(pubkey.GetID(), key);
- return MakeUnique<ConstPubkeyProvider>(key_exp_index, pubkey);
+ return std::make_unique<ConstPubkeyProvider>(key_exp_index, pubkey);
} else {
error = "Uncompressed keys are not allowed";
return nullptr;
@@ -811,11 +909,11 @@ std::unique_ptr<PubkeyProvider> ParsePubkeyInner(uint32_t key_exp_index, const S
extpubkey = extkey.Neuter();
out.keys.emplace(extpubkey.pubkey.GetID(), extkey.key);
}
- return MakeUnique<BIP32PubkeyProvider>(key_exp_index, extpubkey, std::move(path), type);
+ return std::make_unique<BIP32PubkeyProvider>(key_exp_index, extpubkey, std::move(path), type);
}
/** Parse a public key including origin information (if enabled). */
-std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out, std::string& error)
+std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error)
{
using namespace spanparsing;
@@ -824,7 +922,7 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<c
error = "Multiple ']' characters found for a single pubkey";
return nullptr;
}
- if (origin_split.size() == 1) return ParsePubkeyInner(key_exp_index, origin_split[0], permit_uncompressed, out, error);
+ if (origin_split.size() == 1) return ParsePubkeyInner(key_exp_index, origin_split[0], ctx, out, error);
if (origin_split[0].empty() || origin_split[0][0] != '[') {
error = strprintf("Key origin start '[ character expected but not found, got '%c' instead",
origin_split[0].empty() ? /** empty, implies split char */ ']' : origin_split[0][0]);
@@ -846,34 +944,37 @@ std::unique_ptr<PubkeyProvider> ParsePubkey(uint32_t key_exp_index, const Span<c
assert(fpr_bytes.size() == 4);
std::copy(fpr_bytes.begin(), fpr_bytes.end(), info.fingerprint);
if (!ParseKeyPath(slash_split, info.path, error)) return nullptr;
- auto provider = ParsePubkeyInner(key_exp_index, origin_split[1], permit_uncompressed, out, error);
+ auto provider = ParsePubkeyInner(key_exp_index, origin_split[1], ctx, out, error);
if (!provider) return nullptr;
- return MakeUnique<OriginPubkeyProvider>(key_exp_index, std::move(info), std::move(provider));
+ return std::make_unique<OriginPubkeyProvider>(key_exp_index, std::move(info), std::move(provider));
}
/** Parse a script in a particular context. */
-std::unique_ptr<DescriptorImpl> ParseScript(uint32_t key_exp_index, Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error)
+std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out, std::string& error)
{
using namespace spanparsing;
auto expr = Expr(sp);
bool sorted_multi = false;
if (Func("pk", expr)) {
- auto pubkey = ParsePubkey(key_exp_index, expr, ctx != ParseScriptContext::P2WSH, out, error);
+ auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error);
if (!pubkey) return nullptr;
- return MakeUnique<PKDescriptor>(std::move(pubkey));
+ ++key_exp_index;
+ return std::make_unique<PKDescriptor>(std::move(pubkey));
}
if (Func("pkh", expr)) {
- auto pubkey = ParsePubkey(key_exp_index, expr, ctx != ParseScriptContext::P2WSH, out, error);
+ auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error);
if (!pubkey) return nullptr;
- return MakeUnique<PKHDescriptor>(std::move(pubkey));
+ ++key_exp_index;
+ return std::make_unique<PKHDescriptor>(std::move(pubkey));
}
if (ctx == ParseScriptContext::TOP && Func("combo", expr)) {
- auto pubkey = ParsePubkey(key_exp_index, expr, true, out, error);
+ auto pubkey = ParsePubkey(key_exp_index, expr, ctx, out, error);
if (!pubkey) return nullptr;
- return MakeUnique<ComboDescriptor>(std::move(pubkey));
- } else if (ctx != ParseScriptContext::TOP && Func("combo", expr)) {
- error = "Cannot have combo in non-top level";
+ ++key_exp_index;
+ return std::make_unique<ComboDescriptor>(std::move(pubkey));
+ } else if (Func("combo", expr)) {
+ error = "Can only have combo() at top level";
return nullptr;
}
if ((sorted_multi = Func("sortedmulti", expr)) || Func("multi", expr)) {
@@ -891,14 +992,14 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t key_exp_index, Span<const c
return nullptr;
}
auto arg = Expr(expr);
- auto pk = ParsePubkey(key_exp_index, arg, ctx != ParseScriptContext::P2WSH, out, error);
+ auto pk = ParsePubkey(key_exp_index, arg, ctx, out, error);
if (!pk) return nullptr;
script_size += pk->GetSize() + 1;
providers.emplace_back(std::move(pk));
key_exp_index++;
}
- if (providers.empty() || providers.size() > 16) {
- error = strprintf("Cannot have %u keys in multisig; must have between 1 and 16 keys, inclusive", providers.size());
+ if (providers.empty() || providers.size() > MAX_PUBKEYS_PER_MULTISIG) {
+ error = strprintf("Cannot have %u keys in multisig; must have between 1 and %d keys, inclusive", providers.size(), MAX_PUBKEYS_PER_MULTISIG);
return nullptr;
} else if (thres < 1) {
error = strprintf("Multisig threshold cannot be %d, must be at least 1", thres);
@@ -914,35 +1015,37 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t key_exp_index, Span<const c
}
}
if (ctx == ParseScriptContext::P2SH) {
+ // This limits the maximum number of compressed pubkeys to 15.
if (script_size + 3 > MAX_SCRIPT_ELEMENT_SIZE) {
error = strprintf("P2SH script is too large, %d bytes is larger than %d bytes", script_size + 3, MAX_SCRIPT_ELEMENT_SIZE);
return nullptr;
}
}
- return MakeUnique<MultisigDescriptor>(thres, std::move(providers), sorted_multi);
+ return std::make_unique<MultisigDescriptor>(thres, std::move(providers), sorted_multi);
}
- if (ctx != ParseScriptContext::P2WSH && Func("wpkh", expr)) {
- auto pubkey = ParsePubkey(key_exp_index, expr, false, out, error);
+ if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH) && Func("wpkh", expr)) {
+ auto pubkey = ParsePubkey(key_exp_index, expr, ParseScriptContext::P2WPKH, out, error);
if (!pubkey) return nullptr;
- return MakeUnique<WPKHDescriptor>(std::move(pubkey));
- } else if (ctx == ParseScriptContext::P2WSH && Func("wpkh", expr)) {
- error = "Cannot have wpkh within wsh";
+ key_exp_index++;
+ return std::make_unique<WPKHDescriptor>(std::move(pubkey));
+ } else if (Func("wpkh", expr)) {
+ error = "Can only have wpkh() at top level or inside sh()";
return nullptr;
}
if (ctx == ParseScriptContext::TOP && Func("sh", expr)) {
auto desc = ParseScript(key_exp_index, expr, ParseScriptContext::P2SH, out, error);
if (!desc || expr.size()) return nullptr;
- return MakeUnique<SHDescriptor>(std::move(desc));
- } else if (ctx != ParseScriptContext::TOP && Func("sh", expr)) {
- error = "Cannot have sh in non-top level";
+ return std::make_unique<SHDescriptor>(std::move(desc));
+ } else if (Func("sh", expr)) {
+ error = "Can only have sh() at top level";
return nullptr;
}
- if (ctx != ParseScriptContext::P2WSH && Func("wsh", expr)) {
+ if ((ctx == ParseScriptContext::TOP || ctx == ParseScriptContext::P2SH) && Func("wsh", expr)) {
auto desc = ParseScript(key_exp_index, expr, ParseScriptContext::P2WSH, out, error);
if (!desc || expr.size()) return nullptr;
- return MakeUnique<WSHDescriptor>(std::move(desc));
- } else if (ctx == ParseScriptContext::P2WSH && Func("wsh", expr)) {
- error = "Cannot have wsh within wsh";
+ return std::make_unique<WSHDescriptor>(std::move(desc));
+ } else if (Func("wsh", expr)) {
+ error = "Can only have wsh() at top level or inside sh()";
return nullptr;
}
if (ctx == ParseScriptContext::TOP && Func("addr", expr)) {
@@ -951,7 +1054,10 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t key_exp_index, Span<const c
error = "Address is not valid";
return nullptr;
}
- return MakeUnique<AddressDescriptor>(std::move(dest));
+ return std::make_unique<AddressDescriptor>(std::move(dest));
+ } else if (Func("addr", expr)) {
+ error = "Can only have addr() at top level";
+ return nullptr;
}
if (ctx == ParseScriptContext::TOP && Func("raw", expr)) {
std::string str(expr.begin(), expr.end());
@@ -960,7 +1066,10 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t key_exp_index, Span<const c
return nullptr;
}
auto bytes = ParseHex(str);
- return MakeUnique<RawDescriptor>(CScript(bytes.begin(), bytes.end()));
+ return std::make_unique<RawDescriptor>(CScript(bytes.begin(), bytes.end()));
+ } else if (Func("raw", expr)) {
+ error = "Can only have raw() at top level";
+ return nullptr;
}
if (ctx == ParseScriptContext::P2SH) {
error = "A function is needed within P2SH";
@@ -975,10 +1084,10 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t key_exp_index, Span<const c
std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptContext, const SigningProvider& provider)
{
- std::unique_ptr<PubkeyProvider> key_provider = MakeUnique<ConstPubkeyProvider>(0, pubkey);
+ std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey);
KeyOriginInfo info;
if (provider.GetKeyOrigin(pubkey.GetID(), info)) {
- return MakeUnique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider));
+ return std::make_unique<OriginPubkeyProvider>(0, std::move(info), std::move(key_provider));
}
return key_provider;
}
@@ -989,9 +1098,9 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
TxoutType txntype = Solver(script, data);
if (txntype == TxoutType::PUBKEY) {
- CPubKey pubkey(data[0].begin(), data[0].end());
+ CPubKey pubkey(data[0]);
if (pubkey.IsValid()) {
- return MakeUnique<PKDescriptor>(InferPubkey(pubkey, ctx, provider));
+ return std::make_unique<PKDescriptor>(InferPubkey(pubkey, ctx, provider));
}
}
if (txntype == TxoutType::PUBKEYHASH) {
@@ -999,7 +1108,7 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
CKeyID keyid(hash);
CPubKey pubkey;
if (provider.GetPubKey(keyid, pubkey)) {
- return MakeUnique<PKHDescriptor>(InferPubkey(pubkey, ctx, provider));
+ return std::make_unique<PKHDescriptor>(InferPubkey(pubkey, ctx, provider));
}
}
if (txntype == TxoutType::WITNESS_V0_KEYHASH && ctx != ParseScriptContext::P2WSH) {
@@ -1007,16 +1116,16 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
CKeyID keyid(hash);
CPubKey pubkey;
if (provider.GetPubKey(keyid, pubkey)) {
- return MakeUnique<WPKHDescriptor>(InferPubkey(pubkey, ctx, provider));
+ return std::make_unique<WPKHDescriptor>(InferPubkey(pubkey, ctx, provider));
}
}
if (txntype == TxoutType::MULTISIG) {
std::vector<std::unique_ptr<PubkeyProvider>> providers;
for (size_t i = 1; i + 1 < data.size(); ++i) {
- CPubKey pubkey(data[i].begin(), data[i].end());
+ CPubKey pubkey(data[i]);
providers.push_back(InferPubkey(pubkey, ctx, provider));
}
- return MakeUnique<MultisigDescriptor>((int)data[0][0], std::move(providers));
+ return std::make_unique<MultisigDescriptor>((int)data[0][0], std::move(providers));
}
if (txntype == TxoutType::SCRIPTHASH && ctx == ParseScriptContext::TOP) {
uint160 hash(data[0]);
@@ -1024,7 +1133,7 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
CScript subscript;
if (provider.GetCScript(scriptid, subscript)) {
auto sub = InferScript(subscript, ParseScriptContext::P2SH, provider);
- if (sub) return MakeUnique<SHDescriptor>(std::move(sub));
+ if (sub) return std::make_unique<SHDescriptor>(std::move(sub));
}
}
if (txntype == TxoutType::WITNESS_V0_SCRIPTHASH && ctx != ParseScriptContext::P2WSH) {
@@ -1033,18 +1142,18 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
CScript subscript;
if (provider.GetCScript(scriptid, subscript)) {
auto sub = InferScript(subscript, ParseScriptContext::P2WSH, provider);
- if (sub) return MakeUnique<WSHDescriptor>(std::move(sub));
+ if (sub) return std::make_unique<WSHDescriptor>(std::move(sub));
}
}
CTxDestination dest;
if (ExtractDestination(script, dest)) {
if (GetScriptForDestination(dest) == script) {
- return MakeUnique<AddressDescriptor>(std::move(dest));
+ return std::make_unique<AddressDescriptor>(std::move(dest));
}
}
- return MakeUnique<RawDescriptor>(script);
+ return std::make_unique<RawDescriptor>(script);
}
@@ -1090,7 +1199,8 @@ std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProv
{
Span<const char> sp{descriptor};
if (!CheckChecksum(sp, require_checksum, error)) return nullptr;
- auto ret = ParseScript(0, sp, ParseScriptContext::TOP, out, error);
+ uint32_t key_exp_index = 0;
+ auto ret = ParseScript(key_exp_index, sp, ParseScriptContext::TOP, out, error);
if (sp.size() == 0 && ret) return std::unique_ptr<Descriptor>(std::move(ret));
return nullptr;
}
diff --git a/src/script/descriptor.h b/src/script/descriptor.h
index 17b43e7c81..332ae2f230 100644
--- a/src/script/descriptor.h
+++ b/src/script/descriptor.h
@@ -5,12 +5,12 @@
#ifndef BITCOIN_SCRIPT_DESCRIPTOR_H
#define BITCOIN_SCRIPT_DESCRIPTOR_H
-#include <optional.h>
#include <outputtype.h>
#include <script/script.h>
#include <script/sign.h>
#include <script/signingprovider.h>
+#include <optional>
#include <vector>
using ExtPubKeyMap = std::unordered_map<uint32_t, CExtPubKey>;
@@ -93,6 +93,9 @@ struct Descriptor {
/** Convert the descriptor to a private string. This fails if the provided provider does not have the relevant private keys. */
virtual bool ToPrivateString(const SigningProvider& provider, std::string& out) const = 0;
+ /** Convert the descriptor to a normalized string. Normalized descriptors have the xpub at the last hardened step. This fails if the provided provider does not have the private keys to derive that xpub. */
+ virtual bool ToNormalizedString(const SigningProvider& provider, std::string& out, bool priv) const = 0;
+
/** Expand a descriptor at a specified position.
*
* @param[in] pos The position at which to expand the descriptor. If IsRange() is false, this is ignored.
@@ -121,7 +124,7 @@ struct Descriptor {
virtual void ExpandPrivate(int pos, const SigningProvider& provider, FlatSigningProvider& out) const = 0;
/** @return The OutputType of the scriptPubKey(s) produced by this descriptor. Or nullopt if indeterminate (multiple or none) */
- virtual Optional<OutputType> GetOutputType() const = 0;
+ virtual std::optional<OutputType> GetOutputType() const = 0;
};
/** Parse a `descriptor` string. Included private keys are put in `out`.
diff --git a/src/script/interpreter.cpp b/src/script/interpreter.cpp
index ecac3b9e7e..dc0f165be0 100644
--- a/src/script/interpreter.cpp
+++ b/src/script/interpreter.cpp
@@ -225,7 +225,7 @@ bool static CheckPubKeyEncoding(const valtype &vchPubKey, unsigned int flags, co
return true;
}
-bool static CheckMinimalPush(const valtype& data, opcodetype opcode) {
+bool CheckMinimalPush(const valtype& data, opcodetype opcode) {
// Excludes OP_1NEGATE, OP_1-16 since they are by definition minimal
assert(0 <= opcode && opcode <= OP_PUSHDATA4);
if (data.size() == 0) {
@@ -1488,8 +1488,20 @@ static const CHashWriter HASHER_TAPLEAF = TaggedHash("TapLeaf");
static const CHashWriter HASHER_TAPBRANCH = TaggedHash("TapBranch");
static const CHashWriter HASHER_TAPTWEAK = TaggedHash("TapTweak");
+static bool HandleMissingData(MissingDataBehavior mdb)
+{
+ switch (mdb) {
+ case MissingDataBehavior::ASSERT_FAIL:
+ assert(!"Missing data");
+ break;
+ case MissingDataBehavior::FAIL:
+ return false;
+ }
+ assert(!"Unknown MissingDataBehavior value");
+}
+
template<typename T>
-bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, const PrecomputedTransactionData& cache)
+bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata, const T& tx_to, uint32_t in_pos, uint8_t hash_type, SigVersion sigversion, const PrecomputedTransactionData& cache, MissingDataBehavior mdb)
{
uint8_t ext_flag, key_version;
switch (sigversion) {
@@ -1509,7 +1521,9 @@ bool SignatureHashSchnorr(uint256& hash_out, const ScriptExecutionData& execdata
assert(false);
}
assert(in_pos < tx_to.vin.size());
- assert(cache.m_bip341_taproot_ready && cache.m_spent_outputs_ready);
+ if (!(cache.m_bip341_taproot_ready && cache.m_spent_outputs_ready)) {
+ return HandleMissingData(mdb);
+ }
CHashWriter ss = HASHER_TAPSIGHASH;
@@ -1667,6 +1681,9 @@ bool GenericTransactionSignatureChecker<T>::CheckECDSASignature(const std::vecto
int nHashType = vchSig.back();
vchSig.pop_back();
+ // Witness sighashes need the amount.
+ if (sigversion == SigVersion::WITNESS_V0 && amount < 0) return HandleMissingData(m_mdb);
+
uint256 sighash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion, this->txdata);
if (!VerifyECDSASignature(vchSig, pubkey, sighash))
@@ -1696,7 +1713,7 @@ bool GenericTransactionSignatureChecker<T>::CheckSchnorrSignature(Span<const uns
}
uint256 sighash;
assert(this->txdata);
- if (!SignatureHashSchnorr(sighash, execdata, *txTo, nIn, hashtype, sigversion, *this->txdata)) {
+ if (!SignatureHashSchnorr(sighash, execdata, *txTo, nIn, hashtype, sigversion, *this->txdata, m_mdb)) {
return set_error(serror, SCRIPT_ERR_SCHNORR_SIG_HASHTYPE);
}
if (!VerifySchnorrSignature(sig, pubkey, sighash)) return set_error(serror, SCRIPT_ERR_SCHNORR_SIG);
@@ -1834,7 +1851,7 @@ static bool ExecuteWitnessScript(const Span<const valtype>& stack_span, const CS
static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, const std::vector<unsigned char>& program, const CScript& script, uint256& tapleaf_hash)
{
const int path_len = (control.size() - TAPROOT_CONTROL_BASE_SIZE) / TAPROOT_CONTROL_NODE_SIZE;
- //! The inner pubkey (x-only, so no Y coordinate parity).
+ //! The internal pubkey (x-only, so no Y coordinate parity).
const XOnlyPubKey p{uint256(std::vector<unsigned char>(control.begin() + 1, control.begin() + TAPROOT_CONTROL_BASE_SIZE))};
//! The output pubkey (taken from the scriptPubKey).
const XOnlyPubKey q{uint256(program)};
@@ -1852,9 +1869,9 @@ static bool VerifyTaprootCommitment(const std::vector<unsigned char>& control, c
}
k = ss_branch.GetSHA256();
}
- // Compute the tweak from the Merkle root and the inner pubkey.
+ // Compute the tweak from the Merkle root and the internal pubkey.
k = (CHashWriter(HASHER_TAPTWEAK) << MakeSpan(p) << k).GetSHA256();
- // Verify that the output pubkey matches the tweaked inner pubkey, after correcting for parity.
+ // Verify that the output pubkey matches the tweaked internal pubkey, after correcting for parity.
return q.CheckPayToContract(p, k, control[0] & 1);
}
@@ -1873,7 +1890,7 @@ static bool VerifyWitnessProgram(const CScriptWitness& witness, int witversion,
const valtype& script_bytes = SpanPopBack(stack);
exec_script = CScript(script_bytes.begin(), script_bytes.end());
uint256 hash_exec_script;
- CSHA256().Write(&exec_script[0], exec_script.size()).Finalize(hash_exec_script.begin());
+ CSHA256().Write(exec_script.data(), exec_script.size()).Finalize(hash_exec_script.begin());
if (memcmp(hash_exec_script.begin(), program.data(), 32)) {
return set_error(serror, SCRIPT_ERR_WITNESS_PROGRAM_MISMATCH);
}
diff --git a/src/script/interpreter.h b/src/script/interpreter.h
index b4c163c841..212de17c7b 100644
--- a/src/script/interpreter.h
+++ b/src/script/interpreter.h
@@ -247,11 +247,21 @@ public:
virtual ~BaseSignatureChecker() {}
};
+/** Enum to specify what *TransactionSignatureChecker's behavior should be
+ * when dealing with missing transaction data.
+ */
+enum class MissingDataBehavior
+{
+ ASSERT_FAIL, //!< Abort execution through assertion failure (for consensus code)
+ FAIL, //!< Just act as if the signature was invalid
+};
+
template <class T>
class GenericTransactionSignatureChecker : public BaseSignatureChecker
{
private:
const T* txTo;
+ const MissingDataBehavior m_mdb;
unsigned int nIn;
const CAmount amount;
const PrecomputedTransactionData* txdata;
@@ -261,8 +271,8 @@ protected:
virtual bool VerifySchnorrSignature(Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const;
public:
- GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(nullptr) {}
- GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn) : txTo(txToIn), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {}
+ GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, MissingDataBehavior mdb) : txTo(txToIn), m_mdb(mdb), nIn(nInIn), amount(amountIn), txdata(nullptr) {}
+ GenericTransactionSignatureChecker(const T* txToIn, unsigned int nInIn, const CAmount& amountIn, const PrecomputedTransactionData& txdataIn, MissingDataBehavior mdb) : txTo(txToIn), m_mdb(mdb), nIn(nInIn), amount(amountIn), txdata(&txdataIn) {}
bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override;
bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, const ScriptExecutionData& execdata, ScriptError* serror = nullptr) const override;
bool CheckLockTime(const CScriptNum& nLockTime) const override;
@@ -272,12 +282,42 @@ public:
using TransactionSignatureChecker = GenericTransactionSignatureChecker<CTransaction>;
using MutableTransactionSignatureChecker = GenericTransactionSignatureChecker<CMutableTransaction>;
+class DeferringSignatureChecker : public BaseSignatureChecker
+{
+protected:
+ BaseSignatureChecker& m_checker;
+
+public:
+ DeferringSignatureChecker(BaseSignatureChecker& checker) : m_checker(checker) {}
+
+ bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override
+ {
+ return m_checker.CheckECDSASignature(scriptSig, vchPubKey, scriptCode, sigversion);
+ }
+
+ bool CheckSchnorrSignature(Span<const unsigned char> sig, Span<const unsigned char> pubkey, SigVersion sigversion, const ScriptExecutionData& execdata, ScriptError* serror = nullptr) const override
+ {
+ return m_checker.CheckSchnorrSignature(sig, pubkey, sigversion, execdata, serror);
+ }
+
+ bool CheckLockTime(const CScriptNum& nLockTime) const override
+ {
+ return m_checker.CheckLockTime(nLockTime);
+ }
+ bool CheckSequence(const CScriptNum& nSequence) const override
+ {
+ return m_checker.CheckSequence(nSequence);
+ }
+};
+
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptExecutionData& execdata, ScriptError* error = nullptr);
bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript& script, unsigned int flags, const BaseSignatureChecker& checker, SigVersion sigversion, ScriptError* error = nullptr);
bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags, const BaseSignatureChecker& checker, ScriptError* serror = nullptr);
size_t CountWitnessSigOps(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags);
+bool CheckMinimalPush(const std::vector<unsigned char>& data, opcodetype opcode);
+
int FindAndDelete(CScript& script, const CScript& b);
#endif // BITCOIN_SCRIPT_INTERPRETER_H
diff --git a/src/script/sigcache.cpp b/src/script/sigcache.cpp
index 2bfe206597..65867c1c14 100644
--- a/src/script/sigcache.cpp
+++ b/src/script/sigcache.cpp
@@ -11,7 +11,11 @@
#include <util/system.h>
#include <cuckoocache.h>
-#include <boost/thread/shared_mutex.hpp>
+
+#include <algorithm>
+#include <mutex>
+#include <shared_mutex>
+#include <vector>
namespace {
/**
@@ -27,7 +31,7 @@ private:
CSHA256 m_salted_hasher_schnorr;
typedef CuckooCache::cache<uint256, SignatureCacheHasher> map_type;
map_type setValid;
- boost::shared_mutex cs_sigcache;
+ std::shared_mutex cs_sigcache;
public:
CSignatureCache()
@@ -49,26 +53,26 @@ public:
ComputeEntryECDSA(uint256& entry, const uint256 &hash, const std::vector<unsigned char>& vchSig, const CPubKey& pubkey) const
{
CSHA256 hasher = m_salted_hasher_ecdsa;
- hasher.Write(hash.begin(), 32).Write(&pubkey[0], pubkey.size()).Write(&vchSig[0], vchSig.size()).Finalize(entry.begin());
+ hasher.Write(hash.begin(), 32).Write(pubkey.data(), pubkey.size()).Write(vchSig.data(), vchSig.size()).Finalize(entry.begin());
}
void
ComputeEntrySchnorr(uint256& entry, const uint256 &hash, Span<const unsigned char> sig, const XOnlyPubKey& pubkey) const
{
CSHA256 hasher = m_salted_hasher_schnorr;
- hasher.Write(hash.begin(), 32).Write(&pubkey[0], pubkey.size()).Write(sig.data(), sig.size()).Finalize(entry.begin());
+ hasher.Write(hash.begin(), 32).Write(pubkey.data(), pubkey.size()).Write(sig.data(), sig.size()).Finalize(entry.begin());
}
bool
Get(const uint256& entry, const bool erase)
{
- boost::shared_lock<boost::shared_mutex> lock(cs_sigcache);
+ std::shared_lock<std::shared_mutex> lock(cs_sigcache);
return setValid.contains(entry, erase);
}
void Set(const uint256& entry)
{
- boost::unique_lock<boost::shared_mutex> lock(cs_sigcache);
+ std::unique_lock<std::shared_mutex> lock(cs_sigcache);
setValid.insert(entry);
}
uint32_t setup_bytes(size_t n)
diff --git a/src/script/sigcache.h b/src/script/sigcache.h
index bf0ba38c2d..7b6b91c963 100644
--- a/src/script/sigcache.h
+++ b/src/script/sigcache.h
@@ -27,7 +27,7 @@ private:
bool store;
public:
- CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, bool storeIn, PrecomputedTransactionData& txdataIn) : TransactionSignatureChecker(txToIn, nInIn, amountIn, txdataIn), store(storeIn) {}
+ CachingTransactionSignatureChecker(const CTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, bool storeIn, PrecomputedTransactionData& txdataIn) : TransactionSignatureChecker(txToIn, nInIn, amountIn, txdataIn, MissingDataBehavior::ASSERT_FAIL), store(storeIn) {}
bool VerifyECDSASignature(const std::vector<unsigned char>& vchSig, const CPubKey& vchPubKey, const uint256& sighash) const override;
bool VerifySchnorrSignature(Span<const unsigned char> sig, const XOnlyPubKey& pubkey, const uint256& sighash) const override;
diff --git a/src/script/sign.cpp b/src/script/sign.cpp
index 8afbe9ebed..da0092f9e3 100644
--- a/src/script/sign.cpp
+++ b/src/script/sign.cpp
@@ -14,7 +14,7 @@
typedef std::vector<unsigned char> valtype;
-MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn) {}
+MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn, MissingDataBehavior::FAIL) {}
bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provider, std::vector<unsigned char>& vchSig, const CKeyID& address, const CScript& scriptCode, SigVersion sigversion) const
{
@@ -26,6 +26,9 @@ bool MutableTransactionSignatureCreator::CreateSig(const SigningProvider& provid
if (sigversion == SigVersion::WITNESS_V0 && !key.IsCompressed())
return false;
+ // Signing for witness scripts needs the amount.
+ if (sigversion == SigVersion::WITNESS_V0 && amount < 0) return false;
+
uint256 hash = SignatureHash(scriptCode, *txTo, nIn, nHashType, amount, sigversion);
if (!key.Sign(hash, vchSig))
return false;
@@ -106,8 +109,7 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator
std::vector<valtype> vSolutions;
whichTypeRet = Solver(scriptPubKey, vSolutions);
- switch (whichTypeRet)
- {
+ switch (whichTypeRet) {
case TxoutType::NONSTANDARD:
case TxoutType::NULL_DATA:
case TxoutType::WITNESS_UNKNOWN:
@@ -165,7 +167,7 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator
return true;
case TxoutType::WITNESS_V0_SCRIPTHASH:
- CRIPEMD160().Write(&vSolutions[0][0], vSolutions[0].size()).Finalize(h160.begin());
+ CRIPEMD160().Write(vSolutions[0].data(), vSolutions[0].size()).Finalize(h160.begin());
if (GetCScript(provider, sigdata, CScriptID{h160}, scriptRet)) {
ret.push_back(std::vector<unsigned char>(scriptRet.begin(), scriptRet.end()));
return true;
@@ -173,10 +175,8 @@ static bool SignStep(const SigningProvider& provider, const BaseSignatureCreator
// Could not find witnessScript, add to missing
sigdata.missing_witness_script = uint256(vSolutions[0]);
return false;
-
- default:
- return false;
- }
+ } // no default case, so the compiler can warn about missing cases
+ assert(false);
}
static CScript PushAll(const std::vector<valtype>& values)
@@ -253,17 +253,17 @@ bool ProduceSignature(const SigningProvider& provider, const BaseSignatureCreato
}
namespace {
-class SignatureExtractorChecker final : public BaseSignatureChecker
+class SignatureExtractorChecker final : public DeferringSignatureChecker
{
private:
SignatureData& sigdata;
- BaseSignatureChecker& checker;
public:
- SignatureExtractorChecker(SignatureData& sigdata, BaseSignatureChecker& checker) : sigdata(sigdata), checker(checker) {}
+ SignatureExtractorChecker(SignatureData& sigdata, BaseSignatureChecker& checker) : DeferringSignatureChecker(checker), sigdata(sigdata) {}
+
bool CheckECDSASignature(const std::vector<unsigned char>& scriptSig, const std::vector<unsigned char>& vchPubKey, const CScript& scriptCode, SigVersion sigversion) const override
{
- if (checker.CheckECDSASignature(scriptSig, vchPubKey, scriptCode, sigversion)) {
+ if (m_checker.CheckECDSASignature(scriptSig, vchPubKey, scriptCode, sigversion)) {
CPubKey pubkey(vchPubKey);
sigdata.signatures.emplace(pubkey.GetID(), SigPair(pubkey, scriptSig));
return true;
@@ -295,7 +295,7 @@ SignatureData DataFromTransaction(const CMutableTransaction& tx, unsigned int nI
Stacks stack(data);
// Get signatures
- MutableTransactionSignatureChecker tx_checker(&tx, nIn, txout.nValue);
+ MutableTransactionSignatureChecker tx_checker(&tx, nIn, txout.nValue, MissingDataBehavior::FAIL);
SignatureExtractorChecker extractor_checker(data, tx_checker);
if (VerifyScript(data.scriptSig, txout.scriptPubKey, &data.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, extractor_checker)) {
data.complete = true;
@@ -502,7 +502,7 @@ bool SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore,
}
ScriptError serror = SCRIPT_ERR_OK;
- if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount), &serror)) {
+ if (!VerifyScript(txin.scriptSig, prevPubKey, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&txConst, i, amount, MissingDataBehavior::FAIL), &serror)) {
if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) {
// Unable to sign input and verification failed (possible attempt to partially sign).
input_errors[i] = "Unable to sign input, invalid stack size (possibly missing key)";
diff --git a/src/script/standard.cpp b/src/script/standard.cpp
index 7967c01858..364fac3c84 100644
--- a/src/script/standard.cpp
+++ b/src/script/standard.cpp
@@ -45,8 +45,7 @@ WitnessV0ScriptHash::WitnessV0ScriptHash(const CScript& in)
std::string GetTxnOutputType(TxoutType t)
{
- switch (t)
- {
+ switch (t) {
case TxoutType::NONSTANDARD: return "nonstandard";
case TxoutType::PUBKEY: return "pubkey";
case TxoutType::PUBKEYHASH: return "pubkeyhash";
@@ -89,21 +88,53 @@ static constexpr bool IsSmallInteger(opcodetype opcode)
return opcode >= OP_1 && opcode <= OP_16;
}
-static bool MatchMultisig(const CScript& script, unsigned int& required, std::vector<valtype>& pubkeys)
+static constexpr bool IsPushdataOp(opcodetype opcode)
+{
+ return opcode > OP_FALSE && opcode <= OP_PUSHDATA4;
+}
+
+static constexpr bool IsValidMultisigKeyCount(int n_keys)
+{
+ return n_keys > 0 && n_keys <= MAX_PUBKEYS_PER_MULTISIG;
+}
+
+static bool GetMultisigKeyCount(opcodetype opcode, valtype data, int& count)
+{
+ if (IsSmallInteger(opcode)) {
+ count = CScript::DecodeOP_N(opcode);
+ return IsValidMultisigKeyCount(count);
+ }
+
+ if (IsPushdataOp(opcode)) {
+ if (!CheckMinimalPush(data, opcode)) return false;
+ try {
+ count = CScriptNum(data, /* fRequireMinimal = */ true).getint();
+ return IsValidMultisigKeyCount(count);
+ } catch (const scriptnum_error&) {
+ return false;
+ }
+ }
+
+ return false;
+}
+
+static bool MatchMultisig(const CScript& script, int& required_sigs, std::vector<valtype>& pubkeys)
{
opcodetype opcode;
valtype data;
+ int num_keys;
+
CScript::const_iterator it = script.begin();
if (script.size() < 1 || script.back() != OP_CHECKMULTISIG) return false;
- if (!script.GetOp(it, opcode, data) || !IsSmallInteger(opcode)) return false;
- required = CScript::DecodeOP_N(opcode);
+ if (!script.GetOp(it, opcode, data) || !GetMultisigKeyCount(opcode, data, required_sigs)) return false;
while (script.GetOp(it, opcode, data) && CPubKey::ValidSize(data)) {
pubkeys.emplace_back(std::move(data));
}
- if (!IsSmallInteger(opcode)) return false;
- unsigned int keys = CScript::DecodeOP_N(opcode);
- if (pubkeys.size() != keys || keys < required) return false;
+ if (!GetMultisigKeyCount(opcode, data, num_keys)) return false;
+
+ if (pubkeys.size() != static_cast<unsigned long>(num_keys) || num_keys < required_sigs) return false;
+
return (it + 1 == script.end());
}
@@ -164,12 +195,12 @@ TxoutType Solver(const CScript& scriptPubKey, std::vector<std::vector<unsigned c
return TxoutType::PUBKEYHASH;
}
- unsigned int required;
+ int required;
std::vector<std::vector<unsigned char>> keys;
if (MatchMultisig(scriptPubKey, required, keys)) {
- vSolutionsRet.push_back({static_cast<unsigned char>(required)}); // safe as required is in range 1..16
+ vSolutionsRet.push_back({static_cast<unsigned char>(required)}); // safe as required is in range 1..20
vSolutionsRet.insert(vSolutionsRet.end(), keys.begin(), keys.end());
- vSolutionsRet.push_back({static_cast<unsigned char>(keys.size())}); // safe as size is in range 1..16
+ vSolutionsRet.push_back({static_cast<unsigned char>(keys.size())}); // safe as size is in range 1..20
return TxoutType::MULTISIG;
}
@@ -182,7 +213,8 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
std::vector<valtype> vSolutions;
TxoutType whichType = Solver(scriptPubKey, vSolutions);
- if (whichType == TxoutType::PUBKEY) {
+ switch (whichType) {
+ case TxoutType::PUBKEY: {
CPubKey pubKey(vSolutions[0]);
if (!pubKey.IsValid())
return false;
@@ -190,26 +222,28 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
addressRet = PKHash(pubKey);
return true;
}
- else if (whichType == TxoutType::PUBKEYHASH)
- {
+ case TxoutType::PUBKEYHASH: {
addressRet = PKHash(uint160(vSolutions[0]));
return true;
}
- else if (whichType == TxoutType::SCRIPTHASH)
- {
+ case TxoutType::SCRIPTHASH: {
addressRet = ScriptHash(uint160(vSolutions[0]));
return true;
- } else if (whichType == TxoutType::WITNESS_V0_KEYHASH) {
+ }
+ case TxoutType::WITNESS_V0_KEYHASH: {
WitnessV0KeyHash hash;
std::copy(vSolutions[0].begin(), vSolutions[0].end(), hash.begin());
addressRet = hash;
return true;
- } else if (whichType == TxoutType::WITNESS_V0_SCRIPTHASH) {
+ }
+ case TxoutType::WITNESS_V0_SCRIPTHASH: {
WitnessV0ScriptHash hash;
std::copy(vSolutions[0].begin(), vSolutions[0].end(), hash.begin());
addressRet = hash;
return true;
- } else if (whichType == TxoutType::WITNESS_UNKNOWN || whichType == TxoutType::WITNESS_V1_TAPROOT) {
+ }
+ case TxoutType::WITNESS_UNKNOWN:
+ case TxoutType::WITNESS_V1_TAPROOT: {
WitnessUnknown unk;
unk.version = vSolutions[0][0];
std::copy(vSolutions[1].begin(), vSolutions[1].end(), unk.program);
@@ -217,10 +251,15 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
addressRet = unk;
return true;
}
- // Multisig txns have more than one address...
- return false;
+ case TxoutType::MULTISIG:
+ case TxoutType::NULL_DATA:
+ case TxoutType::NONSTANDARD:
+ return false;
+ } // no default case, so the compiler can warn about missing cases
+ assert(false);
}
+// TODO: from v23 ("addresses" and "reqSigs" deprecated) "ExtractDestinations" should be removed
bool ExtractDestinations(const CScript& scriptPubKey, TxoutType& typeRet, std::vector<CTxDestination>& addressRet, int& nRequiredRet)
{
addressRet.clear();
@@ -311,10 +350,11 @@ CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys)
{
CScript script;
- script << CScript::EncodeOP_N(nRequired);
+ script << nRequired;
for (const CPubKey& key : keys)
script << ToByteVector(key);
- script << CScript::EncodeOP_N(keys.size()) << OP_CHECKMULTISIG;
+ script << keys.size() << OP_CHECKMULTISIG;
+
return script;
}
diff --git a/src/script/standard.h b/src/script/standard.h
index d5d87392ad..f2bf4a8af3 100644
--- a/src/script/standard.h
+++ b/src/script/standard.h
@@ -247,6 +247,8 @@ bool ExtractDestination(const CScript& scriptPubKey, CTxDestination& addressRet)
* Note: this function confuses destinations (a subset of CScripts that are
* encodable as an address) with key identifiers (of keys involved in a
* CScript), and its use should be phased out.
+ *
+ * TODO: from v23 ("addresses" and "reqSigs" deprecated) "ExtractDestinations" should be removed
*/
bool ExtractDestinations(const CScript& scriptPubKey, TxoutType& typeRet, std::vector<CTxDestination>& addressRet, int& nRequiredRet);