Skip to content

Commit 940a219

Browse files
committed
SegWit wallet support
This introduces two command line flags (-addresstype and -changetype) which control the type of addresses/outputs created by the GUI and RPCs. Certain RPCs allow overriding these (`getnewaddress` and `getrawchangeaddress`). Supported types are "legacy" (P2PKH and P2SH-multisig), "p2sh-segwit" (P2SH-P2WPKH and P2SH-P2WSH-multisig), and "bech32" (P2WPKH and P2WSH-multisig). A few utility functions are added to the wallet to construct different address type and to add the necessary entries to the wallet file to be compatible with earlier versions (see `CWallet::LearnRelatedScripts`, `GetDestinationForKey`, `GetAllDestinationsForKey`, `CWallet::AddAndGetDestinationForScript`).
1 parent f37c64e commit 940a219

22 files changed

+278
-61
lines changed

src/qt/addresstablemodel.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,8 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con
384384
return QString();
385385
}
386386
}
387-
strAddress = EncodeDestination(newKey.GetID());
387+
wallet->LearnRelatedScripts(newKey, g_address_type);
388+
strAddress = EncodeDestination(GetDestinationForKey(newKey, g_address_type));
388389
}
389390
else
390391
{

src/qt/paymentserver.cpp

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -636,27 +636,24 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, const SendCoinsRecipient& r
636636
// Create a new refund address, or re-use:
637637
QString account = tr("Refund from %1").arg(recipient.authenticatedMerchant);
638638
std::string strAccount = account.toStdString();
639-
std::set<CTxDestination> refundAddresses = wallet->GetAccountAddresses(strAccount);
640-
if (!refundAddresses.empty()) {
641-
CScript s = GetScriptForDestination(*refundAddresses.begin());
639+
CPubKey newKey;
640+
if (wallet->GetKeyFromPool(newKey)) {
641+
// BIP70 requests encode the scriptPubKey directly, so we are not restricted to address
642+
// types supported by the receiver. As a result, we choose the address format we also
643+
// use for change. Despite an actual payment and not change, this is a close match:
644+
// it's the output type we use subject to privacy issues, but not restricted by what
645+
// other software supports.
646+
wallet->LearnRelatedScripts(newKey, g_change_type);
647+
CTxDestination dest = GetDestinationForKey(newKey, g_change_type);
648+
wallet->SetAddressBook(dest, strAccount, "refund");
649+
650+
CScript s = GetScriptForDestination(dest);
642651
payments::Output* refund_to = payment.add_refund_to();
643652
refund_to->set_script(&s[0], s.size());
644-
}
645-
else {
646-
CPubKey newKey;
647-
if (wallet->GetKeyFromPool(newKey)) {
648-
CKeyID keyID = newKey.GetID();
649-
wallet->SetAddressBook(keyID, strAccount, "refund");
650-
651-
CScript s = GetScriptForDestination(keyID);
652-
payments::Output* refund_to = payment.add_refund_to();
653-
refund_to->set_script(&s[0], s.size());
654-
}
655-
else {
656-
// This should never happen, because sending coins should have
657-
// just unlocked the wallet and refilled the keypool.
658-
qWarning() << "PaymentServer::fetchPaymentACK: Error getting refund key, refund_to not set";
659-
}
653+
} else {
654+
// This should never happen, because sending coins should have
655+
// just unlocked the wallet and refilled the keypool.
656+
qWarning() << "PaymentServer::fetchPaymentACK: Error getting refund key, refund_to not set";
660657
}
661658

662659
int length = payment.ByteSize();

src/qt/test/wallettests.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, st
149149
// src/qt/test/test_bitcoin-qt -platform cocoa # macOS
150150
void TestGUI()
151151
{
152+
g_address_type = OUTPUT_TYPE_P2SH_SEGWIT;
153+
g_change_type = OUTPUT_TYPE_P2SH_SEGWIT;
154+
152155
// Set up wallet and chain with 105 blocks (5 mature blocks for spending).
153156
TestChain100Setup test;
154157
for (int i = 0; i < 5; ++i) {
@@ -161,7 +164,7 @@ void TestGUI()
161164
wallet.LoadWallet(firstRun);
162165
{
163166
LOCK(wallet.cs_wallet);
164-
wallet.SetAddressBook(test.coinbaseKey.GetPubKey().GetID(), "", "receive");
167+
wallet.SetAddressBook(GetDestinationForKey(test.coinbaseKey.GetPubKey(), g_address_type), "", "receive");
165168
wallet.AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey());
166169
}
167170
{

src/wallet/init.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
std::string GetWalletHelpString(bool showDebug)
1717
{
1818
std::string strUsage = HelpMessageGroup(_("Wallet options:"));
19+
strUsage += HelpMessageOpt("-addresstype", strprintf(_("What type of addresses to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default: \"%s\")"), FormatOutputType(OUTPUT_TYPE_DEFAULT)));
20+
strUsage += HelpMessageOpt("-changetype", _("What type of change to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default is same as -addresstype)"));
1921
strUsage += HelpMessageOpt("-disablewallet", _("Do not load the wallet and disable wallet RPC calls"));
2022
strUsage += HelpMessageOpt("-keypool=<n>", strprintf(_("Set key pool size to <n> (default: %u)"), DEFAULT_KEYPOOL_SIZE));
2123
strUsage += HelpMessageOpt("-fallbackfee=<amt>", strprintf(_("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)"),
@@ -175,6 +177,16 @@ bool WalletParameterInteraction()
175177
bSpendZeroConfChange = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE);
176178
fWalletRbf = gArgs.GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF);
177179

180+
g_address_type = ParseOutputType(gArgs.GetArg("-addresstype", ""));
181+
if (g_address_type == OUTPUT_TYPE_NONE) {
182+
return InitError(strprintf(_("Unknown address type '%s'"), gArgs.GetArg("-addresstype", "")));
183+
}
184+
185+
g_change_type = ParseOutputType(gArgs.GetArg("-changetype", ""), g_address_type);
186+
if (g_change_type == OUTPUT_TYPE_NONE) {
187+
return InitError(strprintf(_("Unknown change type '%s'"), gArgs.GetArg("-changetype", "")));
188+
}
189+
178190
return true;
179191
}
180192

src/wallet/rpcdump.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,11 @@ UniValue importprivkey(const JSONRPCRequest& request)
131131
CKeyID vchAddress = pubkey.GetID();
132132
{
133133
pwallet->MarkDirty();
134-
pwallet->SetAddressBook(vchAddress, strLabel, "receive");
134+
135+
// We don't know which corresponding address will be used; label them all
136+
for (const auto& dest : GetAllDestinationsForKey(pubkey)) {
137+
pwallet->SetAddressBook(dest, strLabel, "receive");
138+
}
135139

136140
// Don't throw error in case a key is already there
137141
if (pwallet->HaveKey(vchAddress)) {
@@ -143,6 +147,7 @@ UniValue importprivkey(const JSONRPCRequest& request)
143147
if (!pwallet->AddKeyPubKey(key, pubkey)) {
144148
throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
145149
}
150+
pwallet->LearnAllRelatedScripts(pubkey);
146151

147152
// whenever a key is imported, we need to scan the whole chain
148153
pwallet->UpdateTimeFirstKey(1);
@@ -433,8 +438,11 @@ UniValue importpubkey(const JSONRPCRequest& request)
433438

434439
LOCK2(cs_main, pwallet->cs_wallet);
435440

436-
ImportAddress(pwallet, pubKey.GetID(), strLabel);
441+
for (const auto& dest : GetAllDestinationsForKey(pubKey)) {
442+
ImportAddress(pwallet, dest, strLabel);
443+
}
437444
ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false);
445+
pwallet->LearnAllRelatedScripts(pubKey);
438446

439447
if (fRescan)
440448
{

src/wallet/rpcwallet.cpp

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -136,14 +136,15 @@ UniValue getnewaddress(const JSONRPCRequest& request)
136136
return NullUniValue;
137137
}
138138

139-
if (request.fHelp || request.params.size() > 1)
139+
if (request.fHelp || request.params.size() > 2)
140140
throw std::runtime_error(
141-
"getnewaddress ( \"account\" )\n"
141+
"getnewaddress ( \"account\" \"address_type\" )\n"
142142
"\nReturns a new Bitcoin address for receiving payments.\n"
143143
"If 'account' is specified (DEPRECATED), it is added to the address book \n"
144144
"so payments received with the address will be credited to 'account'.\n"
145145
"\nArguments:\n"
146146
"1. \"account\" (string, optional) DEPRECATED. The account name for the address to be linked to. If not provided, the default account \"\" is used. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created if there is no account by the given name.\n"
147+
"2. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh\", and \"bech32\". Default is set by -addresstype.\n"
147148
"\nResult:\n"
148149
"\"address\" (string) The new bitcoin address\n"
149150
"\nExamples:\n"
@@ -158,6 +159,14 @@ UniValue getnewaddress(const JSONRPCRequest& request)
158159
if (!request.params[0].isNull())
159160
strAccount = AccountFromValue(request.params[0]);
160161

162+
OutputType output_type = g_address_type;
163+
if (!request.params[1].isNull()) {
164+
output_type = ParseOutputType(request.params[1].get_str(), g_address_type);
165+
if (output_type == OUTPUT_TYPE_NONE) {
166+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str()));
167+
}
168+
}
169+
161170
if (!pwallet->IsLocked()) {
162171
pwallet->TopUpKeyPool();
163172
}
@@ -167,11 +176,12 @@ UniValue getnewaddress(const JSONRPCRequest& request)
167176
if (!pwallet->GetKeyFromPool(newKey)) {
168177
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
169178
}
170-
CKeyID keyID = newKey.GetID();
179+
pwallet->LearnRelatedScripts(newKey, output_type);
180+
CTxDestination dest = GetDestinationForKey(newKey, output_type);
171181

172-
pwallet->SetAddressBook(keyID, strAccount, "receive");
182+
pwallet->SetAddressBook(dest, strAccount, "receive");
173183

174-
return EncodeDestination(keyID);
184+
return EncodeDestination(dest);
175185
}
176186

177187

@@ -226,11 +236,13 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request)
226236
return NullUniValue;
227237
}
228238

229-
if (request.fHelp || request.params.size() > 0)
239+
if (request.fHelp || request.params.size() > 1)
230240
throw std::runtime_error(
231-
"getrawchangeaddress\n"
241+
"getrawchangeaddress ( \"address_type\" )\n"
232242
"\nReturns a new Bitcoin address, for receiving change.\n"
233243
"This is for use with raw transactions, NOT normal use.\n"
244+
"\nArguments:\n"
245+
"1. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh\", and \"bech32\". Default is set by -changetype.\n"
234246
"\nResult:\n"
235247
"\"address\" (string) The address\n"
236248
"\nExamples:\n"
@@ -244,16 +256,25 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request)
244256
pwallet->TopUpKeyPool();
245257
}
246258

259+
OutputType output_type = g_change_type;
260+
if (!request.params[0].isNull()) {
261+
output_type = ParseOutputType(request.params[0].get_str(), g_change_type);
262+
if (output_type == OUTPUT_TYPE_NONE) {
263+
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
264+
}
265+
}
266+
247267
CReserveKey reservekey(pwallet);
248268
CPubKey vchPubKey;
249269
if (!reservekey.GetReservedKey(vchPubKey, true))
250270
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
251271

252272
reservekey.KeepKey();
253273

254-
CKeyID keyID = vchPubKey.GetID();
274+
pwallet->LearnRelatedScripts(vchPubKey, output_type);
275+
CTxDestination dest = GetDestinationForKey(vchPubKey, output_type);
255276

256-
return EncodeDestination(keyID);
277+
return EncodeDestination(dest);
257278
}
258279

259280

@@ -1184,11 +1205,12 @@ UniValue addmultisigaddress(const JSONRPCRequest& request)
11841205

11851206
// Construct using pay-to-script-hash:
11861207
CScript inner = _createmultisig_redeemScript(pwallet, request.params);
1187-
CScriptID innerID(inner);
11881208
pwallet->AddCScript(inner);
11891209

1190-
pwallet->SetAddressBook(innerID, strAccount, "send");
1191-
return EncodeDestination(innerID);
1210+
CTxDestination dest = pwallet->AddAndGetDestinationForScript(inner, g_address_type);
1211+
1212+
pwallet->SetAddressBook(dest, strAccount, "send");
1213+
return EncodeDestination(dest);
11921214
}
11931215

11941216
class Witnessifier : public boost::static_visitor<bool>
@@ -3446,8 +3468,8 @@ static const CRPCCommand commands[] =
34463468
{ "wallet", "getaccount", &getaccount, {"address"} },
34473469
{ "wallet", "getaddressesbyaccount", &getaddressesbyaccount, {"account"} },
34483470
{ "wallet", "getbalance", &getbalance, {"account","minconf","include_watchonly"} },
3449-
{ "wallet", "getnewaddress", &getnewaddress, {"account"} },
3450-
{ "wallet", "getrawchangeaddress", &getrawchangeaddress, {} },
3471+
{ "wallet", "getnewaddress", &getnewaddress, {"account","address_type"} },
3472+
{ "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} },
34513473
{ "wallet", "getreceivedbyaccount", &getreceivedbyaccount, {"account","minconf"} },
34523474
{ "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} },
34533475
{ "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} },

src/wallet/test/wallet_test_fixture.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ WalletTestingSetup::WalletTestingSetup(const std::string& chainName):
1313
bitdb.MakeMock();
1414

1515
bool fFirstRun;
16+
g_address_type = OUTPUT_TYPE_DEFAULT;
17+
g_change_type = OUTPUT_TYPE_DEFAULT;
1618
std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, "wallet_test.dat"));
1719
pwalletMain = MakeUnique<CWallet>(std::move(dbw));
1820
pwalletMain->LoadWallet(fFirstRun);

0 commit comments

Comments
 (0)