Skip to content

Commit 9fc68fa

Browse files
committed
script: match multisigs with up to MAX_PUBKEYS_PER_MULTISIG keys
We were previously ruling out 17-20 pubkeys multisig, while they are only invalid under P2SH context. This makes multisigs with up to 20 keys be detected as valid by the solver. This is however *not* a policy change as it would only apply to bare multisigs, which are already limited to 3 pubkeys. Note that this does not change the sigOpCount calculation (as it would break consensus). Therefore 1-16 keys multisigs are counted as 1-16 sigops and 17-20 keys multisigs are counted as 20 sigops. Signed-off-by: Antoine Poinsot <[email protected]>
1 parent ac219dc commit 9fc68fa

File tree

4 files changed

+72
-8
lines changed

4 files changed

+72
-8
lines changed

src/script/interpreter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ bool static CheckPubKeyEncoding(const valtype &vchPubKey, unsigned int flags, co
225225
return true;
226226
}
227227

228-
bool static CheckMinimalPush(const valtype& data, opcodetype opcode) {
228+
bool CheckMinimalPush(const valtype& data, opcodetype opcode) {
229229
// Excludes OP_1NEGATE, OP_1-16 since they are by definition minimal
230230
assert(0 <= opcode && opcode <= OP_PUSHDATA4);
231231
if (data.size() == 0) {

src/script/interpreter.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,8 @@ bool VerifyScript(const CScript& scriptSig, const CScript& scriptPubKey, const C
316316

317317
size_t CountWitnessSigOps(const CScript& scriptSig, const CScript& scriptPubKey, const CScriptWitness* witness, unsigned int flags);
318318

319+
bool CheckMinimalPush(const std::vector<unsigned char>& data, opcodetype opcode);
320+
319321
int FindAndDelete(CScript& script, const CScript& b);
320322

321323
#endif // BITCOIN_SCRIPT_INTERPRETER_H

src/script/standard.cpp

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,21 +88,53 @@ static constexpr bool IsSmallInteger(opcodetype opcode)
8888
return opcode >= OP_1 && opcode <= OP_16;
8989
}
9090

91-
static bool MatchMultisig(const CScript& script, unsigned int& required, std::vector<valtype>& pubkeys)
91+
static constexpr bool IsPushdataOp(opcodetype opcode)
92+
{
93+
return opcode > OP_FALSE && opcode <= OP_PUSHDATA4;
94+
}
95+
96+
static constexpr bool IsValidMultisigKeyCount(int n_keys)
97+
{
98+
return n_keys > 0 && n_keys <= MAX_PUBKEYS_PER_MULTISIG;
99+
}
100+
101+
static bool GetMultisigKeyCount(opcodetype opcode, valtype data, int& count)
102+
{
103+
if (IsSmallInteger(opcode)) {
104+
count = CScript::DecodeOP_N(opcode);
105+
return IsValidMultisigKeyCount(count);
106+
}
107+
108+
if (IsPushdataOp(opcode)) {
109+
if (!CheckMinimalPush(data, opcode)) return false;
110+
try {
111+
count = CScriptNum(data, /* fRequireMinimal = */ true).getint();
112+
return IsValidMultisigKeyCount(count);
113+
} catch (const scriptnum_error&) {
114+
return false;
115+
}
116+
}
117+
118+
return false;
119+
}
120+
121+
static bool MatchMultisig(const CScript& script, int& required_sigs, std::vector<valtype>& pubkeys)
92122
{
93123
opcodetype opcode;
94124
valtype data;
125+
int num_keys;
126+
95127
CScript::const_iterator it = script.begin();
96128
if (script.size() < 1 || script.back() != OP_CHECKMULTISIG) return false;
97129

98-
if (!script.GetOp(it, opcode, data) || !IsSmallInteger(opcode)) return false;
99-
required = CScript::DecodeOP_N(opcode);
130+
if (!script.GetOp(it, opcode, data) || !GetMultisigKeyCount(opcode, data, required_sigs)) return false;
100131
while (script.GetOp(it, opcode, data) && CPubKey::ValidSize(data)) {
101132
pubkeys.emplace_back(std::move(data));
102133
}
103-
if (!IsSmallInteger(opcode)) return false;
104-
unsigned int keys = CScript::DecodeOP_N(opcode);
105-
if (pubkeys.size() != keys || keys < required) return false;
134+
if (!GetMultisigKeyCount(opcode, data, num_keys)) return false;
135+
136+
if (pubkeys.size() != static_cast<unsigned long>(num_keys) || num_keys < required_sigs) return false;
137+
106138
return (it + 1 == script.end());
107139
}
108140

@@ -163,7 +195,7 @@ TxoutType Solver(const CScript& scriptPubKey, std::vector<std::vector<unsigned c
163195
return TxoutType::PUBKEYHASH;
164196
}
165197

166-
unsigned int required;
198+
int required;
167199
std::vector<std::vector<unsigned char>> keys;
168200
if (MatchMultisig(scriptPubKey, required, keys)) {
169201
vSolutionsRet.push_back({static_cast<unsigned char>(required)}); // safe as required is in range 1..16

src/test/descriptor_tests.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

5+
#include <pubkey.h>
56
#include <script/descriptor.h>
67
#include <script/sign.h>
78
#include <script/standard.h>
@@ -27,6 +28,14 @@ void CheckUnparsable(const std::string& prv, const std::string& pub, const std::
2728
BOOST_CHECK_EQUAL(error, expected_error);
2829
}
2930

31+
/** Check that the script is inferred as non-standard */
32+
void CheckInferRaw(const CScript& script)
33+
{
34+
FlatSigningProvider dummy_provider;
35+
std::unique_ptr<Descriptor> desc = InferDescriptor(script, dummy_provider);
36+
BOOST_CHECK(desc->ToString().rfind("raw(", 0) == 0);
37+
}
38+
3039
constexpr int DEFAULT = 0;
3140
constexpr int RANGE = 1; // Expected to be ranged descriptor
3241
constexpr int HARDENED = 2; // Derivation needs access to private keys
@@ -376,6 +385,27 @@ BOOST_AUTO_TEST_CASE(descriptor_test)
376385
CheckUnparsable("", "addr(asdf)", "Address is not valid"); // Invalid address
377386
CheckUnparsable("", "raw(asdf)", "Raw script is not hex"); // Invalid script
378387
CheckUnparsable("", "raw(Ü)#00000000", "Invalid characters in payload"); // Invalid chars
388+
389+
// A 2of4 but using a direct push rather than OP_2
390+
CScript nonminimalmultisig;
391+
CKey keys[4];
392+
nonminimalmultisig << std::vector<unsigned char>{2};
393+
for (int i = 0; i < 4; i++) {
394+
keys[i].MakeNewKey(true);
395+
nonminimalmultisig << ToByteVector(keys[i].GetPubKey());
396+
}
397+
nonminimalmultisig << 4 << OP_CHECKMULTISIG;
398+
CheckInferRaw(nonminimalmultisig);
399+
400+
// A 2of4 but using a direct push rather than OP_4
401+
nonminimalmultisig.clear();
402+
nonminimalmultisig << 2;
403+
for (int i = 0; i < 4; i++) {
404+
keys[i].MakeNewKey(true);
405+
nonminimalmultisig << ToByteVector(keys[i].GetPubKey());
406+
}
407+
nonminimalmultisig << std::vector<unsigned char>{4} << OP_CHECKMULTISIG;
408+
CheckInferRaw(nonminimalmultisig);
379409
}
380410

381411
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)