Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for SegWit Addresses in RPC calls and change addresses #11177

Closed
wants to merge 7 commits into from
1 change: 1 addition & 0 deletions src/init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,7 @@ std::string HelpMessage(HelpMessageMode mode)
strUsage += HelpMessageOpt("-limitdescendantcount=<n>", strprintf("Do not accept transactions if any ancestor would have <n> or more in-mempool descendants (default: %u)", DEFAULT_DESCENDANT_LIMIT));
strUsage += HelpMessageOpt("-limitdescendantsize=<n>", strprintf("Do not accept transactions if any ancestor would have more than <n> kilobytes of in-mempool descendants (default: %u).", DEFAULT_DESCENDANT_SIZE_LIMIT));
strUsage += HelpMessageOpt("-vbparams=deployment:start:end", "Use given start/end times for specified version bits deployment (regtest-only)");
strUsage += HelpMessageOpt("-defaultwitnessaddress", "Return witness addresses by default in RPC calls (default: false)");
}
strUsage += HelpMessageOpt("-debug=<category>", strprintf(_("Output debugging information (default: %u, supplying <category> is optional)"), 0) + ". " +
_("If <category> is not supplied or if <category> = 1, output all debugging information.") + " " + _("<category> can be:") + " " + ListLogCategories() + ".");
Expand Down
2 changes: 2 additions & 0 deletions src/rpc/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ static const CRPCConvertParam vRPCConvertParams[] =
{ "estimaterawfee", 1, "threshold" },
{ "prioritisetransaction", 1, "dummy" },
{ "prioritisetransaction", 2, "fee_delta" },
{ "getaccountaddress", 1, "witness" },
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This call is deprecated. Don't change it.

{ "getnewaddress", 1, "witness" },
{ "setban", 2, "bantime" },
{ "setban", 3, "absolute" },
{ "setnetworkactive", 0, "state" },
Expand Down
190 changes: 115 additions & 75 deletions src/wallet/rpcwallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,78 @@ CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest& request)
return ::vpwallets.size() == 1 || (request.fHelp && ::vpwallets.size() > 0) ? ::vpwallets[0] : nullptr;
}

class Witnessifier : public boost::static_visitor<bool>
{
public:
CWallet * const pwallet;
CScriptID result;

Witnessifier(CWallet *_pwallet) : pwallet(_pwallet) {}

bool operator()(const CNoDestination &dest) const { return false; }

bool operator()(const CKeyID &keyID) {
if (pwallet) {
CScript basescript = GetScriptForDestination(keyID);
CScript witscript = GetScriptForWitness(basescript);
SignatureData sigs;
// This check is to make sure that the script we created can actually be solved for and signed by us
// if we were to have the private keys. This is just to make sure that the script is valid and that,
// if found in a transaction, we would still accept and relay that transcation.
if (!ProduceSignature(DummySignatureCreator(pwallet), witscript, sigs) ||
!VerifyScript(sigs.scriptSig, witscript, &sigs.scriptWitness, MANDATORY_SCRIPT_VERIFY_FLAGS | SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, DummySignatureCreator(pwallet).Checker())) {
return false;
}
pwallet->AddCScript(witscript);
result = CScriptID(witscript);
return true;
}
return false;
}

bool operator()(const CScriptID &scriptID) {
CScript subscript;
if (pwallet && pwallet->GetCScript(scriptID, subscript)) {
int witnessversion;
std::vector<unsigned char> witprog;
if (subscript.IsWitnessProgram(witnessversion, witprog)) {
result = scriptID;
return true;
}
CScript witscript = GetScriptForWitness(subscript);
SignatureData sigs;
// This check is to make sure that the script we created can actually be solved for and signed by us
// if we were to have the private keys. This is just to make sure that the script is valid and that,
// if found in a transaction, we would still accept and relay that transcation.
if (!ProduceSignature(DummySignatureCreator(pwallet), witscript, sigs) ||
!VerifyScript(sigs.scriptSig, witscript, &sigs.scriptWitness, MANDATORY_SCRIPT_VERIFY_FLAGS | SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, DummySignatureCreator(pwallet).Checker())) {
return false;
}
pwallet->AddCScript(witscript);
result = CScriptID(witscript);
return true;
}
return false;
}
};

CScriptID GetWitnessScriptFromAddress(CWallet * const pwallet, CBitcoinAddress addr)
{
Witnessifier w(pwallet);
CTxDestination dest = addr.Get();
if (!boost::apply_visitor(w, dest))
throw JSONRPCError(RPC_WALLET_ERROR, "Public key or redeemscript not known to wallet, or the key is uncompressed");
return w.result;
}

CBitcoinAddress WitnessifyBitcoinAddress(CWallet* const pwallet, std::string strAccount, CBitcoinAddress addr)
{
Witnessifier w(pwallet);
CScriptID wscript = GetWitnessScriptFromAddress(pwallet, addr);
pwallet->SetAddressBook(wscript, strAccount, "receive");
return CBitcoinAddress(wscript);
}

std::string HelpRequiringPassphrase(CWallet * const pwallet)
{
return pwallet && pwallet->IsCrypted()
Expand Down Expand Up @@ -134,19 +206,22 @@ 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\" \"witness\" )\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. \"witness\" (boolean, optional, default=false) Return a witness address"
"\nResult:\n"
"\"address\" (string) The new bitcoin address\n"
"\nExamples:\n"
+ HelpExampleCli("getnewaddress", "")
+ HelpExampleCli("getnewaddress", "\"account\" false")
+ HelpExampleRpc("getnewaddress", "")
+ HelpExampleRpc("getnewaddress", "\"account\" false")
);

LOCK2(cs_main, pwallet->cs_wallet);
Expand All @@ -166,12 +241,20 @@ UniValue getnewaddress(const JSONRPCRequest& request)
throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first");
}
CKeyID keyID = newKey.GetID();
CBitcoinAddress addr = CBitcoinAddress(keyID);

pwallet->SetAddressBook(keyID, strAccount, "receive");
bool witnessify = gArgs.GetBoolArg("-defaultwitnessaddress", false);
if (!request.params[1].isNull()) {
witnessify = request.params[1].get_bool();
}

return CBitcoinAddress(keyID).ToString();
}
if (witnessify) {
return WitnessifyBitcoinAddress(pwallet, strAccount, addr).ToString();
}

return addr.ToString();
}

CBitcoinAddress GetAccountAddress(CWallet* const pwallet, std::string strAccount, bool bForceNew=false)
{
Expand All @@ -190,19 +273,22 @@ UniValue getaccountaddress(const JSONRPCRequest& request)
return NullUniValue;
}

if (request.fHelp || request.params.size() != 1)
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
throw std::runtime_error(
"getaccountaddress \"account\"\n"
"getaccountaddress \"account\" \"witness\"\n"
"\nDEPRECATED. Returns the current Bitcoin address for receiving payments to this account.\n"
"\nArguments:\n"
"1. \"account\" (string, required) The account name for the address. 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 and a new address created if there is no account by the given name.\n"
"2. \"witness\" (boolean, optional, default=false) Return a witness address\n"
"\nResult:\n"
"\"address\" (string) The account bitcoin address\n"
"\nExamples:\n"
+ HelpExampleCli("getaccountaddress", "")
+ HelpExampleCli("getaccountaddress", "\"\"")
+ HelpExampleCli("getaccountaddress", "\"myaccount\"")
+ HelpExampleCli("getaccountaddress", "\"myaccount\" true")
+ HelpExampleRpc("getaccountaddress", "\"myaccount\"")
+ HelpExampleRpc("getaccountaddress", "\"myaccount\", true")
);

LOCK2(cs_main, pwallet->cs_wallet);
Expand All @@ -212,7 +298,16 @@ UniValue getaccountaddress(const JSONRPCRequest& request)

UniValue ret(UniValue::VSTR);

ret = GetAccountAddress(pwallet, strAccount).ToString();
CBitcoinAddress addr = GetAccountAddress(pwallet, strAccount);
bool witnessify = gArgs.GetBoolArg("-defaultwitnessaddress", false);
if (!request.params[1].isNull()) {
witnessify = request.params[1].get_bool();
}

if (witnessify) {
return WitnessifyBitcoinAddress(pwallet, strAccount, addr).ToString();
}
ret = addr.ToString();
return ret;
}

Expand Down Expand Up @@ -1131,76 +1226,22 @@ UniValue addmultisigaddress(const JSONRPCRequest& request)
return CBitcoinAddress(innerID).ToString();
}

class Witnessifier : public boost::static_visitor<bool>
{
public:
CWallet * const pwallet;
CScriptID result;

explicit Witnessifier(CWallet *_pwallet) : pwallet(_pwallet) {}

bool operator()(const CNoDestination &dest) const { return false; }

bool operator()(const CKeyID &keyID) {
if (pwallet) {
CScript basescript = GetScriptForDestination(keyID);
CScript witscript = GetScriptForWitness(basescript);
SignatureData sigs;
// This check is to make sure that the script we created can actually be solved for and signed by us
// if we were to have the private keys. This is just to make sure that the script is valid and that,
// if found in a transaction, we would still accept and relay that transaction.
if (!ProduceSignature(DummySignatureCreator(pwallet), witscript, sigs) ||
!VerifyScript(sigs.scriptSig, witscript, &sigs.scriptWitness, MANDATORY_SCRIPT_VERIFY_FLAGS | SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, DummySignatureCreator(pwallet).Checker())) {
return false;
}
pwallet->AddCScript(witscript);
result = CScriptID(witscript);
return true;
}
return false;
}

bool operator()(const CScriptID &scriptID) {
CScript subscript;
if (pwallet && pwallet->GetCScript(scriptID, subscript)) {
int witnessversion;
std::vector<unsigned char> witprog;
if (subscript.IsWitnessProgram(witnessversion, witprog)) {
result = scriptID;
return true;
}
CScript witscript = GetScriptForWitness(subscript);
SignatureData sigs;
// This check is to make sure that the script we created can actually be solved for and signed by us
// if we were to have the private keys. This is just to make sure that the script is valid and that,
// if found in a transaction, we would still accept and relay that transaction.
if (!ProduceSignature(DummySignatureCreator(pwallet), witscript, sigs) ||
!VerifyScript(sigs.scriptSig, witscript, &sigs.scriptWitness, MANDATORY_SCRIPT_VERIFY_FLAGS | SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, DummySignatureCreator(pwallet).Checker())) {
return false;
}
pwallet->AddCScript(witscript);
result = CScriptID(witscript);
return true;
}
return false;
}
};

UniValue addwitnessaddress(const JSONRPCRequest& request)
{
CWallet * const pwallet = GetWalletForJSONRPCRequest(request);
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}

if (request.fHelp || request.params.size() < 1 || request.params.size() > 1)
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
{
std::string msg = "addwitnessaddress \"address\"\n"
std::string msg = "addwitnessaddress \"address\" \"label\"\n"
"\nAdd a witness address for a script (with pubkey or redeemscript known).\n"
"It returns the witness script.\n"

"\nArguments:\n"
"1. \"address\" (string, required) An address known to the wallet\n"
"2. \"label\" (string, optional) A comment for the address if any\n"

"\nResult:\n"
"\"witnessaddress\", (string) The value of the new address (P2SH of witness script).\n"
Expand All @@ -1220,16 +1261,15 @@ UniValue addwitnessaddress(const JSONRPCRequest& request)
if (!address.IsValid())
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");

Witnessifier w(pwallet);
CTxDestination dest = address.Get();
bool ret = boost::apply_visitor(w, dest);
if (!ret) {
throw JSONRPCError(RPC_WALLET_ERROR, "Public key or redeemscript not known to wallet, or the key is uncompressed");
}
CScriptID wresult = GetWitnessScriptFromAddress(pwallet, address);

std::string strLabel;
if (request.params.size() >= 2)
strLabel = request.params[1].get_str();

pwallet->SetAddressBook(w.result, "", "receive");
pwallet->SetAddressBook(wresult, strLabel, "receive");

return CBitcoinAddress(w.result).ToString();
return CBitcoinAddress(wresult).ToString();
}

struct tallyitem
Expand Down Expand Up @@ -3166,17 +3206,17 @@ static const CRPCCommand commands[] =
{ "wallet", "abandontransaction", &abandontransaction, false, {"txid"} },
{ "wallet", "abortrescan", &abortrescan, false, {} },
{ "wallet", "addmultisigaddress", &addmultisigaddress, true, {"nrequired","keys","account"} },
{ "wallet", "addwitnessaddress", &addwitnessaddress, true, {"address"} },
{ "wallet", "addwitnessaddress", &addwitnessaddress, true, {"address", "label"} },
{ "wallet", "backupwallet", &backupwallet, true, {"destination"} },
{ "wallet", "bumpfee", &bumpfee, true, {"txid", "options"} },
{ "wallet", "dumpprivkey", &dumpprivkey, true, {"address"} },
{ "wallet", "dumpwallet", &dumpwallet, true, {"filename"} },
{ "wallet", "encryptwallet", &encryptwallet, true, {"passphrase"} },
{ "wallet", "getaccountaddress", &getaccountaddress, true, {"account"} },
{ "wallet", "getaccountaddress", &getaccountaddress, true, {"account", "witness"} },
{ "wallet", "getaccount", &getaccount, true, {"address"} },
{ "wallet", "getaddressesbyaccount", &getaddressesbyaccount, true, {"account"} },
{ "wallet", "getbalance", &getbalance, false, {"account","minconf","include_watchonly"} },
{ "wallet", "getnewaddress", &getnewaddress, true, {"account"} },
{ "wallet", "getnewaddress", &getnewaddress, true, {"account", "witness"} },
{ "wallet", "getrawchangeaddress", &getrawchangeaddress, true, {} },
{ "wallet", "getreceivedbyaccount", &getreceivedbyaccount, false, {"account","minconf"} },
{ "wallet", "getreceivedbyaddress", &getreceivedbyaddress, false, {"address","minconf"} },
Expand Down
8 changes: 7 additions & 1 deletion src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -804,11 +804,12 @@ bool CWallet::GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bFo
else {
// Check if the current key has been used
CScript scriptPubKey = GetScriptForDestination(account.vchPubKey.GetID());
CScript scriptWitness = GetScriptForDestination(GetScriptForWitness(scriptPubKey));
for (std::map<uint256, CWalletTx>::iterator it = mapWallet.begin();
it != mapWallet.end() && account.vchPubKey.IsValid();
++it)
for (const CTxOut& txout : (*it).second.tx->vout)
if (txout.scriptPubKey == scriptPubKey) {
if (txout.scriptPubKey == scriptPubKey || txout.scriptPubKey == scriptWitness) {
bForceNew = true;
break;
}
Expand Down Expand Up @@ -2648,6 +2649,11 @@ bool CWallet::CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletT
}

scriptChange = GetScriptForDestination(vchPubKey.GetID());
if (gArgs.GetBoolArg("-defaultwitnessaddress", false)) {
scriptChange = GetScriptForWitness(scriptChange);
this->AddCScript(scriptChange);
scriptChange = GetScriptForDestination(scriptChange);
}
}
CTxOut change_prototype_txout(0, scriptChange);
size_t change_prototype_size = GetSerializeSize(change_prototype_txout, SER_DISK, 0);
Expand Down