Skip to content

Commit 303ec10

Browse files
committed
Merge #16026: Ensure that uncompressed public keys in a multisig always returns a legacy address
a495034 Make and get the multisig redeemscript and destination in one function instead of two (Andrew Chow) Pull request description: `CreateMultisigRedeemscript()` is changed to `AddAndGetMultisigDestination()` so that the process of constructing the redeemScript and then getting the `CTxDestination` are done in the same function. This allows that function to see what the keys in the multisig are so that the correct address type is returned from `AddAndGetDestinationForScript()`. This only effects the `createmultisig` and `addmultisigaddress` RPCs and does not change signing logic as #16022 does. Alternative to #16022 and #16012 Fixes #16011 ACKs for commit a49503: Tree-SHA512: 5b0154a714deea3b2cc3a54beb420c95eeeacf4ca30c40ca80940d9d640f8b03611b0fc14c2f0710bfd8a79e8d27ad7d9ae380b4b83d52b40ab201624f2a63f0
2 parents 23815ee + a495034 commit 303ec10

File tree

5 files changed

+53
-12
lines changed

5 files changed

+53
-12
lines changed

src/rpc/misc.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,9 @@ static UniValue createmultisig(const JSONRPCRequest& request)
122122
}
123123

124124
// Construct using pay-to-script-hash:
125-
const CScript inner = CreateMultisigRedeemscript(required, pubkeys);
126125
CBasicKeyStore keystore;
127-
const CTxDestination dest = AddAndGetDestinationForScript(keystore, inner, output_type);
126+
CScript inner;
127+
const CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, keystore, inner);
128128

129129
UniValue result(UniValue::VOBJ);
130130
result.pushKV("address", EncodeDestination(dest));

src/rpc/util.cpp

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include <key_io.h>
66
#include <keystore.h>
7+
#include <outputtype.h>
78
#include <rpc/util.h>
89
#include <tinyformat.h>
910
#include <util/strencodings.h>
@@ -150,8 +151,8 @@ CPubKey AddrToPubKey(CKeyStore* const keystore, const std::string& addr_in)
150151
return vchPubKey;
151152
}
152153

153-
// Creates a multisig redeemscript from a given list of public keys and number required.
154-
CScript CreateMultisigRedeemscript(const int required, const std::vector<CPubKey>& pubkeys)
154+
// Creates a multisig address from a given list of public keys, number of signatures required, and the address type
155+
CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, CKeyStore& keystore, CScript& script_out)
155156
{
156157
// Gather public keys
157158
if (required < 1) {
@@ -164,13 +165,24 @@ CScript CreateMultisigRedeemscript(const int required, const std::vector<CPubKey
164165
throw JSONRPCError(RPC_INVALID_PARAMETER, "Number of keys involved in the multisignature address creation > 16\nReduce the number");
165166
}
166167

167-
CScript result = GetScriptForMultisig(required, pubkeys);
168+
script_out = GetScriptForMultisig(required, pubkeys);
168169

169-
if (result.size() > MAX_SCRIPT_ELEMENT_SIZE) {
170-
throw JSONRPCError(RPC_INVALID_PARAMETER, (strprintf("redeemScript exceeds size limit: %d > %d", result.size(), MAX_SCRIPT_ELEMENT_SIZE)));
170+
if (script_out.size() > MAX_SCRIPT_ELEMENT_SIZE) {
171+
throw JSONRPCError(RPC_INVALID_PARAMETER, (strprintf("redeemScript exceeds size limit: %d > %d", script_out.size(), MAX_SCRIPT_ELEMENT_SIZE)));
171172
}
172173

173-
return result;
174+
// Check if any keys are uncompressed. If so, the type is legacy
175+
for (const CPubKey& pk : pubkeys) {
176+
if (!pk.IsCompressed()) {
177+
type = OutputType::LEGACY;
178+
break;
179+
}
180+
}
181+
182+
// Make the address
183+
CTxDestination dest = AddAndGetDestinationForScript(keystore, script_out, type);
184+
185+
return dest;
174186
}
175187

176188
class DescribeAddressVisitor : public boost::static_visitor<UniValue>

src/rpc/util.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#define BITCOIN_RPC_UTIL_H
77

88
#include <node/transaction.h>
9+
#include <outputtype.h>
910
#include <pubkey.h>
1011
#include <rpc/protocol.h>
1112
#include <script/standard.h>
@@ -70,7 +71,7 @@ extern std::string HelpExampleRpc(const std::string& methodname, const std::stri
7071

7172
CPubKey HexToPubKey(const std::string& hex_in);
7273
CPubKey AddrToPubKey(CKeyStore* const keystore, const std::string& addr_in);
73-
CScript CreateMultisigRedeemscript(const int required, const std::vector<CPubKey>& pubkeys);
74+
CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, CKeyStore& keystore, CScript& script_out);
7475

7576
UniValue DescribeAddress(const CTxDestination& dest);
7677

src/wallet/rpcwallet.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,8 +1036,8 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request)
10361036
}
10371037

10381038
// Construct using pay-to-script-hash:
1039-
CScript inner = CreateMultisigRedeemscript(required, pubkeys);
1040-
CTxDestination dest = AddAndGetDestinationForScript(*pwallet, inner, output_type);
1039+
CScript inner;
1040+
CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, *pwallet, inner);
10411041
pwallet->SetAddressBook(dest, label, "send");
10421042

10431043
UniValue result(UniValue::VOBJ);

test/functional/rpc_createmultisig.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@
77
from test_framework.test_framework import BitcoinTestFramework
88
from test_framework.util import (
99
assert_raises_rpc_error,
10+
assert_equal,
1011
)
11-
import decimal
12+
from test_framework.key import ECPubKey
1213

14+
import binascii
15+
import decimal
16+
import itertools
1317

1418
class RpcCreateMultiSigTest(BitcoinTestFramework):
1519
def set_test_params(self):
@@ -44,6 +48,30 @@ def run_test(self):
4448

4549
self.checkbalances()
4650

51+
# Test mixed compressed and uncompressed pubkeys
52+
self.log.info('Mixed compressed and uncompressed multisigs are not allowed')
53+
pk0 = node0.getaddressinfo(node0.getnewaddress())['pubkey']
54+
pk1 = node1.getaddressinfo(node1.getnewaddress())['pubkey']
55+
pk2 = node2.getaddressinfo(node2.getnewaddress())['pubkey']
56+
57+
# decompress pk2
58+
pk_obj = ECPubKey()
59+
pk_obj.set(binascii.unhexlify(pk2))
60+
pk_obj.compressed = False
61+
pk2 = binascii.hexlify(pk_obj.get_bytes()).decode()
62+
63+
# Check all permutations of keys because order matters apparently
64+
for keys in itertools.permutations([pk0, pk1, pk2]):
65+
# Results should be the same as this legacy one
66+
legacy_addr = node0.createmultisig(2, keys, 'legacy')['address']
67+
assert_equal(legacy_addr, node0.addmultisigaddress(2, keys, '', 'legacy')['address'])
68+
69+
# Generate addresses with the segwit types. These should all make legacy addresses
70+
assert_equal(legacy_addr, node0.createmultisig(2, keys, 'bech32')['address'])
71+
assert_equal(legacy_addr, node0.createmultisig(2, keys, 'p2sh-segwit')['address'])
72+
assert_equal(legacy_addr, node0.addmultisigaddress(2, keys, '', 'bech32')['address'])
73+
assert_equal(legacy_addr, node0.addmultisigaddress(2, keys, '', 'p2sh-segwit')['address'])
74+
4775
def check_addmultisigaddress_errors(self):
4876
self.log.info('Check that addmultisigaddress fails when the private keys are missing')
4977
addresses = [self.nodes[1].getnewaddress(address_type='legacy') for _ in range(2)]

0 commit comments

Comments
 (0)