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 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 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 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 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): * [`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)). 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" }, { "dumptxoutset", 2, "show_header" },
{ "addmultisigaddress", 0, "nrequired" }, { "addmultisigaddress", 0, "nrequired" },
{ "addmultisigaddress", 1, "keys" }, { "addmultisigaddress", 1, "keys" },
{ "addmultisigaddress", 2, "options" },
{ "addmultisigaddress", 2, "sort" },
{ "createmultisig", 0, "nrequired" }, { "createmultisig", 0, "nrequired" },
{ "createmultisig", 1, "keys" }, { "createmultisig", 1, "keys" },
{ "createmultisig", 2, "options" },
{ "createmultisig", 2, "sort" },
{ "listunspent", 0, "minconf" }, { "listunspent", 0, "minconf" },
{ "listunspent", 1, "maxconf" }, { "listunspent", 1, "maxconf" },
{ "listunspent", 2, "addresses" }, { "listunspent", 2, "addresses" },

View File

@ -89,14 +89,20 @@ static RPCHelpMan createmultisig()
{ {
return RPCHelpMan{"createmultisig", return RPCHelpMan{"createmultisig",
"\nCreates a multi-signature address with n signature of m keys required.\n" "\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."}, {"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.", {"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"}, {"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{
RPCResult::Type::OBJ, "", "", RPCResult::Type::OBJ, "", "",
@ -120,33 +126,59 @@ static RPCHelpMan createmultisig()
{ {
int required = request.params[0].getInt<int>(); 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 // Get the public keys
const UniValue& keys = request.params[1].get_array(); const UniValue& keys = request.params[1].get_array();
std::vector<CPubKey> pubkeys; std::vector<CPubKey> pubkeys;
for (unsigned int i = 0; i < keys.size(); ++i) { 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)) { 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())); 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 { } else {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid public key: %s\n.", keys[i].get_str())); 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: // Construct using pay-to-script-hash:
FillableSigningProvider keystore; FillableSigningProvider keystore;
CScript inner; 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 // Make the descriptor
std::unique_ptr<Descriptor> descriptor = InferDescriptor(GetScriptForDestination(dest), keystore); 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 // 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 // Gather public keys
if (required < 1) { 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)); 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 // Check if any keys are uncompressed. If so, the type is legacy
for (const CPubKey& pk : pubkeys) { 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 HexToPubKey(const std::string& hex_in);
CPubKey AddrToPubKey(const FillableSigningProvider& keystore, const std::string& addr_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); 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; 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; script << nRequired;
for (const CPubKey& key : keys) for (const std::vector<unsigned char>& bytes : vEncoded) {
script << ToByteVector(key); script << bytes;
}
script << keys.size() << OP_CHECKMULTISIG; script << keys.size() << OP_CHECKMULTISIG;
return script; 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); std::optional<std::pair<int, std::vector<Span<const unsigned char>>>> MatchMultiA(const CScript& script LIFETIMEBOUND);
/** Generate a multisig script. */ /** 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 #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" "This functionality is only intended for use with non-watchonly addresses.\n"
"See `importaddress` for watchonly p2sh address support.\n" "See `importaddress` for watchonly p2sh address support.\n"
"If 'label' is specified, assign address to that label.\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", "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."}, {"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"}, {"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."}, {"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\"."}, {
{"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{
RPCResult::Type::OBJ, "", "", RPCResult::Type::OBJ, "", "",
@ -263,10 +270,47 @@ RPCHelpMan addmultisigaddress()
LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore); LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
const std::string label{LabelFromValue(request.params[2])};
int required = request.params[0].getInt<int>(); 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 // Get the public keys
const UniValue& keys_or_addrs = request.params[1].get_array(); const UniValue& keys_or_addrs = request.params[1].get_array();
std::vector<CPubKey> pubkeys; std::vector<CPubKey> pubkeys;
@ -276,9 +320,11 @@ RPCHelpMan addmultisigaddress()
} else { } else {
pubkeys.push_back(AddrToPubKey(spk_man, keys_or_addrs[i].get_str())); 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()) { if (!request.params[3].isNull()) {
std::optional<OutputType> parsed = ParseOutputType(request.params[3].get_str()); std::optional<OutputType> parsed = ParseOutputType(request.params[3].get_str());
if (!parsed) { if (!parsed) {
@ -291,7 +337,7 @@ RPCHelpMan addmultisigaddress()
// Construct using pay-to-script-hash: // Construct using pay-to-script-hash:
CScript inner; 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); pwallet->SetAddressBook(dest, label, AddressPurpose::SEND);
// Make the descriptor // 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() wallet_info = self.getwalletinfo()
if 'descriptors' not in wallet_info or ('descriptors' in wallet_info and not wallet_info['descriptors']): 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) 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) cms = self.createmultisig(nrequired, keys, address_type)
req = [{ req = [{
'desc': cms['descriptor'], 'desc': cms['descriptor'],

View File

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

View File

@ -66,6 +66,52 @@ class WalletLabelsTest(BitcoinTestFramework):
for rpc_call in rpc_calls: for rpc_call in rpc_calls:
assert_raises_rpc_error(-11, "Invalid label name", *rpc_call, "*") 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): def run_test(self):
# Check that there's no UTXO on the node # Check that there's no UTXO on the node
node = self.nodes[0] node = self.nodes[0]
@ -188,6 +234,10 @@ class WalletLabelsTest(BitcoinTestFramework):
self.invalid_label_name_test() 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: if self.options.descriptors:
# This is a descriptor wallet test because of segwit v1+ addresses # This is a descriptor wallet test because of segwit v1+ addresses
self.log.info('Check watchonly labels') self.log.info('Check watchonly labels')