Skip to content

Commit 69ec021

Browse files
committed
Merge #11415: [RPC] Disallow using addresses in createmultisig
1df206f Disallow using addresses in createmultisig (Andrew Chow) Pull request description: This PR should be the last part of #7965. This PR makes createmultisig only accept public keys and marks the old functionality of accepting addresses as deprecated. It also splits `_createmultisig_redeemscript` into two functions, `_createmultisig_getpubkeys` and `_createmultisig_getaddr_pubkeys`. `_createmultisig_getpubkeys` retrieves public keys from the RPC parameters and `_createmultisig_getaddr_pubkeys` retrieves addresses' public keys from the wallet. `_createmultisig_getaddr_pubkeys` requires the wallet and is only used by `addwitnessaddress` (except when `createmultisig` is used in deprecated mode). `addwitnessaddress`'s API is also changed. Instead of returning just an address, it now returns the same thing as `createmultisig`: a JSON object with two fields, address and redeemscript. Tree-SHA512: a5796e41935ad5e47d8165ff996a8b20d5112b5fc1a06a6d3c7f5513c13e7628a4fd37ec30fde05d8b15abfed51bc250710140f6834b13f64d0a0e47a3817969
2 parents 6e89de5 + 1df206f commit 69ec021

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)