Skip to content

Commit 76a33be

Browse files
committed
Merge bitcoin#28307: rpc, wallet: fix incorrect segwit redeem script size limit
2451a21 test: addmultisigaddress, coverage for script size limits (furszy) 53302a0 bugfix: addmultisigaddress, add unsupported operation for redeem scripts over 520 bytes (furszy) 9be6065 test: coverage for 16-20 segwit multisig scripts (furszy) 9d9a91c rpc: bugfix, incorrect segwit redeem script size used in signrawtransactionwithkey (furszy) 0c9fedf fix incorrect multisig redeem script size limit for segwit (furszy) f7a173b test: rpc_createmultisig, decouple 'test_sortedmulti_descriptors_bip67' (furszy) 4f33dbd test: rpc_createmultisig, decouple 'test_mixing_uncompressed_and_compressed_keys' (furszy) 25a8170 test: rpc_createmultisig, remove unnecessary checkbalances() (furszy) b5a3289 test: refactor, multiple cleanups in rpc_createmultisig.py (furszy) 3635d43 test: rpc_createmultisig, remove manual wallet initialization (furszy) Pull request description: Fixing bitcoin#28250 (comment) and more. Currently, redeem scripts longer than 520 bytes, which are technically valid under segwit rules, have flaws in the following processes: 1) The multisig creation process fails to deduce the output descriptor, resulting in the generation of an incorrect descriptor. Additionally, the accompanying user warning is also inaccurate. 2) The `signrawtransactionwithkey` RPC command fail to sign them. 3) The legacy wallet `addmultisigaddress` wrongly discards them. The issue arises because most of these flows are utilizing the legacy spkm keystore, which imposes the [p2sh max redeem script size rule](https://github.com/bitcoin/bitcoin/blob/ded687334031f4790ef6a36b999fb30a79dcf7b3/src/script/signingprovider.cpp#L160) on all scripts. Which blocks segwit redeem scripts longer than the max element size in all the previously mentioned processes (`createmultisig`, `addmultisigaddress`, and `signrawtransactionwithkey`). This PR fixes the problem, enabling the creation of multisig output descriptors involving more than 15 keys and allowing the signing of these scripts, along with other post-segwit redeem scripts that surpass the 520-byte p2sh limit. Important note: Instead of adding support for these longer redeem scripts in the legacy wallet, an "unsupported operation" error has been added. The reasons behind this decision are: 1) The introduction of this feature brings about a compatibility-breaking change that requires downgrade protection; older wallets would be unable to interact with these "new" legacy wallets. 2) Considering the ongoing deprecation of the legacy spkm, this issue provides another compelling reason to transition towards descriptors. Testing notes: To easily verify each of the fixes, I decoupled the tests into standalone commits. So they can be cherry-picked on top of master. Where `rpc_createmultisig.py` (with and without the `--legacy-wallet` arg) will fail without the bugs fixes commits. Extra note: The initial commits improves the `rpc_createmultisig.py` test in many ways. I found this test very antiquated, screaming for an update and cleanup. ACKs for top commit: pinheadmz: ACK 2451a21 theStack: Code-review ACK 2451a21 achow101: ACK 2451a21 Tree-SHA512: 71794533cbd46b3a1079fb4e9d190d3ea3b615de0cbfa443466e14f05e4616ca90e12ce2bf07113515ea8113e64a560ad572bb9ea9d4835b6fb67b6ae596167f
2 parents b3a61bd + 2451a21 commit 76a33be

11 files changed

+167
-140
lines changed

src/outputtype.cpp

+4-4
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,11 @@ std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key)
8181
}
8282
}
8383

84-
CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore, const CScript& script, OutputType type)
84+
CTxDestination AddAndGetDestinationForScript(FlatSigningProvider& keystore, const CScript& script, OutputType type)
8585
{
8686
// Add script to keystore
87-
keystore.AddCScript(script);
88-
// Note that scripts over MAX_SCRIPT_ELEMENT_SIZE bytes are not yet supported.
87+
keystore.scripts.emplace(CScriptID(script), script);
88+
8989
switch (type) {
9090
case OutputType::LEGACY:
9191
return ScriptHash(script);
@@ -94,7 +94,7 @@ CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore,
9494
CTxDestination witdest = WitnessV0ScriptHash(script);
9595
CScript witprog = GetScriptForDestination(witdest);
9696
// Add the redeemscript, so that P2WSH and P2SH-P2WSH outputs are recognized as ours.
97-
keystore.AddCScript(witprog);
97+
keystore.scripts.emplace(CScriptID(witprog), witprog);
9898
if (type == OutputType::BECH32) {
9999
return witdest;
100100
} else {

src/outputtype.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ std::vector<CTxDestination> GetAllDestinationsForKey(const CPubKey& key);
4646
* This function will automatically add the script (and any other
4747
* necessary scripts) to the keystore.
4848
*/
49-
CTxDestination AddAndGetDestinationForScript(FillableSigningProvider& keystore, const CScript& script, OutputType);
49+
CTxDestination AddAndGetDestinationForScript(FlatSigningProvider& keystore, const CScript& script, OutputType);
5050

5151
/** Get the OutputType for a CTxDestination */
5252
std::optional<OutputType> OutputTypeFromDestination(const CTxDestination& dest);

src/rpc/output_script.cpp

+1-2
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,7 @@ static RPCHelpMan createmultisig()
139139
output_type = parsed.value();
140140
}
141141

142-
// Construct using pay-to-script-hash:
143-
FillableSigningProvider keystore;
142+
FlatSigningProvider keystore;
144143
CScript inner;
145144
const CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, keystore, inner);
146145

src/rpc/rawtransaction.cpp

+6-2
Original file line numberDiff line numberDiff line change
@@ -785,15 +785,19 @@ static RPCHelpMan signrawtransactionwithkey()
785785
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
786786
}
787787

788-
FillableSigningProvider keystore;
788+
FlatSigningProvider keystore;
789789
const UniValue& keys = request.params[1].get_array();
790790
for (unsigned int idx = 0; idx < keys.size(); ++idx) {
791791
UniValue k = keys[idx];
792792
CKey key = DecodeSecret(k.get_str());
793793
if (!key.IsValid()) {
794794
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
795795
}
796-
keystore.AddKey(key);
796+
797+
CPubKey pubkey = key.GetPubKey();
798+
CKeyID key_id = pubkey.GetID();
799+
keystore.pubkeys.emplace(key_id, pubkey);
800+
keystore.keys.emplace(key_id, key);
797801
}
798802

799803
// Fetch previous transactions (inputs):

src/rpc/rawtransaction_util.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ static void TxInErrorToJSON(const CTxIn& txin, UniValue& vErrorsRet, const std::
181181
vErrorsRet.push_back(std::move(entry));
182182
}
183183

184-
void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins)
184+
void ParsePrevouts(const UniValue& prevTxsUnival, FlatSigningProvider* keystore, std::map<COutPoint, Coin>& coins)
185185
{
186186
if (!prevTxsUnival.isNull()) {
187187
const UniValue& prevTxs = prevTxsUnival.get_array();
@@ -247,11 +247,11 @@ void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keyst
247247
// work from witnessScript when possible
248248
std::vector<unsigned char> scriptData(!ws.isNull() ? ParseHexV(ws, "witnessScript") : ParseHexV(rs, "redeemScript"));
249249
CScript script(scriptData.begin(), scriptData.end());
250-
keystore->AddCScript(script);
250+
keystore->scripts.emplace(CScriptID(script), script);
251251
// Automatically also add the P2WSH wrapped version of the script (to deal with P2SH-P2WSH).
252252
// This is done for redeemScript only for compatibility, it is encouraged to use the explicit witnessScript field instead.
253253
CScript witness_output_script{GetScriptForDestination(WitnessV0ScriptHash(script))};
254-
keystore->AddCScript(witness_output_script);
254+
keystore->scripts.emplace(CScriptID(witness_output_script), witness_output_script);
255255

256256
if (!ws.isNull() && !rs.isNull()) {
257257
// if both witnessScript and redeemScript are provided,

src/rpc/rawtransaction_util.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
#include <optional>
1313

1414
struct bilingual_str;
15-
class FillableSigningProvider;
15+
struct FlatSigningProvider;
1616
class UniValue;
1717
struct CMutableTransaction;
1818
class Coin;
@@ -38,7 +38,7 @@ void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const
3838
* @param keystore A pointer to the temporary keystore if there is one
3939
* @param coins Map of unspent outputs - coins in mempool and current chain UTXO set, may be extended by previous txns outputs after call
4040
*/
41-
void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keystore, std::map<COutPoint, Coin>& coins);
41+
void ParsePrevouts(const UniValue& prevTxsUnival, FlatSigningProvider* keystore, std::map<COutPoint, Coin>& coins);
4242

4343
/** Normalize univalue-represented inputs and add them to the transaction */
4444
void AddInputs(CMutableTransaction& rawTx, const UniValue& inputs_in, bool rbf);

src/rpc/util.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ CPubKey AddrToPubKey(const FillableSigningProvider& keystore, const std::string&
228228
}
229229

230230
// Creates a multisig address from a given list of public keys, number of signatures required, and the address type
231-
CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, FillableSigningProvider& keystore, CScript& script_out)
231+
CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, FlatSigningProvider& keystore, CScript& script_out)
232232
{
233233
// Gather public keys
234234
if (required < 1) {

src/rpc/util.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ std::string HelpExampleRpcNamed(const std::string& methodname, const RPCArgList&
117117

118118
CPubKey HexToPubKey(const std::string& hex_in);
119119
CPubKey AddrToPubKey(const FillableSigningProvider& keystore, const std::string& addr_in);
120-
CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, FillableSigningProvider& keystore, CScript& script_out);
120+
CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, FlatSigningProvider& keystore, CScript& script_out);
121121

122122
UniValue DescribeAddress(const CTxDestination& dest);
123123

src/wallet/rpc/addresses.cpp

+23-2
Original file line numberDiff line numberDiff line change
@@ -287,9 +287,30 @@ RPCHelpMan addmultisigaddress()
287287
output_type = parsed.value();
288288
}
289289

290-
// Construct using pay-to-script-hash:
290+
// Construct multisig scripts
291+
FlatSigningProvider provider;
291292
CScript inner;
292-
CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, spk_man, inner);
293+
CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, provider, inner);
294+
295+
// Import scripts into the wallet
296+
for (const auto& [id, script] : provider.scripts) {
297+
// Due to a bug in the legacy wallet, the p2sh maximum script size limit is also imposed on 'p2sh-segwit' and 'bech32' redeem scripts.
298+
// Even when redeem scripts over MAX_SCRIPT_ELEMENT_SIZE bytes are valid for segwit output types, we don't want to
299+
// enable it because:
300+
// 1) It introduces a compatibility-breaking change requiring downgrade protection; older wallets would be unable to interact with these "new" legacy wallets.
301+
// 2) Considering the ongoing deprecation of the legacy spkm, this issue adds another good reason to transition towards descriptors.
302+
if (script.size() > MAX_SCRIPT_ELEMENT_SIZE) throw JSONRPCError(RPC_WALLET_ERROR, "Unsupported multisig script size for legacy wallet. Upgrade to descriptors to overcome this limitation for p2sh-segwit or bech32 scripts");
303+
304+
if (!spk_man.AddCScript(script)) {
305+
if (CScript inner_script; spk_man.GetCScript(CScriptID(script), inner_script)) {
306+
CHECK_NONFATAL(inner_script == script); // Nothing to add, script already contained by the wallet
307+
continue;
308+
}
309+
throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error importing script into the wallet"));
310+
}
311+
}
312+
313+
// Store destination in the addressbook
293314
pwallet->SetAddressBook(dest, label, AddressPurpose::SEND);
294315

295316
// Make the descriptor

0 commit comments

Comments
 (0)