Skip to content

Commit 1df206f

Browse files
committed
Disallow using addresses in createmultisig
Make createmultisig only accept public keys with the old functionality marked as deprecated. Splits _createmultisig_redeemscript into two functions, one for getting public keys from UniValue and one for getting addresses from UniValue and then their respective public keys. The one for retrieving address's public keys is located in rpcwallet.cpp Changes addwitnessaddress's output to be a JSON object with two fields, address and redeemscript. Adds a test to deprecated_rpc.py for testing the deprecation. Update the tests to use addwitnessaddress or give only public keys to createmultisig. Anything that used addwitnessaddress was also updated to reflect the new API.
1 parent 0910cbe commit 1df206f

15 files changed

+204
-126
lines changed

src/Makefile.am

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ BITCOIN_CORE_H = \
133133
rpc/safemode.h \
134134
rpc/server.h \
135135
rpc/register.h \
136+
rpc/util.h \
136137
scheduler.h \
137138
script/sigcache.h \
138139
script/sign.h \
@@ -352,6 +353,7 @@ libbitcoin_util_a_SOURCES = \
352353
fs.cpp \
353354
random.cpp \
354355
rpc/protocol.cpp \
356+
rpc/util.cpp \
355357
support/cleanse.cpp \
356358
sync.cpp \
357359
threadinterrupt.cpp \

src/rpc/misc.cpp

Lines changed: 32 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <netbase.h>
1616
#include <rpc/blockchain.h>
1717
#include <rpc/server.h>
18+
#include <rpc/util.h>
1819
#include <timedata.h>
1920
#include <util.h>
2021
#include <utilstrencodings.h>
@@ -254,88 +255,21 @@ UniValue validateaddress(const JSONRPCRequest& request)
254255
// Needed even with !ENABLE_WALLET, to pass (ignored) pointers around
255256
class CWallet;
256257

257-
/**
258-
* Used by addmultisigaddress / createmultisig:
259-
*/
260-
CScript _createmultisig_redeemScript(CWallet * const pwallet, const UniValue& params)
261-
{
262-
int nRequired = params[0].get_int();
263-
const UniValue& keys = params[1].get_array();
264-
265-
// Gather public keys
266-
if (nRequired < 1)
267-
throw std::runtime_error("a multisignature address must require at least one key to redeem");
268-
if ((int)keys.size() < nRequired)
269-
throw std::runtime_error(
270-
strprintf("not enough keys supplied "
271-
"(got %u keys, but need at least %d to redeem)", keys.size(), nRequired));
272-
if (keys.size() > 16)
273-
throw std::runtime_error("Number of addresses involved in the multisignature address creation > 16\nReduce the number");
274-
std::vector<CPubKey> pubkeys;
275-
pubkeys.resize(keys.size());
276-
for (unsigned int i = 0; i < keys.size(); i++)
277-
{
278-
const std::string& ks = keys[i].get_str();
279-
#ifdef ENABLE_WALLET
280-
// Case 1: Bitcoin address and we have full public key:
281-
CTxDestination dest = DecodeDestination(ks);
282-
if (pwallet && IsValidDestination(dest)) {
283-
CKeyID key = GetKeyForDestination(*pwallet, dest);
284-
if (key.IsNull()) {
285-
throw std::runtime_error(strprintf("%s does not refer to a key", ks));
286-
}
287-
CPubKey vchPubKey;
288-
if (!pwallet->GetPubKey(key, vchPubKey)) {
289-
throw std::runtime_error(strprintf("no full public key for address %s", ks));
290-
}
291-
if (!vchPubKey.IsFullyValid())
292-
throw std::runtime_error(" Invalid public key: "+ks);
293-
pubkeys[i] = vchPubKey;
294-
}
295-
296-
// Case 2: hex public key
297-
else
298-
#endif
299-
if (IsHex(ks))
300-
{
301-
CPubKey vchPubKey(ParseHex(ks));
302-
if (!vchPubKey.IsFullyValid())
303-
throw std::runtime_error(" Invalid public key: "+ks);
304-
pubkeys[i] = vchPubKey;
305-
}
306-
else
307-
{
308-
throw std::runtime_error(" Invalid public key: "+ks);
309-
}
310-
}
311-
CScript result = GetScriptForMultisig(nRequired, pubkeys);
312-
313-
if (result.size() > MAX_SCRIPT_ELEMENT_SIZE)
314-
throw std::runtime_error(
315-
strprintf("redeemScript exceeds size limit: %d > %d", result.size(), MAX_SCRIPT_ELEMENT_SIZE));
316-
317-
return result;
318-
}
319-
320258
UniValue createmultisig(const JSONRPCRequest& request)
321259
{
322-
#ifdef ENABLE_WALLET
323-
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
324-
#else
325-
CWallet * const pwallet = nullptr;
326-
#endif
327-
328260
if (request.fHelp || request.params.size() < 2 || request.params.size() > 2)
329261
{
330262
std::string msg = "createmultisig nrequired [\"key\",...]\n"
331263
"\nCreates a multi-signature address with n signature of m keys required.\n"
332264
"It returns a json object with the address and redeemScript.\n"
333-
265+
"DEPRECATION WARNING: Using addresses with createmultisig is deprecated. Clients must\n"
266+
"transition to using addmultisigaddress to create multisig addresses with addresses known\n"
267+
"to the wallet before upgrading to v0.17. To use the deprecated functionality, start bitcoind with -deprecatedrpc=createmultisig\n"
334268
"\nArguments:\n"
335-
"1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n"
336-
"2. \"keys\" (string, required) A json array of keys which are bitcoin addresses or hex-encoded public keys\n"
269+
"1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n"
270+
"2. \"keys\" (string, required) A json array of hex-encoded public keys\n"
337271
" [\n"
338-
" \"key\" (string) bitcoin address or hex-encoded public key\n"
272+
" \"key\" (string) The hex-encoded public key\n"
339273
" ,...\n"
340274
" ]\n"
341275

@@ -346,16 +280,37 @@ UniValue createmultisig(const JSONRPCRequest& request)
346280
"}\n"
347281

348282
"\nExamples:\n"
349-
"\nCreate a multisig address from 2 addresses\n"
350-
+ HelpExampleCli("createmultisig", "2 \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") +
283+
"\nCreate a multisig address from 2 public keys\n"
284+
+ HelpExampleCli("createmultisig", "2 \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"") +
351285
"\nAs a json rpc call\n"
352-
+ HelpExampleRpc("createmultisig", "2, \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"")
286+
+ HelpExampleRpc("createmultisig", "2, \"[\\\"03789ed0bb717d88f7d321a368d905e7430207ebbd82bd342cf11ae157a7ace5fd\\\",\\\"03dbc6764b8884a92e871274b87583e6d5c2a58819473e17e107ef3f6aa5a61626\\\"]\"")
353287
;
354288
throw std::runtime_error(msg);
355289
}
356290

291+
int required = request.params[0].get_int();
292+
293+
// Get the public keys
294+
const UniValue& keys = request.params[1].get_array();
295+
std::vector<CPubKey> pubkeys;
296+
for (unsigned int i = 0; i < keys.size(); ++i) {
297+
if (IsHex(keys[i].get_str()) && (keys[i].get_str().length() == 66 || keys[i].get_str().length() == 130)) {
298+
pubkeys.push_back(HexToPubKey(keys[i].get_str()));
299+
} else {
300+
#ifdef ENABLE_WALLET
301+
CWallet* const pwallet = GetWalletForJSONRPCRequest(request);
302+
if (IsDeprecatedRPCEnabled("createmultisig") && EnsureWalletIsAvailable(pwallet, false)) {
303+
pubkeys.push_back(AddrToPubKey(pwallet, keys[i].get_str()));
304+
} else
305+
#endif
306+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid public key: %s\nNote that from v0.16, createmultisig no longer accepts addresses."
307+
" Clients must transition to using addmultisigaddress to create multisig addresses with addresses known to the wallet before upgrading to v0.17."
308+
" To use the deprecated functionality, start bitcoind with -deprecatedrpc=createmultisig", keys[i].get_str()));
309+
}
310+
}
311+
357312
// Construct using pay-to-script-hash:
358-
CScript inner = _createmultisig_redeemScript(pwallet, request.params);
313+
CScript inner = CreateMultisigRedeemscript(required, pubkeys);
359314
CScriptID innerID(inner);
360315

361316
UniValue result(UniValue::VOBJ);

src/rpc/util.cpp

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright (c) 2017 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <base58.h>
6+
#include <keystore.h>
7+
#include <pubkey.h>
8+
#include <rpc/protocol.h>
9+
#include <rpc/util.h>
10+
#include <tinyformat.h>
11+
#include <utilstrencodings.h>
12+
13+
// Converts a hex string to a public key if possible
14+
CPubKey HexToPubKey(const std::string& hex_in)
15+
{
16+
if (!IsHex(hex_in)) {
17+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key: " + hex_in);
18+
}
19+
CPubKey vchPubKey(ParseHex(hex_in));
20+
if (!vchPubKey.IsFullyValid()) {
21+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid public key: " + hex_in);
22+
}
23+
return vchPubKey;
24+
}
25+
26+
// Retrieves a public key for an address from the given CKeyStore
27+
CPubKey AddrToPubKey(CKeyStore* const keystore, const std::string& addr_in)
28+
{
29+
CTxDestination dest = DecodeDestination(addr_in);
30+
if (!IsValidDestination(dest)) {
31+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address: " + addr_in);
32+
}
33+
CKeyID key = GetKeyForDestination(*keystore, dest);
34+
if (key.IsNull()) {
35+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("%s does not refer to a key", addr_in));
36+
}
37+
CPubKey vchPubKey;
38+
if (!keystore->GetPubKey(key, vchPubKey)) {
39+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("no full public key for address %s", addr_in));
40+
}
41+
if (!vchPubKey.IsFullyValid()) {
42+
throw JSONRPCError(RPC_INTERNAL_ERROR, "Wallet contains an invalid public key");
43+
}
44+
return vchPubKey;
45+
}
46+
47+
// Creates a multisig redeemscript from a given list of public keys and number required.
48+
CScript CreateMultisigRedeemscript(const int required, const std::vector<CPubKey>& pubkeys)
49+
{
50+
// Gather public keys
51+
if (required < 1) {
52+
throw JSONRPCError(RPC_INVALID_PARAMETER, "a multisignature address must require at least one key to redeem");
53+
}
54+
if ((int)pubkeys.size() < required) {
55+
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("not enough keys supplied (got %u keys, but need at least %d to redeem)", pubkeys.size(), required));
56+
}
57+
if (pubkeys.size() > 16) {
58+
throw JSONRPCError(RPC_INVALID_PARAMETER, "Number of keys involved in the multisignature address creation > 16\nReduce the number");
59+
}
60+
61+
CScript result = GetScriptForMultisig(required, pubkeys);
62+
63+
if (result.size() > MAX_SCRIPT_ELEMENT_SIZE) {
64+
throw JSONRPCError(RPC_INVALID_PARAMETER, (strprintf("redeemScript exceeds size limit: %d > %d", result.size(), MAX_SCRIPT_ELEMENT_SIZE)));
65+
}
66+
67+
return result;
68+
}

src/rpc/util.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) 2017 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_RPC_UTIL_H
6+
#define BITCOIN_RPC_UTIL_H
7+
8+
#include <string>
9+
#include <vector>
10+
11+
class CKeyStore;
12+
class CPubKey;
13+
class CScript;
14+
15+
CPubKey HexToPubKey(const std::string& hex_in);
16+
CPubKey AddrToPubKey(CKeyStore* const keystore, const std::string& addr_in);
17+
CScript CreateMultisigRedeemscript(const int required, const std::vector<CPubKey>& pubkeys);
18+
19+
#endif // BITCOIN_RPC_UTIL_H

src/wallet/rpcwallet.cpp

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <rpc/mining.h>
1919
#include <rpc/safemode.h>
2020
#include <rpc/server.h>
21+
#include <rpc/util.h>
2122
#include <script/sign.h>
2223
#include <timedata.h>
2324
#include <util.h>
@@ -1161,9 +1162,6 @@ UniValue sendmany(const JSONRPCRequest& request)
11611162
return wtx.GetHash().GetHex();
11621163
}
11631164

1164-
// Defined in rpc/misc.cpp
1165-
extern CScript _createmultisig_redeemScript(CWallet * const pwallet, const UniValue& params);
1166-
11671165
UniValue addmultisigaddress(const JSONRPCRequest& request)
11681166
{
11691167
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
@@ -1181,16 +1179,22 @@ UniValue addmultisigaddress(const JSONRPCRequest& request)
11811179
"If 'account' is specified (DEPRECATED), assign address to that account.\n"
11821180

11831181
"\nArguments:\n"
1184-
"1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n"
1185-
"2. \"keys\" (string, required) A json array of bitcoin addresses or hex-encoded public keys\n"
1182+
"1. nrequired (numeric, required) The number of required signatures out of the n keys or addresses.\n"
1183+
"2. \"keys\" (string, required) A json array of bitcoin addresses or hex-encoded public keys\n"
11861184
" [\n"
1187-
" \"address\" (string) bitcoin address or hex-encoded public key\n"
1185+
" \"address\" (string) bitcoin address or hex-encoded public key\n"
11881186
" ...,\n"
11891187
" ]\n"
1190-
"3. \"account\" (string, optional) DEPRECATED. An account to assign the addresses to.\n"
1188+
"3. \"account\" (string, optional) DEPRECATED. An account to assign the addresses to.\n"
11911189

11921190
"\nResult:\n"
1193-
"\"address\" (string) A bitcoin address associated with the keys.\n"
1191+
"{\n"
1192+
" \"address\":\"multisigaddress\", (string) The value of the new multisig address.\n"
1193+
" \"redeemScript\":\"script\" (string) The string value of the hex-encoded redemption script.\n"
1194+
"}\n"
1195+
"\nResult (DEPRECATED. To see this result in v0.16 instead, please start bitcoind with -deprecatedrpc=addmultisigaddress).\n"
1196+
" clients should transition to the new output api before upgrading to v0.17.\n"
1197+
"\"address\" (string) A bitcoin address associated with the keys.\n"
11941198

11951199
"\nExamples:\n"
11961200
"\nAdd a multisig address from 2 addresses\n"
@@ -1207,14 +1211,34 @@ UniValue addmultisigaddress(const JSONRPCRequest& request)
12071211
if (!request.params[2].isNull())
12081212
strAccount = AccountFromValue(request.params[2]);
12091213

1214+
int required = request.params[0].get_int();
1215+
1216+
// Get the public keys
1217+
const UniValue& keys_or_addrs = request.params[1].get_array();
1218+
std::vector<CPubKey> pubkeys;
1219+
for (unsigned int i = 0; i < keys_or_addrs.size(); ++i) {
1220+
if (IsHex(keys_or_addrs[i].get_str()) && (keys_or_addrs[i].get_str().length() == 66 || keys_or_addrs[i].get_str().length() == 130)) {
1221+
pubkeys.push_back(HexToPubKey(keys_or_addrs[i].get_str()));
1222+
} else {
1223+
pubkeys.push_back(AddrToPubKey(pwallet, keys_or_addrs[i].get_str()));
1224+
}
1225+
}
1226+
12101227
// Construct using pay-to-script-hash:
1211-
CScript inner = _createmultisig_redeemScript(pwallet, request.params);
1228+
CScript inner = CreateMultisigRedeemscript(required, pubkeys);
12121229
pwallet->AddCScript(inner);
1213-
12141230
CTxDestination dest = pwallet->AddAndGetDestinationForScript(inner, g_address_type);
1215-
12161231
pwallet->SetAddressBook(dest, strAccount, "send");
1217-
return EncodeDestination(dest);
1232+
1233+
// Return old style interface
1234+
if (IsDeprecatedRPCEnabled("addmultisigaddress")) {
1235+
return EncodeDestination(dest);
1236+
}
1237+
1238+
UniValue result(UniValue::VOBJ);
1239+
result.pushKV("address", EncodeDestination(dest));
1240+
result.pushKV("redeemScript", HexStr(inner.begin(), inner.end()));
1241+
return result;
12181242
}
12191243

12201244
class Witnessifier : public boost::static_visitor<bool>

test/functional/address_types.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,14 @@ def run_test(self):
117117

118118
# addmultisigaddress with at least 1 uncompressed key should return a legacy address.
119119
for node in range(4):
120-
self.test_address(node, self.nodes[node].addmultisigaddress(2, [uncompressed_1, uncompressed_2]), True, 'legacy')
121-
self.test_address(node, self.nodes[node].addmultisigaddress(2, [compressed_1, uncompressed_2]), True, 'legacy')
122-
self.test_address(node, self.nodes[node].addmultisigaddress(2, [uncompressed_1, compressed_2]), True, 'legacy')
120+
self.test_address(node, self.nodes[node].addmultisigaddress(2, [uncompressed_1, uncompressed_2])['address'], True, 'legacy')
121+
self.test_address(node, self.nodes[node].addmultisigaddress(2, [compressed_1, uncompressed_2])['address'], True, 'legacy')
122+
self.test_address(node, self.nodes[node].addmultisigaddress(2, [uncompressed_1, compressed_2])['address'], True, 'legacy')
123123
# addmultisigaddress with all compressed keys should return the appropriate address type (even when the keys are not ours).
124-
self.test_address(0, self.nodes[0].addmultisigaddress(2, [compressed_1, compressed_2]), True, 'legacy')
125-
self.test_address(1, self.nodes[1].addmultisigaddress(2, [compressed_1, compressed_2]), True, 'p2sh-segwit')
126-
self.test_address(2, self.nodes[2].addmultisigaddress(2, [compressed_1, compressed_2]), True, 'p2sh-segwit')
127-
self.test_address(3, self.nodes[3].addmultisigaddress(2, [compressed_1, compressed_2]), True, 'bech32')
124+
self.test_address(0, self.nodes[0].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'legacy')
125+
self.test_address(1, self.nodes[1].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'p2sh-segwit')
126+
self.test_address(2, self.nodes[2].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'p2sh-segwit')
127+
self.test_address(3, self.nodes[3].addmultisigaddress(2, [compressed_1, compressed_2])['address'], True, 'bech32')
128128

129129
for explicit_type, multisig, from_node in itertools.product([False, True], [False, True], range(4)):
130130
address_type = None
@@ -155,7 +155,7 @@ def run_test(self):
155155
else:
156156
addr1 = self.nodes[to_node].getnewaddress()
157157
addr2 = self.nodes[to_node].getnewaddress()
158-
address = self.nodes[to_node].addmultisigaddress(2, [addr1, addr2])
158+
address = self.nodes[to_node].addmultisigaddress(2, [addr1, addr2])['address']
159159

160160
# Do some sanity checking on the created address
161161
if address_type is not None:

test/functional/deprecated_rpc.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class DeprecatedRpcTest(BitcoinTestFramework):
1010
def set_test_params(self):
1111
self.num_nodes = 2
1212
self.setup_clean_chain = True
13-
self.extra_args = [[], ["-deprecatedrpc=estimatefee"]]
13+
self.extra_args = [[], ["-deprecatedrpc=estimatefee", "-deprecatedrpc=createmultisig"]]
1414

1515
def run_test(self):
1616
self.log.info("estimatefee: Shows deprecated message")
@@ -19,5 +19,9 @@ def run_test(self):
1919
self.log.info("Using -deprecatedrpc=estimatefee bypasses the error")
2020
self.nodes[1].estimatefee(1)
2121

22+
self.log.info("Make sure that -deprecatedrpc=createmultisig allows it to take addresses")
23+
assert_raises_rpc_error(-5, "Invalid public key", self.nodes[0].createmultisig, 1, [self.nodes[0].getnewaddress()])
24+
self.nodes[1].createmultisig(1, [self.nodes[1].getnewaddress()])
25+
2226
if __name__ == '__main__':
2327
DeprecatedRpcTest().main()

0 commit comments

Comments
 (0)