Skip to content

Commit a495034

Browse files
committed
Make and get the multisig redeemscript and destination in one function instead of two
Instead of creating a redeemScript with CreateMultisigRedeemscript and then getting the destination with AddAndGetDestinationForScript, do both in the same function. CreateMultisigRedeemscript is changed to AddAndGetMultisigDestination. It creates the redeemScript and returns it via an output parameter. Then it calls AddAndGetDestinationForScript to add the destination to the keystore and get the proper destination. This allows us to inspect the public keys in the redeemScript before creating the destination so that the correct destination is used when uncompressed pubkeys are in the multisig.
1 parent 65526fc commit a495034

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
@@ -130,9 +130,9 @@ static UniValue createmultisig(const JSONRPCRequest& request)
130130
}
131131

132132
// Construct using pay-to-script-hash:
133-
const CScript inner = CreateMultisigRedeemscript(required, pubkeys);
134133
CBasicKeyStore keystore;
135-
const CTxDestination dest = AddAndGetDestinationForScript(keystore, inner, output_type);
134+
CScript inner;
135+
const CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, keystore, inner);
136136

137137
UniValue result(UniValue::VOBJ);
138138
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
@@ -1023,8 +1023,8 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request)
10231023
}
10241024

10251025
// Construct using pay-to-script-hash:
1026-
CScript inner = CreateMultisigRedeemscript(required, pubkeys);
1027-
CTxDestination dest = AddAndGetDestinationForScript(*pwallet, inner, output_type);
1026+
CScript inner;
1027+
CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, *pwallet, inner);
10281028
pwallet->SetAddressBook(dest, label, "send");
10291029

10301030
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)