Skip to content

Commit

Permalink
SegWit wallet support
Browse files Browse the repository at this point in the history
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`).
  • Loading branch information
sipa committed Jan 9, 2018
1 parent f37c64e commit 940a219
Show file tree
Hide file tree
Showing 22 changed files with 278 additions and 61 deletions.
3 changes: 2 additions & 1 deletion src/qt/addresstablemodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,8 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con
return QString();
}
}
strAddress = EncodeDestination(newKey.GetID());
wallet->LearnRelatedScripts(newKey, g_address_type);
strAddress = EncodeDestination(GetDestinationForKey(newKey, g_address_type));
}
else
{
Expand Down
35 changes: 16 additions & 19 deletions src/qt/paymentserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -636,27 +636,24 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, const SendCoinsRecipient& r
// Create a new refund address, or re-use:
QString account = tr("Refund from %1").arg(recipient.authenticatedMerchant);
std::string strAccount = account.toStdString();
std::set<CTxDestination> refundAddresses = wallet->GetAccountAddresses(strAccount);
if (!refundAddresses.empty()) {
CScript s = GetScriptForDestination(*refundAddresses.begin());
CPubKey newKey;
if (wallet->GetKeyFromPool(newKey)) {
// BIP70 requests encode the scriptPubKey directly, so we are not restricted to address
// types supported by the receiver. As a result, we choose the address format we also
// use for change. Despite an actual payment and not change, this is a close match:
// it's the output type we use subject to privacy issues, but not restricted by what
// other software supports.
wallet->LearnRelatedScripts(newKey, g_change_type);
CTxDestination dest = GetDestinationForKey(newKey, g_change_type);
wallet->SetAddressBook(dest, strAccount, "refund");

CScript s = GetScriptForDestination(dest);
payments::Output* refund_to = payment.add_refund_to();
refund_to->set_script(&s[0], s.size());
}
else {
CPubKey newKey;
if (wallet->GetKeyFromPool(newKey)) {
CKeyID keyID = newKey.GetID();
wallet->SetAddressBook(keyID, strAccount, "refund");

CScript s = GetScriptForDestination(keyID);
payments::Output* refund_to = payment.add_refund_to();
refund_to->set_script(&s[0], s.size());
}
else {
// This should never happen, because sending coins should have
// just unlocked the wallet and refilled the keypool.
qWarning() << "PaymentServer::fetchPaymentACK: Error getting refund key, refund_to not set";
}
} else {
// This should never happen, because sending coins should have
// just unlocked the wallet and refilled the keypool.
qWarning() << "PaymentServer::fetchPaymentACK: Error getting refund key, refund_to not set";
}

int length = payment.ByteSize();
Expand Down
5 changes: 4 additions & 1 deletion src/qt/test/wallettests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, st
// src/qt/test/test_bitcoin-qt -platform cocoa # macOS
void TestGUI()
{
g_address_type = OUTPUT_TYPE_P2SH_SEGWIT;
g_change_type = OUTPUT_TYPE_P2SH_SEGWIT;

// Set up wallet and chain with 105 blocks (5 mature blocks for spending).
TestChain100Setup test;
for (int i = 0; i < 5; ++i) {
Expand All @@ -161,7 +164,7 @@ void TestGUI()
wallet.LoadWallet(firstRun);
{
LOCK(wallet.cs_wallet);
wallet.SetAddressBook(test.coinbaseKey.GetPubKey().GetID(), "", "receive");
wallet.SetAddressBook(GetDestinationForKey(test.coinbaseKey.GetPubKey(), g_address_type), "", "receive");
wallet.AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey());
}
{
Expand Down
12 changes: 12 additions & 0 deletions src/wallet/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
std::string GetWalletHelpString(bool showDebug)
{
std::string strUsage = HelpMessageGroup(_("Wallet options:"));
strUsage += HelpMessageOpt("-addresstype", strprintf(_("What type of addresses to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default: \"%s\")"), FormatOutputType(OUTPUT_TYPE_DEFAULT)));
strUsage += HelpMessageOpt("-changetype", _("What type of change to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default is same as -addresstype)"));
strUsage += HelpMessageOpt("-disablewallet", _("Do not load the wallet and disable wallet RPC calls"));
strUsage += HelpMessageOpt("-keypool=<n>", strprintf(_("Set key pool size to <n> (default: %u)"), DEFAULT_KEYPOOL_SIZE));
strUsage += HelpMessageOpt("-fallbackfee=<amt>", strprintf(_("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)"),
Expand Down Expand Up @@ -175,6 +177,16 @@ bool WalletParameterInteraction()
bSpendZeroConfChange = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE);
fWalletRbf = gArgs.GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF);

g_address_type = ParseOutputType(gArgs.GetArg("-addresstype", ""));
if (g_address_type == OUTPUT_TYPE_NONE) {
return InitError(strprintf(_("Unknown address type '%s'"), gArgs.GetArg("-addresstype", "")));
}

g_change_type = ParseOutputType(gArgs.GetArg("-changetype", ""), g_address_type);
if (g_change_type == OUTPUT_TYPE_NONE) {
return InitError(strprintf(_("Unknown change type '%s'"), gArgs.GetArg("-changetype", "")));
}

return true;
}

Expand Down
12 changes: 10 additions & 2 deletions src/wallet/rpcdump.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,11 @@ UniValue importprivkey(const JSONRPCRequest& request)
CKeyID vchAddress = pubkey.GetID();
{
pwallet->MarkDirty();
pwallet->SetAddressBook(vchAddress, strLabel, "receive");

// We don't know which corresponding address will be used; label them all
for (const auto& dest : GetAllDestinationsForKey(pubkey)) {
pwallet->SetAddressBook(dest, strLabel, "receive");
}

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

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

LOCK2(cs_main, pwallet->cs_wallet);

ImportAddress(pwallet, pubKey.GetID(), strLabel);
for (const auto& dest : GetAllDestinationsForKey(pubKey)) {
ImportAddress(pwallet, dest, strLabel);
}
ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false);
pwallet->LearnAllRelatedScripts(pubKey);

if (fRescan)
{
Expand Down
50 changes: 36 additions & 14 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,14 +136,15 @@ UniValue getnewaddress(const JSONRPCRequest& request)
return NullUniValue;
}

if (request.fHelp || request.params.size() > 1)
if (request.fHelp || request.params.size() > 2)
throw std::runtime_error(
"getnewaddress ( \"account\" )\n"
"getnewaddress ( \"account\" \"address_type\" )\n"
"\nReturns a new Bitcoin address for receiving payments.\n"
"If 'account' is specified (DEPRECATED), it is added to the address book \n"
"so payments received with the address will be credited to 'account'.\n"
"\nArguments:\n"
"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"
"2. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh\", and \"bech32\". Default is set by -addresstype.\n"
"\nResult:\n"
"\"address\" (string) The new bitcoin address\n"
"\nExamples:\n"
Expand All @@ -158,6 +159,14 @@ UniValue getnewaddress(const JSONRPCRequest& request)
if (!request.params[0].isNull())
strAccount = AccountFromValue(request.params[0]);

OutputType output_type = g_address_type;
if (!request.params[1].isNull()) {
output_type = ParseOutputType(request.params[1].get_str(), g_address_type);
if (output_type == OUTPUT_TYPE_NONE) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str()));
}
}

if (!pwallet->IsLocked()) {
pwallet->TopUpKeyPool();
}
Expand All @@ -167,11 +176,12 @@ UniValue getnewaddress(const JSONRPCRequest& request)
if (!pwallet->GetKeyFromPool(newKey)) {
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
}
CKeyID keyID = newKey.GetID();
pwallet->LearnRelatedScripts(newKey, output_type);
CTxDestination dest = GetDestinationForKey(newKey, output_type);

pwallet->SetAddressBook(keyID, strAccount, "receive");
pwallet->SetAddressBook(dest, strAccount, "receive");

return EncodeDestination(keyID);
return EncodeDestination(dest);
}


Expand Down Expand Up @@ -226,11 +236,13 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request)
return NullUniValue;
}

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

OutputType output_type = g_change_type;
if (!request.params[0].isNull()) {
output_type = ParseOutputType(request.params[0].get_str(), g_change_type);
if (output_type == OUTPUT_TYPE_NONE) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str()));
}
}

CReserveKey reservekey(pwallet);
CPubKey vchPubKey;
if (!reservekey.GetReservedKey(vchPubKey, true))
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");

reservekey.KeepKey();

CKeyID keyID = vchPubKey.GetID();
pwallet->LearnRelatedScripts(vchPubKey, output_type);
CTxDestination dest = GetDestinationForKey(vchPubKey, output_type);

return EncodeDestination(keyID);
return EncodeDestination(dest);
}


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

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

pwallet->SetAddressBook(innerID, strAccount, "send");
return EncodeDestination(innerID);
CTxDestination dest = pwallet->AddAndGetDestinationForScript(inner, g_address_type);

pwallet->SetAddressBook(dest, strAccount, "send");
return EncodeDestination(dest);
}

class Witnessifier : public boost::static_visitor<bool>
Expand Down Expand Up @@ -3446,8 +3468,8 @@ static const CRPCCommand commands[] =
{ "wallet", "getaccount", &getaccount, {"address"} },
{ "wallet", "getaddressesbyaccount", &getaddressesbyaccount, {"account"} },
{ "wallet", "getbalance", &getbalance, {"account","minconf","include_watchonly"} },
{ "wallet", "getnewaddress", &getnewaddress, {"account"} },
{ "wallet", "getrawchangeaddress", &getrawchangeaddress, {} },
{ "wallet", "getnewaddress", &getnewaddress, {"account","address_type"} },
{ "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} },
{ "wallet", "getreceivedbyaccount", &getreceivedbyaccount, {"account","minconf"} },
{ "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} },
{ "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} },
Expand Down
2 changes: 2 additions & 0 deletions src/wallet/test/wallet_test_fixture.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ WalletTestingSetup::WalletTestingSetup(const std::string& chainName):
bitdb.MakeMock();

bool fFirstRun;
g_address_type = OUTPUT_TYPE_DEFAULT;
g_change_type = OUTPUT_TYPE_DEFAULT;
std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, "wallet_test.dat"));
pwalletMain = MakeUnique<CWallet>(std::move(dbw));
pwalletMain->LoadWallet(fFirstRun);
Expand Down
Loading

0 comments on commit 940a219

Please sign in to comment.