Skip to content

Commit 6013238

Browse files
committed
Merge bitcoin/bitcoin#20867: Support up to 20 keys for multisig under Segwit context
ebd4be4 doc: add release notes for 20867 (Antoine Poinsot) 5aa50ab rpc/util: multisig: only check redeemScript size is <= 520 for P2SH (Antoine Poinsot) 063df9e test/functional: standardness sanity checks for P2(W)SH multisig (Antoine Poinsot) ae0429d script: allow up to 20 keys in wsh() descriptors (Antoine Poinsot) 9fc68fa script: match multisigs with up to MAX_PUBKEYS_PER_MULTISIG keys (Antoine Poinsot) Pull request description: As described in bitcoin/bitcoin#20620 multisigs are currently limited to 16 keys in descriptors and RPC helpers, even for P2WSH and P2SH-P2WSH. This adds support for multisig with up to 20 keys (which are already standard) for Segwit v0 context for descriptors (`wsh()`, `sh(wsh())`) and RPC helpers. Fixes bitcoin/bitcoin#20620 ACKs for top commit: meshcollider: re-utACK ebd4be4 instagibbs: re-ACK bitcoin/bitcoin@ebd4be4 Tree-SHA512: 36141f10a8288010d17d5c4fe8d24878bcd4533b88a8aba3a44fa8f74ceb3182d70fee01427e0ab7f53ce7fab46c88c1cd3ac3b18ab8a10bd4a6b8b74ed79e46
2 parents 2448457 + ebd4be4 commit 6013238

File tree

8 files changed

+171
-22
lines changed

8 files changed

+171
-22
lines changed

doc/release-notes-20867.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Wallet
2+
------
3+
4+
- We now support up to 20 keys in `multi()` and `sortedmulti()` descriptors
5+
under `wsh()`. (#20867)
6+
7+
Updated RPCs
8+
------------
9+
10+
- `addmultisigaddress` and `createmultisig` now support up to 20 keys for
11+
Segwit addresses.

src/rpc/util.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -231,16 +231,12 @@ CTxDestination AddAndGetMultisigDestination(const int required, const std::vecto
231231
if ((int)pubkeys.size() < required) {
232232
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("not enough keys supplied (got %u keys, but need at least %d to redeem)", pubkeys.size(), required));
233233
}
234-
if (pubkeys.size() > 16) {
235-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Number of keys involved in the multisignature address creation > 16\nReduce the number");
234+
if (pubkeys.size() > MAX_PUBKEYS_PER_MULTISIG) {
235+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Number of keys involved in the multisignature address creation > %d\nReduce the number", MAX_PUBKEYS_PER_MULTISIG));
236236
}
237237

238238
script_out = GetScriptForMultisig(required, pubkeys);
239239

240-
if (script_out.size() > MAX_SCRIPT_ELEMENT_SIZE) {
241-
throw JSONRPCError(RPC_INVALID_PARAMETER, (strprintf("redeemScript exceeds size limit: %d > %d", script_out.size(), MAX_SCRIPT_ELEMENT_SIZE)));
242-
}
243-
244240
// Check if any keys are uncompressed. If so, the type is legacy
245241
for (const CPubKey& pk : pubkeys) {
246242
if (!pk.IsCompressed()) {
@@ -249,6 +245,10 @@ CTxDestination AddAndGetMultisigDestination(const int required, const std::vecto
249245
}
250246
}
251247

248+
if (type == OutputType::LEGACY && script_out.size() > MAX_SCRIPT_ELEMENT_SIZE) {
249+
throw JSONRPCError(RPC_INVALID_PARAMETER, (strprintf("redeemScript exceeds size limit: %d > %d", script_out.size(), MAX_SCRIPT_ELEMENT_SIZE)));
250+
}
251+
252252
// Make the address
253253
CTxDestination dest = AddAndGetDestinationForScript(keystore, script_out, type);
254254

src/script/descriptor.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -998,8 +998,8 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
998998
providers.emplace_back(std::move(pk));
999999
key_exp_index++;
10001000
}
1001-
if (providers.empty() || providers.size() > 16) {
1002-
error = strprintf("Cannot have %u keys in multisig; must have between 1 and 16 keys, inclusive", providers.size());
1001+
if (providers.empty() || providers.size() > MAX_PUBKEYS_PER_MULTISIG) {
1002+
error = strprintf("Cannot have %u keys in multisig; must have between 1 and %d keys, inclusive", providers.size(), MAX_PUBKEYS_PER_MULTISIG);
10031003
return nullptr;
10041004
} else if (thres < 1) {
10051005
error = strprintf("Multisig threshold cannot be %d, must be at least 1", thres);
@@ -1015,6 +1015,7 @@ std::unique_ptr<DescriptorImpl> ParseScript(uint32_t& key_exp_index, Span<const
10151015
}
10161016
}
10171017
if (ctx == ParseScriptContext::P2SH) {
1018+
// This limits the maximum number of compressed pubkeys to 15.
10181019
if (script_size + 3 > MAX_SCRIPT_ELEMENT_SIZE) {
10191020
error = strprintf("P2SH script is too large, %d bytes is larger than %d bytes", script_size + 3, MAX_SCRIPT_ELEMENT_SIZE);
10201021
return nullptr;

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: 44 additions & 11 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,12 +195,12 @@ 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)) {
169-
vSolutionsRet.push_back({static_cast<unsigned char>(required)}); // safe as required is in range 1..16
201+
vSolutionsRet.push_back({static_cast<unsigned char>(required)}); // safe as required is in range 1..20
170202
vSolutionsRet.insert(vSolutionsRet.end(), keys.begin(), keys.end());
171-
vSolutionsRet.push_back({static_cast<unsigned char>(keys.size())}); // safe as size is in range 1..16
203+
vSolutionsRet.push_back({static_cast<unsigned char>(keys.size())}); // safe as size is in range 1..20
172204
return TxoutType::MULTISIG;
173205
}
174206

@@ -318,10 +350,11 @@ CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys)
318350
{
319351
CScript script;
320352

321-
script << CScript::EncodeOP_N(nRequired);
353+
script << nRequired;
322354
for (const CPubKey& key : keys)
323355
script << ToByteVector(key);
324-
script << CScript::EncodeOP_N(keys.size()) << OP_CHECKMULTISIG;
356+
script << keys.size() << OP_CHECKMULTISIG;
357+
325358
return script;
326359
}
327360

src/test/descriptor_tests.cpp

Lines changed: 33 additions & 2 deletions
Large diffs are not rendered by default.

test/functional/wallet_importdescriptors.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,77 @@ def run_test(self):
459459
assert_equal(tx_signed_2['complete'], True)
460460
self.nodes[1].sendrawtransaction(tx_signed_2['hex'])
461461

462+
self.log.info("We can create and use a huge multisig under P2WSH")
463+
self.nodes[1].createwallet(wallet_name='wmulti_priv_big', blank=True, descriptors=True)
464+
wmulti_priv_big = self.nodes[1].get_wallet_rpc('wmulti_priv_big')
465+
xkey = "tprv8ZgxMBicQKsPeZSeYx7VXDDTs3XrTcmZQpRLbAeSQFCQGgKwR4gKpcxHaKdoTNHniv4EPDJNdzA3KxRrrBHcAgth8fU5X4oCndkkxk39iAt/*"
466+
xkey_int = "tprv8ZgxMBicQKsPeZSeYx7VXDDTs3XrTcmZQpRLbAeSQFCQGgKwR4gKpcxHaKdoTNHniv4EPDJNdzA3KxRrrBHcAgth8fU5X4oCndkkxk39iAt/1/*"
467+
res = wmulti_priv_big.importdescriptors([
468+
{
469+
"desc": descsum_create(f"wsh(sortedmulti(20,{(xkey + ',') * 19}{xkey}))"),
470+
"active": True,
471+
"range": 1000,
472+
"next_index": 0,
473+
"timestamp": "now"
474+
},
475+
{
476+
"desc": descsum_create(f"wsh(sortedmulti(20,{(xkey_int + ',') * 19}{xkey_int}))"),
477+
"active": True,
478+
"internal": True,
479+
"range": 1000,
480+
"next_index": 0,
481+
"timestamp": "now"
482+
}])
483+
assert_equal(res[0]['success'], True)
484+
assert_equal(res[1]['success'], True)
485+
486+
addr = wmulti_priv_big.getnewaddress()
487+
w0.sendtoaddress(addr, 10)
488+
self.nodes[0].generate(1)
489+
self.sync_all()
490+
# It is standard and would relay.
491+
txid = wmulti_priv_big.sendtoaddress(w0.getnewaddress(), 9.999)
492+
decoded = wmulti_priv_big.decoderawtransaction(wmulti_priv_big.gettransaction(txid)['hex'])
493+
# 20 sigs + dummy + witness script
494+
assert_equal(len(decoded['vin'][0]['txinwitness']), 22)
495+
496+
497+
self.log.info("Under P2SH, multisig are standard with up to 15 "
498+
"compressed keys")
499+
self.nodes[1].createwallet(wallet_name='multi_priv_big_legacy',
500+
blank=True, descriptors=True)
501+
multi_priv_big = self.nodes[1].get_wallet_rpc('multi_priv_big_legacy')
502+
res = multi_priv_big.importdescriptors([
503+
{
504+
"desc": descsum_create(f"sh(multi(15,{(xkey + ',') * 14}{xkey}))"),
505+
"active": True,
506+
"range": 1000,
507+
"next_index": 0,
508+
"timestamp": "now"
509+
},
510+
{
511+
"desc": descsum_create(f"sh(multi(15,{(xkey_int + ',') * 14}{xkey_int}))"),
512+
"active": True,
513+
"internal": True,
514+
"range": 1000,
515+
"next_index": 0,
516+
"timestamp": "now"
517+
}])
518+
assert_equal(res[0]['success'], True)
519+
assert_equal(res[1]['success'], True)
520+
521+
addr = multi_priv_big.getnewaddress("", "legacy")
522+
w0.sendtoaddress(addr, 10)
523+
self.nodes[0].generate(6)
524+
self.sync_all()
525+
# It is standard and would relay.
526+
txid = multi_priv_big.sendtoaddress(w0.getnewaddress(), 10, "", "",
527+
True)
528+
decoded = multi_priv_big.decoderawtransaction(
529+
multi_priv_big.gettransaction(txid)['hex']
530+
)
531+
532+
462533
self.log.info("Combo descriptors cannot be active")
463534
self.test_importdesc({"desc": descsum_create("combo(tpubDCJtdt5dgJpdhW4MtaVYDhG4T4tF6jcLR1PxL43q9pq1mxvXgMS9Mzw1HnXG15vxUGQJMMSqCQHMTy3F1eW5VkgVroWzchsPD5BUojrcWs8/*)"),
464535
"active": True,

0 commit comments

Comments
 (0)