Skip to content

Commit db283a6

Browse files
committed
Merge bitcoin#27255: MiniTapscript: port Miniscript to Tapscript
ec0fc14 miniscript: remove P2WSH-specific part of GetStackSize doc comment (Antoine Poinsot) 128bc10 qa: bound testing for TapMiniscript (Antoine Poinsot) 117927b miniscript: have a custom Node destructor (Antoine Poinsot) b917c71 qa: Tapscript Miniscript signing functional tests (Antoine Poinsot) 5dc341d qa: list descriptors in Miniscript signing functional tests (Antoine Poinsot) 4f473ea script/sign: Miniscript support in Tapscript (Antoine Poinsot) febe2ab MOVEONLY: script/sign: move Satisfier declaration above Tapscript signing (Antoine Poinsot) bd4b11e qa: functional test Miniscript inside Taproot descriptors (Antoine Poinsot) 8571b89 descriptor: parse Miniscript expressions within Taproot descriptors (Antoine Poinsot) 8ff9489 descriptor: Tapscript-specific Miniscript key serialization / parsing (Antoine Poinsot) 5e76f3f fuzz: miniscript: higher sensitivity for max stack size limit under Tapscript (Antoine Poinsot) 6f529cb qa: test Miniscript max stack size tracking (Antoine Poinsot) 770ba5b miniscript: check maximum stack size during execution (Antoine Poinsot) 574523d fuzz: adapt Miniscript targets to Tapscript (Antoine Poinsot) 8462372 qa: Tapscript-Miniscript unit tests (Antoine Poinsot) fcb6f13 pubkey: introduce a GetEvenCorrespondingCPubKey helper (Antoine Poinsot) ce8845f miniscript: account for keys as being 32 bytes under Taproot context (Antoine Poinsot) f4f978d miniscript: adapt resources checks depending on context (Antoine Poinsot) 9cb4c68 serialize: make GetSizeOfCompactSize constexpr (Antoine Poinsot) 892436c miniscript: sanity asserts context in ComputeType (Antoine Poinsot) e5aaa3d miniscript: make 'd:' have the 'u' property under Tapscript context (Antoine Poinsot) 687a0b0 miniscript: introduce a multi_a fragment (Antoine Poinsot) 9164c2e miniscript: restrict multi() usage to P2WSH context (Antoine Poinsot) 91b4db8 miniscript: store the script context within the Node structure (Antoine Poinsot) c3738d0 miniscript: introduce a MsContext() helper to contexts (Antoine Poinsot) bba9340 miniscript: don't anticipate signature presence in CalcStackSize() (Antoine Poinsot) a3793f2 miniscript: add a missing dup key check bypass in Parse() (Antoine Poinsot) Pull request description: Miniscript was targeting P2WSH, and as such can currently only be used in `wsh()` descriptors. This pull request introduces support for Tapscript in Miniscript and makes Miniscript available inside `tr()` descriptors. It adds support for both watching *and* signing TapMiniscript descriptors. The main changes to Miniscript for Tapscript are the following: - A new `multi_a` fragment is introduced with the same semantics as `multi`. Like in other descriptors `multi` and `multi_a` can exclusively be used in respectively P2WSH and Tapscript. - The `d:` fragment has the `u` property under Tapscript, since the `MINIMALIF` rule is now consensus. See also bitcoin#24906. - Keys are now serialized as 32 bytes. (Note this affects the key hashes.) - The resource consumption checks and calculation changed. Some limits were lifted in Tapscript, and signatures are now 64 bytes long. The largest amount of complexity probably lies in the last item. Scripts under Taproot can now run into the maximum stack size while executing a fragment. For instance if you've got a stack size of `999` due to the initial witness plus some execution that happened before and try to execute a `hash256` it would `DUP` (increasing the stack size `1000`), `HASH160` and then push the hash on the stack making the script fail. To make sure this does not happen on any of the spending paths of a sane Miniscript, we introduce a tracking of the maximum stack size during execution of a fragment. See the commits messages for details. Those commits were separated from the resource consumption change, and the fuzz target was tweaked to sometimes pad the witness so the script runs on the brink of the stack size limit to make sure the stack size was not underestimated. Existing Miniscript unit, functional and fuzz tests are extended with Tapscript logic and test cases. Care was taken for seed stability in the fuzz targets where we cared more about them. The design of Miniscript for Tapscript is the result of discussions between various people over the past year(s). To the extent of my knowledge at least Pieter Wuille, Sanket Kanjalkar, Andrew Poelstra and Andrew Chow contributed thoughts and ideas. ACKs for top commit: sipa: ACK ec0fc14 achow101: ACK ec0fc14 Tree-SHA512: f3cf98a3ec8e565650ccf51b7ee7e4b4c2b3949a1168bee16ec03d2942b4d9f20dedc2820457f67a3216161022263573d08419c8346d807a693169ad3a436e07
2 parents d2b8c5e + ec0fc14 commit db283a6

File tree

14 files changed

+1422
-544
lines changed

14 files changed

+1422
-544
lines changed

src/psbt.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ void PSBTInput::FillSignatureData(SignatureData& sigdata) const
132132
}
133133
for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) {
134134
sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin);
135+
sigdata.tap_pubkeys.emplace(Hash160(pubkey), pubkey);
135136
}
136137
for (const auto& [hash, preimage] : ripemd160_preimages) {
137138
sigdata.ripemd160_preimages.emplace(std::vector<unsigned char>(hash.begin(), hash.end()), preimage);
@@ -246,6 +247,7 @@ void PSBTOutput::FillSignatureData(SignatureData& sigdata) const
246247
}
247248
for (const auto& [pubkey, leaf_origin] : m_tap_bip32_paths) {
248249
sigdata.taproot_misc_pubkeys.emplace(pubkey, leaf_origin);
250+
sigdata.tap_pubkeys.emplace(Hash160(pubkey), pubkey);
249251
}
250252
}
251253

src/pubkey.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,13 @@ std::vector<CKeyID> XOnlyPubKey::GetKeyIDs() const
204204
return out;
205205
}
206206

207+
CPubKey XOnlyPubKey::GetEvenCorrespondingCPubKey() const
208+
{
209+
unsigned char full_key[CPubKey::COMPRESSED_SIZE] = {0x02};
210+
std::copy(begin(), end(), full_key + 1);
211+
return CPubKey{full_key};
212+
}
213+
207214
bool XOnlyPubKey::IsFullyValid() const
208215
{
209216
secp256k1_xonly_pubkey pubkey;

src/pubkey.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,8 @@ class XOnlyPubKey
282282
*/
283283
std::vector<CKeyID> GetKeyIDs() const;
284284

285+
CPubKey GetEvenCorrespondingCPubKey() const;
286+
285287
const unsigned char& operator[](int pos) const { return *(m_keydata.begin() + pos); }
286288
const unsigned char* data() const { return m_keydata.begin(); }
287289
static constexpr size_t size() { return decltype(m_keydata)::size(); }

src/script/descriptor.cpp

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,16 +1114,33 @@ class TRDescriptor final : public DescriptorImpl
11141114
class ScriptMaker {
11151115
//! Keys contained in the Miniscript (the evaluation of DescriptorImpl::m_pubkey_args).
11161116
const std::vector<CPubKey>& m_keys;
1117+
//! The script context we're operating within (Tapscript or P2WSH).
1118+
const miniscript::MiniscriptContext m_script_ctx;
1119+
1120+
//! Get the ripemd160(sha256()) hash of this key.
1121+
//! Any key that is valid in a descriptor serializes as 32 bytes within a Tapscript context. So we
1122+
//! must not hash the sign-bit byte in this case.
1123+
uint160 GetHash160(uint32_t key) const {
1124+
if (miniscript::IsTapscript(m_script_ctx)) {
1125+
return Hash160(XOnlyPubKey{m_keys[key]});
1126+
}
1127+
return m_keys[key].GetID();
1128+
}
11171129

11181130
public:
1119-
ScriptMaker(const std::vector<CPubKey>& keys LIFETIMEBOUND) : m_keys(keys) {}
1131+
ScriptMaker(const std::vector<CPubKey>& keys LIFETIMEBOUND, const miniscript::MiniscriptContext script_ctx) : m_keys(keys), m_script_ctx{script_ctx} {}
11201132

11211133
std::vector<unsigned char> ToPKBytes(uint32_t key) const {
1122-
return {m_keys[key].begin(), m_keys[key].end()};
1134+
// In Tapscript keys always serialize as x-only, whether an x-only key was used in the descriptor or not.
1135+
if (!miniscript::IsTapscript(m_script_ctx)) {
1136+
return {m_keys[key].begin(), m_keys[key].end()};
1137+
}
1138+
const XOnlyPubKey xonly_pubkey{m_keys[key]};
1139+
return {xonly_pubkey.begin(), xonly_pubkey.end()};
11231140
}
11241141

11251142
std::vector<unsigned char> ToPKHBytes(uint32_t key) const {
1126-
auto id = m_keys[key].GetID();
1143+
auto id = GetHash160(key);
11271144
return {id.begin(), id.end()};
11281145
}
11291146
};
@@ -1164,8 +1181,15 @@ class MiniscriptDescriptor final : public DescriptorImpl
11641181
std::vector<CScript> MakeScripts(const std::vector<CPubKey>& keys, Span<const CScript> scripts,
11651182
FlatSigningProvider& provider) const override
11661183
{
1167-
for (const auto& key : keys) provider.pubkeys.emplace(key.GetID(), key);
1168-
return Vector(m_node->ToScript(ScriptMaker(keys)));
1184+
const auto script_ctx{m_node->GetMsCtx()};
1185+
for (const auto& key : keys) {
1186+
if (miniscript::IsTapscript(script_ctx)) {
1187+
provider.pubkeys.emplace(Hash160(XOnlyPubKey{key}), key);
1188+
} else {
1189+
provider.pubkeys.emplace(key.GetID(), key);
1190+
}
1191+
}
1192+
return Vector(m_node->ToScript(ScriptMaker(keys, script_ctx)));
11691193
}
11701194

11711195
public:
@@ -1401,9 +1425,7 @@ std::unique_ptr<PubkeyProvider> InferPubkey(const CPubKey& pubkey, ParseScriptCo
14011425

14021426
std::unique_ptr<PubkeyProvider> InferXOnlyPubkey(const XOnlyPubKey& xkey, ParseScriptContext ctx, const SigningProvider& provider)
14031427
{
1404-
unsigned char full_key[CPubKey::COMPRESSED_SIZE] = {0x02};
1405-
std::copy(xkey.begin(), xkey.end(), full_key + 1);
1406-
CPubKey pubkey(full_key);
1428+
CPubKey pubkey{xkey.GetEvenCorrespondingCPubKey()};
14071429
std::unique_ptr<PubkeyProvider> key_provider = std::make_unique<ConstPubkeyProvider>(0, pubkey, true);
14081430
KeyOriginInfo info;
14091431
if (provider.GetKeyOriginByXOnly(xkey, info)) {
@@ -1426,18 +1448,32 @@ struct KeyParser {
14261448
mutable std::vector<std::unique_ptr<PubkeyProvider>> m_keys;
14271449
//! Used to detect key parsing errors within a Miniscript.
14281450
mutable std::string m_key_parsing_error;
1451+
//! The script context we're operating within (Tapscript or P2WSH).
1452+
const miniscript::MiniscriptContext m_script_ctx;
1453+
//! The number of keys that were parsed before starting to parse this Miniscript descriptor.
1454+
uint32_t m_offset;
14291455

1430-
KeyParser(FlatSigningProvider* out LIFETIMEBOUND, const SigningProvider* in LIFETIMEBOUND) : m_out(out), m_in(in) {}
1456+
KeyParser(FlatSigningProvider* out LIFETIMEBOUND, const SigningProvider* in LIFETIMEBOUND,
1457+
miniscript::MiniscriptContext ctx, uint32_t offset = 0)
1458+
: m_out(out), m_in(in), m_script_ctx(ctx), m_offset(offset) {}
14311459

14321460
bool KeyCompare(const Key& a, const Key& b) const {
14331461
return *m_keys.at(a) < *m_keys.at(b);
14341462
}
14351463

1464+
ParseScriptContext ParseContext() const {
1465+
switch (m_script_ctx) {
1466+
case miniscript::MiniscriptContext::P2WSH: return ParseScriptContext::P2WSH;
1467+
case miniscript::MiniscriptContext::TAPSCRIPT: return ParseScriptContext::P2TR;
1468+
}
1469+
assert(false);
1470+
}
1471+
14361472
template<typename I> std::optional<Key> FromString(I begin, I end) const
14371473
{
14381474
assert(m_out);
14391475
Key key = m_keys.size();
1440-
auto pk = ParsePubkey(key, {&*begin, &*end}, ParseScriptContext::P2WSH, *m_out, m_key_parsing_error);
1476+
auto pk = ParsePubkey(m_offset + key, {&*begin, &*end}, ParseContext(), *m_out, m_key_parsing_error);
14411477
if (!pk) return {};
14421478
m_keys.push_back(std::move(pk));
14431479
return key;
@@ -1451,11 +1487,18 @@ struct KeyParser {
14511487
template<typename I> std::optional<Key> FromPKBytes(I begin, I end) const
14521488
{
14531489
assert(m_in);
1454-
CPubKey pubkey(begin, end);
1455-
if (pubkey.IsValidNonHybrid()) {
1456-
Key key = m_keys.size();
1457-
m_keys.push_back(InferPubkey(pubkey, ParseScriptContext::P2WSH, *m_in));
1490+
Key key = m_keys.size();
1491+
if (miniscript::IsTapscript(m_script_ctx) && end - begin == 32) {
1492+
XOnlyPubKey pubkey;
1493+
std::copy(begin, end, pubkey.begin());
1494+
m_keys.push_back(InferPubkey(pubkey.GetEvenCorrespondingCPubKey(), ParseContext(), *m_in));
14581495
return key;
1496+
} else if (!miniscript::IsTapscript(m_script_ctx)) {
1497+
CPubKey pubkey{begin, end};
1498+
if (pubkey.IsValidNonHybrid()) {
1499+
m_keys.push_back(InferPubkey(pubkey, ParseContext(), *m_in));
1500+
return key;
1501+
}
14591502
}
14601503
return {};
14611504
}
@@ -1470,11 +1513,15 @@ struct KeyParser {
14701513
CPubKey pubkey;
14711514
if (m_in->GetPubKey(keyid, pubkey)) {
14721515
Key key = m_keys.size();
1473-
m_keys.push_back(InferPubkey(pubkey, ParseScriptContext::P2WSH, *m_in));
1516+
m_keys.push_back(InferPubkey(pubkey, ParseContext(), *m_in));
14741517
return key;
14751518
}
14761519
return {};
14771520
}
1521+
1522+
miniscript::MiniscriptContext MsContext() const {
1523+
return m_script_ctx;
1524+
}
14781525
};
14791526

14801527
/** Parse a script in a particular context. */
@@ -1500,8 +1547,9 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
15001547
}
15011548
++key_exp_index;
15021549
return std::make_unique<PKHDescriptor>(std::move(pubkey));
1503-
} else if (Func("pkh", expr)) {
1504-
error = "Can only have pkh at top level, in sh(), or in wsh()";
1550+
} else if (ctx != ParseScriptContext::P2TR && Func("pkh", expr)) {
1551+
// Under Taproot, always the Miniscript parser deal with it.
1552+
error = "Can only have pkh at top level, in sh(), wsh(), or in tr()";
15051553
return nullptr;
15061554
}
15071555
if (ctx == ParseScriptContext::TOP && Func("combo", expr)) {
@@ -1714,11 +1762,12 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
17141762
}
17151763
// Process miniscript expressions.
17161764
{
1717-
KeyParser parser(&out, nullptr);
1765+
const auto script_ctx{ctx == ParseScriptContext::P2WSH ? miniscript::MiniscriptContext::P2WSH : miniscript::MiniscriptContext::TAPSCRIPT};
1766+
KeyParser parser(/*out = */&out, /* in = */nullptr, /* ctx = */script_ctx, key_exp_index);
17181767
auto node = miniscript::FromString(std::string(expr.begin(), expr.end()), parser);
17191768
if (node) {
1720-
if (ctx != ParseScriptContext::P2WSH) {
1721-
error = "Miniscript expressions can only be used in wsh";
1769+
if (ctx != ParseScriptContext::P2WSH && ctx != ParseScriptContext::P2TR) {
1770+
error = "Miniscript expressions can only be used in wsh or tr.";
17221771
return nullptr;
17231772
}
17241773
if (parser.m_key_parsing_error != "") {
@@ -1753,6 +1802,7 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
17531802
// A signature check is required for a miniscript to be sane. Therefore no sane miniscript
17541803
// may have an empty list of public keys.
17551804
CHECK_NONFATAL(!parser.m_keys.empty());
1805+
key_exp_index += parser.m_keys.size();
17561806
return std::make_unique<MiniscriptDescriptor>(std::move(parser.m_keys), std::move(node));
17571807
}
17581808
}
@@ -1886,8 +1936,9 @@ std::unique_ptr<DescriptorImpl> InferScript(const CScript& script, ParseScriptCo
18861936
}
18871937
}
18881938

1889-
if (ctx == ParseScriptContext::P2WSH) {
1890-
KeyParser parser(nullptr, &provider);
1939+
if (ctx == ParseScriptContext::P2WSH || ctx == ParseScriptContext::P2TR) {
1940+
const auto script_ctx{ctx == ParseScriptContext::P2WSH ? miniscript::MiniscriptContext::P2WSH : miniscript::MiniscriptContext::TAPSCRIPT};
1941+
KeyParser parser(/* out = */nullptr, /* in = */&provider, /* ctx = */script_ctx);
18911942
auto node = miniscript::FromScript(script, parser);
18921943
if (node && node->IsSane()) {
18931944
return std::make_unique<MiniscriptDescriptor>(std::move(parser.m_keys), std::move(node));

src/script/miniscript.cpp

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include <vector>
77
#include <script/script.h>
88
#include <script/miniscript.h>
9+
#include <serialize.h>
910

1011
#include <assert.h>
1112

@@ -32,7 +33,8 @@ Type SanitizeType(Type e) {
3233
return e;
3334
}
3435

35-
Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k, size_t data_size, size_t n_subs, size_t n_keys) {
36+
Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Type>& sub_types, uint32_t k,
37+
size_t data_size, size_t n_subs, size_t n_keys, MiniscriptContext ms_ctx) {
3638
// Sanity check on data
3739
if (fragment == Fragment::SHA256 || fragment == Fragment::HASH256) {
3840
assert(data_size == 32);
@@ -44,7 +46,7 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Ty
4446
// Sanity check on k
4547
if (fragment == Fragment::OLDER || fragment == Fragment::AFTER) {
4648
assert(k >= 1 && k < 0x80000000UL);
47-
} else if (fragment == Fragment::MULTI) {
49+
} else if (fragment == Fragment::MULTI || fragment == Fragment::MULTI_A) {
4850
assert(k >= 1 && k <= n_keys);
4951
} else if (fragment == Fragment::THRESH) {
5052
assert(k >= 1 && k <= n_subs);
@@ -68,7 +70,11 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Ty
6870
if (fragment == Fragment::PK_K || fragment == Fragment::PK_H) {
6971
assert(n_keys == 1);
7072
} else if (fragment == Fragment::MULTI) {
71-
assert(n_keys >= 1 && n_keys <= 20);
73+
assert(n_keys >= 1 && n_keys <= MAX_PUBKEYS_PER_MULTISIG);
74+
assert(!IsTapscript(ms_ctx));
75+
} else if (fragment == Fragment::MULTI_A) {
76+
assert(n_keys >= 1 && n_keys <= MAX_PUBKEYS_PER_MULTI_A);
77+
assert(IsTapscript(ms_ctx));
7278
} else {
7379
assert(n_keys == 0);
7480
}
@@ -113,7 +119,8 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Ty
113119
"e"_mst.If(x << "f"_mst) | // e=f_x
114120
(x & "ghijk"_mst) | // g=g_x, h=h_x, i=i_x, j=j_x, k=k_x
115121
(x & "ms"_mst) | // m=m_x, s=s_x
116-
// NOTE: 'd:' is not 'u' under P2WSH as MINIMALIF is only a policy rule there.
122+
// NOTE: 'd:' is 'u' under Tapscript but not P2WSH as MINIMALIF is only a policy rule there.
123+
"u"_mst.If(IsTapscript(ms_ctx)) |
117124
"ndx"_mst; // n, d, x
118125
case Fragment::WRAP_V: return
119126
"V"_mst.If(x << "B"_mst) | // V=B_x
@@ -210,7 +217,12 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Ty
210217
((x << "h"_mst) && (y << "g"_mst)) ||
211218
((x << "i"_mst) && (y << "j"_mst)) ||
212219
((x << "j"_mst) && (y << "i"_mst)))); // k=k_x*k_y*k_z* !(g_x*h_y + h_x*g_y + i_x*j_y + j_x*i_y)
213-
case Fragment::MULTI: return "Bnudemsk"_mst;
220+
case Fragment::MULTI: {
221+
return "Bnudemsk"_mst;
222+
}
223+
case Fragment::MULTI_A: {
224+
return "Budemsk"_mst;
225+
}
214226
case Fragment::THRESH: {
215227
bool all_e = true;
216228
bool all_m = true;
@@ -246,11 +258,12 @@ Type ComputeType(Fragment fragment, Type x, Type y, Type z, const std::vector<Ty
246258
assert(false);
247259
}
248260

249-
size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs, size_t n_keys) {
261+
size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_t k, size_t n_subs,
262+
size_t n_keys, MiniscriptContext ms_ctx) {
250263
switch (fragment) {
251264
case Fragment::JUST_1:
252265
case Fragment::JUST_0: return 1;
253-
case Fragment::PK_K: return 34;
266+
case Fragment::PK_K: return IsTapscript(ms_ctx) ? 33 : 34;
254267
case Fragment::PK_H: return 3 + 21;
255268
case Fragment::OLDER:
256269
case Fragment::AFTER: return 1 + BuildScript(k).size();
@@ -259,6 +272,7 @@ size_t ComputeScriptLen(Fragment fragment, Type sub0typ, size_t subsize, uint32_
259272
case Fragment::HASH160:
260273
case Fragment::RIPEMD160: return 4 + 2 + 21;
261274
case Fragment::MULTI: return 1 + BuildScript(n_keys).size() + BuildScript(k).size() + 34 * n_keys;
275+
case Fragment::MULTI_A: return (1 + 32 + 1) * n_keys + BuildScript(k).size() + 1;
262276
case Fragment::AND_V: return subsize;
263277
case Fragment::WRAP_V: return subsize + (sub0typ << "x"_mst);
264278
case Fragment::WRAP_S:
@@ -372,9 +386,13 @@ std::optional<std::vector<Opcode>> DecomposeScript(const CScript& script)
372386
// Decompose OP_EQUALVERIFY into OP_EQUAL OP_VERIFY
373387
out.emplace_back(OP_EQUAL, std::vector<unsigned char>());
374388
opcode = OP_VERIFY;
389+
} else if (opcode == OP_NUMEQUALVERIFY) {
390+
// Decompose OP_NUMEQUALVERIFY into OP_NUMEQUAL OP_VERIFY
391+
out.emplace_back(OP_NUMEQUAL, std::vector<unsigned char>());
392+
opcode = OP_VERIFY;
375393
} else if (IsPushdataOp(opcode)) {
376394
if (!CheckMinimalPush(push_data, opcode)) return {};
377-
} else if (it != itend && (opcode == OP_CHECKSIG || opcode == OP_CHECKMULTISIG || opcode == OP_EQUAL) && (*it == OP_VERIFY)) {
395+
} else if (it != itend && (opcode == OP_CHECKSIG || opcode == OP_CHECKMULTISIG || opcode == OP_EQUAL || opcode == OP_NUMEQUAL) && (*it == OP_VERIFY)) {
378396
// Rule out non minimal VERIFY sequences
379397
return {};
380398
}

0 commit comments

Comments
 (0)