Merge 8751 via sort-multisigs-26+knots

This commit is contained in:
Luke Dashjr 2024-06-21 19:28:12 +00:00
commit cd9128640b
12 changed files with 269 additions and 29 deletions

View File

@ -21,6 +21,7 @@ BIPs that are implemented by Bitcoin Core:
* [`BIP 61`](https://github.com/bitcoin/bips/blob/master/bip-0061.mediawiki): The 'reject' protocol message (and the protocol version bump to 70002) was added in **v0.9.0** ([PR #3185](https://github.com/bitcoin/bitcoin/pull/3185)). Starting **v0.17.0**, whether to send reject messages can be configured with the `-enablebip61` option, and support is deprecated (disabled by default) as of **v0.18.0**. Support was removed in **v0.20.0** ([PR #15437](https://github.com/bitcoin/bitcoin/pull/15437)).
* [`BIP 65`](https://github.com/bitcoin/bips/blob/master/bip-0065.mediawiki): The CHECKLOCKTIMEVERIFY softfork was merged in **v0.12.0** ([PR #6351](https://github.com/bitcoin/bitcoin/pull/6351)), and backported to **v0.11.2** and **v0.10.4**. Mempool-only CLTV was added in [PR #6124](https://github.com/bitcoin/bitcoin/pull/6124).
* [`BIP 66`](https://github.com/bitcoin/bips/blob/master/bip-0066.mediawiki): The strict DER rules and associated version 3 blocks have been implemented since **v0.10.0** ([PR #5713](https://github.com/bitcoin/bitcoin/pull/5713)).
* [`BIP 67`](https://github.com/bitcoin/bips/blob/master/bip-0067.mediawiki): Sorting multisig keys according to BIP 67 was merged in **v0.15.1** ([PR #8751](https://github.com/bitcoin/bitcoin/pull/8751)).
* [`BIP 68`](https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki): Sequence locks have been implemented as of **v0.12.1** ([PR #7184](https://github.com/bitcoin/bitcoin/pull/7184)), and have been *buried* since **v0.19.0** ([PR #16060](https://github.com/bitcoin/bitcoin/pull/16060)).
* [`BIP 70`](https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki) [`71`](https://github.com/bitcoin/bips/blob/master/bip-0071.mediawiki) [`72`](https://github.com/bitcoin/bips/blob/master/bip-0072.mediawiki):
Payment Protocol support has been available in Bitcoin Core GUI since **v0.9.0** ([PR #5216](https://github.com/bitcoin/bitcoin/pull/5216)).

View File

@ -101,8 +101,12 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "dumptxoutset", 2, "show_header" },
{ "addmultisigaddress", 0, "nrequired" },
{ "addmultisigaddress", 1, "keys" },
{ "addmultisigaddress", 2, "options" },
{ "addmultisigaddress", 2, "sort" },
{ "createmultisig", 0, "nrequired" },
{ "createmultisig", 1, "keys" },
{ "createmultisig", 2, "options" },
{ "createmultisig", 2, "sort" },
{ "listunspent", 0, "minconf" },
{ "listunspent", 1, "maxconf" },
{ "listunspent", 2, "addresses" },

View File

@ -89,14 +89,20 @@ static RPCHelpMan createmultisig()
{
return RPCHelpMan{"createmultisig",
"\nCreates a multi-signature address with n signature of m keys required.\n"
"It returns a json object with the address and redeemScript.\n",
"It returns a json object with the address and redeemScript.\n"
"Public keys can be sorted according to BIP67 during the request if required.\n",
{
{"nrequired", RPCArg::Type::NUM, RPCArg::Optional::NO, "The number of required signatures out of the n keys."},
{"keys", RPCArg::Type::ARR, RPCArg::Optional::NO, "The hex-encoded public keys.",
{
{"key", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The hex-encoded public key"},
}},
{"address_type", RPCArg::Type::STR, RPCArg::Default{"legacy"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
{"options|address_type", {RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Type::STR}, RPCArg::Optional::OMITTED, "",
{
{"address_type", RPCArg::Type::STR, RPCArg::Default{"legacy"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\".", RPCArgOptions{.also_positional = true}},
{"sort", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to sort public keys according to BIP67."},
},
RPCArgOptions{.oneline_description="options"}},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
@ -120,33 +126,59 @@ static RPCHelpMan createmultisig()
{
int required = request.params[0].getInt<int>();
bool sort = false;
OutputType output_type = OutputType::LEGACY;
if (request.params[2].isStr()) {
// backward compatibility
std::optional<OutputType> parsed = ParseOutputType(request.params[2].get_str());
if (!parsed) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str()));
}
output_type = parsed.value();
} else if (!request.params[2].isNull()) {
const UniValue& options = request.params[2].get_obj();
RPCTypeCheckObj(options,
{
{"address_type", UniValueType(UniValue::VSTR)},
{"sort", UniValueType(UniValue::VBOOL)},
},
true, true);
if (options.exists("address_type")) {
std::optional<OutputType> parsed = ParseOutputType(options["address_type"].get_str());
if (!parsed) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", options["address_type"].get_str()));
}
output_type = parsed.value();
}
if (options.exists("sort")) {
sort = options["sort"].get_bool();
}
}
if (output_type == OutputType::BECH32M) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "createmultisig cannot create bech32m multisig addresses");
}
// Get the public keys
const UniValue& keys = request.params[1].get_array();
std::vector<CPubKey> pubkeys;
for (unsigned int i = 0; i < keys.size(); ++i) {
if (IsHex(keys[i].get_str()) && (keys[i].get_str().length() == 66 || keys[i].get_str().length() == 130)) {
pubkeys.push_back(HexToPubKey(keys[i].get_str()));
if (sort && !pubkeys.back().IsCompressed()) {
throw std::runtime_error(strprintf("Compressed key required for BIP67: %s", keys[i].get_str()));
}
} else {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid public key: %s\n.", keys[i].get_str()));
}
}
// Get the output type
OutputType output_type = OutputType::LEGACY;
if (!request.params[2].isNull()) {
std::optional<OutputType> parsed = ParseOutputType(request.params[2].get_str());
if (!parsed) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[2].get_str()));
} else if (parsed.value() == OutputType::BECH32M) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "createmultisig cannot create bech32m multisig addresses");
}
output_type = parsed.value();
}
// Construct using pay-to-script-hash:
FillableSigningProvider keystore;
CScript inner;
const CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, keystore, inner);
const CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, keystore, inner, sort);
// Make the descriptor
std::unique_ptr<Descriptor> descriptor = InferDescriptor(GetScriptForDestination(dest), keystore);

View File

@ -222,7 +222,7 @@ CPubKey AddrToPubKey(const FillableSigningProvider& keystore, const std::string&
}
// Creates a multisig address from a given list of public keys, number of signatures required, and the address type
CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, FillableSigningProvider& keystore, CScript& script_out)
CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, FillableSigningProvider& keystore, CScript& script_out, bool sort)
{
// Gather public keys
if (required < 1) {
@ -235,7 +235,7 @@ CTxDestination AddAndGetMultisigDestination(const int required, const std::vecto
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Number of keys involved in the multisignature address creation > %d\nReduce the number", MAX_PUBKEYS_PER_MULTISIG));
}
script_out = GetScriptForMultisig(required, pubkeys);
script_out = GetScriptForMultisig(required, pubkeys, sort);
// Check if any keys are uncompressed. If so, the type is legacy
for (const CPubKey& pk : pubkeys) {

View File

@ -118,7 +118,7 @@ std::string HelpExampleRpcNamed(const std::string& methodname, const RPCArgList&
CPubKey HexToPubKey(const std::string& hex_in);
CPubKey AddrToPubKey(const FillableSigningProvider& keystore, const std::string& addr_in);
CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, FillableSigningProvider& keystore, CScript& script_out);
CTxDestination AddAndGetMultisigDestination(const int required, const std::vector<CPubKey>& pubkeys, OutputType type, FillableSigningProvider& keystore, CScript& script_out, bool sort);
UniValue DescribeAddress(const CTxDestination& dest);

View File

@ -211,13 +211,23 @@ CScript GetScriptForRawPubKey(const CPubKey& pubKey)
return CScript() << std::vector<unsigned char>(pubKey.begin(), pubKey.end()) << OP_CHECKSIG;
}
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys)
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys, bool fSorted)
{
CScript script;
std::vector<std::vector<unsigned char>> vEncoded;
vEncoded.reserve(keys.size());
for (const CPubKey& key : keys) {
vEncoded.emplace_back(ToByteVector(key));
}
if (fSorted) {
std::sort(vEncoded.begin(), vEncoded.end());
}
CScript script;
script << nRequired;
for (const CPubKey& key : keys)
script << ToByteVector(key);
for (const std::vector<unsigned char>& bytes : vEncoded) {
script << bytes;
}
script << keys.size() << OP_CHECKMULTISIG;
return script;

View File

@ -61,6 +61,6 @@ CScript GetScriptForRawPubKey(const CPubKey& pubkey);
std::optional<std::pair<int, std::vector<Span<const unsigned char>>>> MatchMultiA(const CScript& script LIFETIMEBOUND);
/** Generate a multisig script. */
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys);
CScript GetScriptForMultisig(int nRequired, const std::vector<CPubKey>& keys, bool fSorted=false);
#endif // BITCOIN_SCRIPT_SOLVER_H

View File

@ -225,6 +225,7 @@ RPCHelpMan addmultisigaddress()
"This functionality is only intended for use with non-watchonly addresses.\n"
"See `importaddress` for watchonly p2sh address support.\n"
"If 'label' is specified, assign address to that label.\n"
"Public keys can be sorted according to BIP67 during the request if required.\n"
"Note: This command is only compatible with legacy wallets.\n",
{
{"nrequired", RPCArg::Type::NUM, RPCArg::Optional::NO, "The number of required signatures out of the n keys or addresses."},
@ -233,8 +234,14 @@ RPCHelpMan addmultisigaddress()
{"key", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "bitcoin address or hex-encoded public key"},
},
},
{"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A label to assign the addresses to."},
{"address_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -addresstype"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
{"options|label", {RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Type::STR}, RPCArg::Optional::OMITTED, "",
{
{"address_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -addresstype"}, "The address type to use. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\".", RPCArgOptions{.also_positional = true}},
{"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A label to assign the address to.", RPCArgOptions{.also_positional = true}},
{"sort", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether to sort public keys according to BIP67."},
},
RPCArgOptions{.oneline_description="\"options\""}},
{"address_type", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "", RPCArgOptions{.hidden=true}},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
@ -263,10 +270,47 @@ RPCHelpMan addmultisigaddress()
LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
const std::string label{LabelFromValue(request.params[2])};
int required = request.params[0].getInt<int>();
std::string label;
OutputType output_type = pwallet->m_default_address_type;
bool sort = false;
if (!request.params[2].isNull()) {
if (request.params[2].type() == UniValue::VSTR) {
// Backward compatibility
label = LabelFromValue(request.params[2]);
} else {
const UniValue& options = request.params[2];
RPCTypeCheckObj(options,
{
{"address_type", UniValueType(UniValue::VSTR)},
{"label", UniValueType(UniValue::VSTR)},
{"sort", UniValueType(UniValue::VBOOL)},
},
true, true);
if (options.exists("address_type")) {
if (!request.params[3].isNull()) {
throw JSONRPCError(RPC_MISC_ERROR, "address_type provided in both options and 4th parameter");
}
std::optional<OutputType> parsed = ParseOutputType(options["address_type"].get_str());
if (!parsed) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", options["address_type"].get_str()));
}
output_type = parsed.value();
}
if (options.exists("label")) {
label = LabelFromValue(options["label"]);
}
if (options.exists("sort")) {
sort = options["sort"].get_bool();
}
}
}
// Get the public keys
const UniValue& keys_or_addrs = request.params[1].get_array();
std::vector<CPubKey> pubkeys;
@ -276,9 +320,11 @@ RPCHelpMan addmultisigaddress()
} else {
pubkeys.push_back(AddrToPubKey(spk_man, keys_or_addrs[i].get_str()));
}
if (sort && !pubkeys.back().IsCompressed()) {
throw std::runtime_error(strprintf("Compressed key required for BIP67: %s", keys_or_addrs[i].get_str()));
}
}
OutputType output_type = pwallet->m_default_address_type;
if (!request.params[3].isNull()) {
std::optional<OutputType> parsed = ParseOutputType(request.params[3].get_str());
if (!parsed) {
@ -291,7 +337,7 @@ RPCHelpMan addmultisigaddress()
// Construct using pay-to-script-hash:
CScript inner;
CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, spk_man, inner);
CTxDestination dest = AddAndGetMultisigDestination(required, pubkeys, output_type, spk_man, inner, sort);
pwallet->SetAddressBook(dest, label, AddressPurpose::SEND);
// Make the descriptor

View File

@ -0,0 +1,90 @@
#!/usr/bin/env python3
# Copyright (c) 2014-2016 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
# Exercise the createmultisig API
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import (
assert_equal,
assert_raises_rpc_error,
)
class SortMultisigTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.extra_args = [[]]
self.setup_clean_chain = True
def run_simple_test(self):
pub1 = "022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da"
pub2 = "03e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e9"
pub3 = "021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc18"
pubs = [pub1,pub2,pub3]
default = self.nodes[0].createmultisig(2, pubs)
unsorted_ms = self.nodes[0].createmultisig(2, pubs, {"sort": False})
assert_equal(unsorted_ms, self.nodes[0].createmultisig(2, pubs, options={"sort": False}))
assert_equal(unsorted_ms, self.nodes[0].createmultisig(2, pubs, sort=False))
assert_equal("2N2BchzwfyuqJep7sKmFfBucfopHZQuPnpt", unsorted_ms["address"])
assert_equal("5221022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da2103e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e921021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc1853ae", unsorted_ms["redeemScript"])
assert_equal(default["address"], unsorted_ms["address"])
assert_equal(default["redeemScript"], unsorted_ms["redeemScript"])
sorted_ms = self.nodes[0].createmultisig(2, pubs, {"sort": True})
assert_equal(sorted_ms, self.nodes[0].createmultisig(2, pubs, options={"sort": True}))
assert_equal(sorted_ms, self.nodes[0].createmultisig(2, pubs, sort=True))
assert_equal("2NFd5JqpwmQNz3gevZJ3rz9ofuHvqaP9Cye", sorted_ms["address"])
assert_equal("5221021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc1821022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da2103e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e953ae", sorted_ms["redeemScript"])
def run_demonstrate_sorting(self):
pub1 = "022df8750480ad5b26950b25c7ba79d3e37d75f640f8e5d9bcd5b150a0f85014da"
pub2 = "03e3818b65bcc73a7d64064106a859cc1a5a728c4345ff0b641209fba0d90de6e9"
pub3 = "021f2f6e1e50cb6a953935c3601284925decd3fd21bc445712576873fb8c6ebc18"
sorted_ms = self.nodes[0].createmultisig(2, [pub3,pub1,pub2,])
self.test_if_result_matches(2, [pub1,pub2,pub3], True, sorted_ms["address"])
self.test_if_result_matches(2, [pub1,pub3,pub2], True, sorted_ms["address"])
self.test_if_result_matches(2, [pub2,pub3,pub1], True, sorted_ms["address"])
self.test_if_result_matches(2, [pub2,pub1,pub3], True, sorted_ms["address"])
self.test_if_result_matches(2, [pub3,pub1,pub2], True, sorted_ms["address"])
self.test_if_result_matches(2, [pub3,pub2,pub1], True, sorted_ms["address"])
self.test_if_result_matches(2, [pub1,pub2,pub3], False, sorted_ms["address"])
self.test_if_result_matches(2, [pub1,pub3,pub2], False, sorted_ms["address"])
self.test_if_result_matches(2, [pub2,pub3,pub1], False, sorted_ms["address"])
self.test_if_result_matches(2, [pub2,pub1,pub3], False, sorted_ms["address"])
self.test_if_result_matches(2, [pub3,pub2,pub1], False, sorted_ms["address"])
def test_if_result_matches(self, m, keys, sort, against):
result = self.nodes[0].createmultisig(m, keys, {"sort": sort})
assert_equal(sort, result["address"] == against)
def test_compressed_keys_forbidden(self):
pub1 = "02fdf7e1b65a477a7815effde996a03a7d94cbc46f7d14c05ef38425156fc92e22"
pub2 = "04823336da95f0b4cf745839dff26992cef239ad2f08f494e5b57c209e4f3602d5526bc251d480e3284d129f736441560e17f3a7eb7ed665fdf0158f44550b926c"
rs = "522102fdf7e1b65a477a7815effde996a03a7d94cbc46f7d14c05ef38425156fc92e224104823336da95f0b4cf745839dff26992cef239ad2f08f494e5b57c209e4f3602d5526bc251d480e3284d129f736441560e17f3a7eb7ed665fdf0158f44550b926c52ae"
pubs = [pub1,pub2]
default = self.nodes[0].createmultisig(2, pubs)
assert_equal(rs, default["redeemScript"])
unsorted_ms = self.nodes[0].createmultisig(2, pubs, {"sort": False})
assert_equal(rs, unsorted_ms["redeemScript"])
assert_equal(default["address"], unsorted_ms["address"])
assert_equal(default["redeemScript"], unsorted_ms["redeemScript"])
assert_raises_rpc_error(-1, "Compressed key required for BIP67: 04823336da95f0b4cf745839dff26992cef239ad2f08f494e5b57c209e4f3602d5526bc251d480e3284d129f736441560e17f3a7eb7ed665fdf0158f44550b926c", self.nodes[0].createmultisig, 2, pubs, {"sort": True})
def run_test(self):
self.run_simple_test()
self.run_demonstrate_sorting()
self.test_compressed_keys_forbidden()
if __name__ == '__main__':
SortMultisigTest().main()

View File

@ -926,6 +926,12 @@ class RPCOverloadWrapper():
wallet_info = self.getwalletinfo()
if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']):
return self.__getattr__('addmultisigaddress')(nrequired, keys, label, address_type)
if isinstance(label, dict):
options = dict(label) # copy, so we can pop and check for emptiness
assert address_type is None
address_type = options.pop('address_type', None)
label = options.pop('label', None)
assert not options
cms = self.createmultisig(nrequired, keys, address_type)
req = [{
'desc': cms['descriptor'],

View File

@ -304,6 +304,7 @@ BASE_SCRIPTS = [
'mempool_accept.py',
'mempool_fee_histogram.py',
'mempool_expiry.py',
'rpc_sort_multisig.py',
'wallet_import_with_label.py --legacy-wallet',
'wallet_importdescriptors.py --descriptors',
'wallet_importseed.py --descriptors',

View File

@ -66,6 +66,52 @@ class WalletLabelsTest(BitcoinTestFramework):
for rpc_call in rpc_calls:
assert_raises_rpc_error(-11, "Invalid label name", *rpc_call, "*")
def test_sort_multisig(self, node):
node.importprivkey("cSJUMwramrFYHKPfY77FH94bv4Q5rwUCyfD6zX3kLro4ZcWsXFEM")
node.importprivkey("cSpQbSsdKRmxaSWJ3TckCFTrksXNPbh8tfeZESGNQekkVxMbQ77H")
node.importprivkey("cRNbfcJgnvk2QJEVbMsxzoprotm1cy3kVA2HoyjSs3ss5NY5mQqr")
addresses = [
"muRmfCwue81ZT9oc3NaepefPscUHtP5kyC",
"n12RzKwqWPPA4cWGzkiebiM7Gu6NXUnDW8",
"n2yWMtx8jVbo8wv9BK2eN1LdbaakgKL3Mt",
]
sorted_default = node.addmultisigaddress(2, addresses, None, 'legacy')
sorted_false = node.addmultisigaddress(2, addresses, {"sort": False}, 'legacy')
sorted_true = node.addmultisigaddress(2, addresses, {"sort": True}, 'legacy')
assert_equal(sorted_default, sorted_false)
assert_equal("2N6dne8yzh13wsRJxCcMgCYNeN9fxKWNHt8", sorted_default['address'])
assert_equal("2MsJ2YhGewgDPGEQk4vahGs4wRikJXpRRtU", sorted_true['address'])
sorted_default = node.addmultisigaddress(2, addresses, {'address_type': 'legacy'})
sorted_false = node.addmultisigaddress(2, addresses, {'address_type': 'legacy', "sort": False})
sorted_true = node.addmultisigaddress(2, addresses, {'address_type': 'legacy', "sort": True})
assert_equal(sorted_default, sorted_false)
assert_equal("2N6dne8yzh13wsRJxCcMgCYNeN9fxKWNHt8", sorted_default['address'])
assert_equal("2MsJ2YhGewgDPGEQk4vahGs4wRikJXpRRtU", sorted_true['address'])
assert_raises_rpc_error(-1, "address_type provided in both options and 4th parameter", node.addmultisigaddress, 2, addresses, {"address_type": 'legacy'}, 'bech32')
def test_sort_multisig_with_uncompressed_hash160(self, node):
node.importpubkey("02632b12f4ac5b1d1b72b2a3b508c19172de44f6f46bcee50ba33f3f9291e47ed0")
node.importpubkey("04dd4fe618a8ad14732f8172fe7c9c5e76dd18c2cc501ef7f86e0f4e285ca8b8b32d93df2f4323ebb02640fa6b975b2e63ab3c9d6979bc291193841332442cc6ad")
address = "2MxvEpFdXeEDbnz8MbRwS23kDZC8tzQ9NjK"
addresses = [
"msDoRfEfZQFaQNfAEWyqf69H99yntZoBbG",
"myrfasv56W7579LpepuRy7KFhVhaWsJYS8",
]
default = self.nodes[0].addmultisigaddress(2, addresses, {'address_type': 'legacy'})
assert_equal(address, default['address'])
unsorted = self.nodes[0].addmultisigaddress(2, addresses, {'address_type': 'legacy', "sort": False})
assert_equal(address, unsorted['address'])
assert_raises_rpc_error(-1, "Compressed key required for BIP67: myrfasv56W7579LpepuRy7KFhVhaWsJYS8", node.addmultisigaddress, 2, addresses, {"sort": True})
def run_test(self):
# Check that there's no UTXO on the node
node = self.nodes[0]
@ -188,6 +234,10 @@ class WalletLabelsTest(BitcoinTestFramework):
self.invalid_label_name_test()
if not self.options.descriptors:
self.test_sort_multisig(node)
self.test_sort_multisig_with_uncompressed_hash160(node)
if self.options.descriptors:
# This is a descriptor wallet test because of segwit v1+ addresses
self.log.info('Check watchonly labels')