diff --git a/doc/release-notes-20867.md b/doc/release-notes-20867.md new file mode 100644 index 0000000000..60eed6838f --- /dev/null +++ b/doc/release-notes-20867.md @@ -0,0 +1,11 @@ +Wallet +------ + +- We now support up to 20 keys in `multi()` and `sortedmulti()` descriptors + under `wsh()`. (#20867) + +Updated RPCs +------------ + +- `addmultisigaddress` and `createmultisig` now support up to 20 keys for + Segwit addresses. diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 8675bf2408..95b6947a26 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -236,16 +236,12 @@ CTxDestination AddAndGetMultisigDestination(const int required, const std::vecto if ((int)pubkeys.size() < required) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("not enough keys supplied (got %u keys, but need at least %d to redeem)", pubkeys.size(), required)); } - if (pubkeys.size() > 16) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Number of keys involved in the multisignature address creation > 16\nReduce the number"); + if (pubkeys.size() > MAX_PUBKEYS_PER_MULTISIG) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Number of keys involved in the multisignature address creation > %d\nReduce the number", MAX_PUBKEYS_PER_MULTISIG)); } script_out = GetScriptForMultisig(required, pubkeys); - if (script_out.size() > MAX_SCRIPT_ELEMENT_SIZE) { - throw JSONRPCError(RPC_INVALID_PARAMETER, (strprintf("redeemScript exceeds size limit: %d > %d", script_out.size(), MAX_SCRIPT_ELEMENT_SIZE))); - } - // Check if any keys are uncompressed. If so, the type is legacy for (const CPubKey& pk : pubkeys) { if (!pk.IsCompressed()) { @@ -254,6 +250,10 @@ CTxDestination AddAndGetMultisigDestination(const int required, const std::vecto } } + if (type == OutputType::LEGACY && script_out.size() > MAX_SCRIPT_ELEMENT_SIZE) { + throw JSONRPCError(RPC_INVALID_PARAMETER, (strprintf("redeemScript exceeds size limit: %d > %d", script_out.size(), MAX_SCRIPT_ELEMENT_SIZE))); + } + // Make the address CTxDestination dest = AddAndGetDestinationForScript(keystore, script_out, type); diff --git a/src/script/descriptor.cpp b/src/script/descriptor.cpp index f1433553bc..e433ed6764 100644 --- a/src/script/descriptor.cpp +++ b/src/script/descriptor.cpp @@ -998,8 +998,8 @@ std::unique_ptr ParseScript(uint32_t& key_exp_index, Span 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); @@ -1015,6 +1015,7 @@ std::unique_ptr ParseScript(uint32_t& key_exp_index, Span 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; diff --git a/src/script/standard.cpp b/src/script/standard.cpp index 8752f5f682..8f319ecaff 100644 --- a/src/script/standard.cpp +++ b/src/script/standard.cpp @@ -102,21 +102,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& 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& 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(num_keys) || num_keys < required_sigs) return false; + return (it + 1 == script.end()); } @@ -186,12 +218,12 @@ TxoutType Solver(const CScript& scriptPubKey, std::vector> keys; if (MatchMultisig(scriptPubKey, required, keys)) { - vSolutionsRet.push_back({static_cast(required)}); // safe as required is in range 1..16 + vSolutionsRet.push_back({static_cast(required)}); // safe as required is in range 1..20 vSolutionsRet.insert(vSolutionsRet.end(), keys.begin(), keys.end()); - vSolutionsRet.push_back({static_cast(keys.size())}); // safe as size is in range 1..16 + vSolutionsRet.push_back({static_cast(keys.size())}); // safe as size is in range 1..20 return TxoutType::MULTISIG; } @@ -352,10 +384,11 @@ CScript GetScriptForMultisig(int nRequired, const std::vector& 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/test/descriptor_tests.cpp b/src/test/descriptor_tests.cpp index ea41a03728..36e2dac3ff 100644 --- a/src/test/descriptor_tests.cpp +++ b/src/test/descriptor_tests.cpp @@ -2,6 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include #include