aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/rpcwallet.cpp328
-rw-r--r--src/wallet/rpcwallet.h3
2 files changed, 331 insertions, 0 deletions
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 7cd9f4cfd3..6d2499ee8a 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -4390,6 +4390,332 @@ UniValue sethdseed(const JSONRPCRequest& request)
return NullUniValue;
}
+bool ParseHDKeypath(std::string keypath_str, std::vector<uint32_t>& keypath)
+{
+ std::stringstream ss(keypath_str);
+ std::string item;
+ bool first = true;
+ while (std::getline(ss, item, '/')) {
+ if (item.compare("m") == 0) {
+ if (first) {
+ first = false;
+ continue;
+ }
+ return false;
+ }
+ // Finds whether it is hardened
+ uint32_t path = 0;
+ size_t pos = item.find("'");
+ if (pos != std::string::npos) {
+ // The hardened tick can only be in the last index of the string
+ if (pos != item.size() - 1) {
+ return false;
+ }
+ path |= 0x80000000;
+ item = item.substr(0, item.size() - 1); // Drop the last character which is the hardened tick
+ }
+
+ // Ensure this is only numbers
+ if (item.find_first_not_of( "0123456789" ) != std::string::npos) {
+ return false;
+ }
+ uint32_t number;
+ ParseUInt32(item, &number);
+ path |= number;
+
+ keypath.push_back(path);
+ first = false;
+ }
+ return true;
+}
+
+void AddKeypathToMap(const CWallet* pwallet, const CKeyID& keyID, std::map<CPubKey, std::vector<uint32_t>>& hd_keypaths)
+{
+ CPubKey vchPubKey;
+ if (!pwallet->GetPubKey(keyID, vchPubKey)) {
+ return;
+ }
+ CKeyMetadata meta;
+ auto it = pwallet->mapKeyMetadata.find(keyID);
+ if (it != pwallet->mapKeyMetadata.end()) {
+ meta = it->second;
+ }
+ std::vector<uint32_t> keypath;
+ if (!meta.hdKeypath.empty()) {
+ if (!ParseHDKeypath(meta.hdKeypath, keypath)) {
+ throw JSONRPCError(RPC_INTERNAL_ERROR, "Internal keypath is broken");
+ }
+ // Get the proper master key id
+ CKey key;
+ pwallet->GetKey(meta.hd_seed_id, key);
+ CExtKey masterKey;
+ masterKey.SetSeed(key.begin(), key.size());
+ // Add to map
+ keypath.insert(keypath.begin(), ReadLE32(masterKey.key.GetPubKey().GetID().begin()));
+ } else { // Single pubkeys get the master fingerprint of themselves
+ keypath.insert(keypath.begin(), ReadLE32(vchPubKey.GetID().begin()));
+ }
+ hd_keypaths.emplace(vchPubKey, keypath);
+}
+
+bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const CTransaction* txConst, int sighash_type, bool sign, bool bip32derivs)
+{
+ LOCK(pwallet->cs_wallet);
+ // Get all of the previous transactions
+ bool complete = true;
+ for (unsigned int i = 0; i < txConst->vin.size(); ++i) {
+ const CTxIn& txin = txConst->vin[i];
+ PSBTInput& input = psbtx.inputs.at(i);
+
+ // If we don't know about this input, skip it and let someone else deal with it
+ const uint256& txhash = txin.prevout.hash;
+ const auto& it = pwallet->mapWallet.find(txhash);
+ if (it != pwallet->mapWallet.end()) {
+ const CWalletTx& wtx = it->second;
+ CTxOut utxo = wtx.tx->vout[txin.prevout.n];
+ input.non_witness_utxo = wtx.tx;
+ input.witness_utxo = utxo;
+ }
+
+ // Get the Sighash type
+ if (sign && input.sighash_type > 0 && input.sighash_type != sighash_type) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Specified Sighash and sighash in PSBT do not match.");
+ }
+
+ SignatureData sigdata;
+ if (sign) {
+ complete &= SignPSBTInput(*pwallet, *psbtx.tx, input, sigdata, i, sighash_type);
+ } else {
+ complete &= SignPSBTInput(PublicOnlySigningProvider(pwallet), *psbtx.tx, input, sigdata, i, sighash_type);
+ }
+
+ // Drop the unnecessary UTXO
+ if (sigdata.witness) {
+ input.non_witness_utxo = nullptr;
+ } else {
+ input.witness_utxo.SetNull();
+ }
+
+ // Get public key paths
+ if (bip32derivs) {
+ for (const auto& pubkey_it : sigdata.misc_pubkeys) {
+ AddKeypathToMap(pwallet, pubkey_it.first, input.hd_keypaths);
+ }
+ }
+ }
+
+ // Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change
+ for (unsigned int i = 0; i < txConst->vout.size(); ++i) {
+ const CTxOut& out = txConst->vout.at(i);
+ PSBTOutput& psbt_out = psbtx.outputs.at(i);
+
+ // Dummy tx so we can use ProduceSignature to get stuff out
+ CMutableTransaction dummy_tx;
+ dummy_tx.vin.push_back(CTxIn());
+ dummy_tx.vout.push_back(CTxOut());
+
+ // Fill a SignatureData with output info
+ SignatureData sigdata;
+ psbt_out.FillSignatureData(sigdata);
+
+ MutableTransactionSignatureCreator creator(psbtx.tx.get_ptr(), 0, out.nValue, 1);
+ ProduceSignature(*pwallet, creator, out.scriptPubKey, sigdata);
+ psbt_out.FromSignatureData(sigdata);
+
+ // Get public key paths
+ if (bip32derivs) {
+ for (const auto& pubkey_it : sigdata.misc_pubkeys) {
+ AddKeypathToMap(pwallet, pubkey_it.first, psbt_out.hd_keypaths);
+ }
+ }
+ }
+ return complete;
+}
+
+UniValue walletprocesspsbt(const JSONRPCRequest& request)
+{
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
+ return NullUniValue;
+ }
+
+ if (request.fHelp || request.params.size() < 1 || request.params.size() > 4)
+ throw std::runtime_error(
+ "walletprocesspsbt \"psbt\" ( sign \"sighashtype\" bip32derivs )\n"
+ "\nUpdate a PSBT with input information from our wallet and then sign inputs\n"
+ "that we can sign for.\n"
+ + HelpRequiringPassphrase(pwallet) + "\n"
+
+ "\nArguments:\n"
+ "1. \"psbt\" (string, required) The transaction base64 string\n"
+ "2. sign (boolean, optional, default=true) Also sign the transaction when updating\n"
+ "3. \"sighashtype\" (string, optional, default=ALL) The signature hash type to sign with if not specified by the PSBT. Must be one of\n"
+ " \"ALL\"\n"
+ " \"NONE\"\n"
+ " \"SINGLE\"\n"
+ " \"ALL|ANYONECANPAY\"\n"
+ " \"NONE|ANYONECANPAY\"\n"
+ " \"SINGLE|ANYONECANPAY\"\n"
+ "4. bip32derivs (boolean, optiona, default=false) If true, includes the BIP 32 derivation paths for public keys if we know them\n"
+
+ "\nResult:\n"
+ "{\n"
+ " \"psbt\" : \"value\", (string) The base64-encoded partially signed transaction\n"
+ " \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n"
+ " ]\n"
+ "}\n"
+
+ "\nExamples:\n"
+ + HelpExampleCli("walletprocesspsbt", "\"psbt\"")
+ );
+
+ RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VSTR});
+
+ // Unserialize the transaction
+ PartiallySignedTransaction psbtx;
+ std::string error;
+ if (!DecodePSBT(psbtx, request.params[0].get_str(), error)) {
+ throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
+ }
+
+ // Get the sighash type
+ int nHashType = ParseSighashString(request.params[2]);
+
+ // Use CTransaction for the constant parts of the
+ // transaction to avoid rehashing.
+ const CTransaction txConst(*psbtx.tx);
+
+ // Fill transaction with our data and also sign
+ bool sign = request.params[1].isNull() ? true : request.params[1].get_bool();
+ bool bip32derivs = request.params[3].isNull() ? false : request.params[3].get_bool();
+ bool complete = FillPSBT(pwallet, psbtx, &txConst, nHashType, sign, bip32derivs);
+
+ UniValue result(UniValue::VOBJ);
+ CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
+ ssTx << psbtx;
+ result.push_back(Pair("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size())));
+ result.push_back(Pair("complete", complete));
+
+ return result;
+}
+
+UniValue walletcreatefundedpsbt(const JSONRPCRequest& request)
+{
+ std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
+ CWallet* const pwallet = wallet.get();
+
+ if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
+ return NullUniValue;
+ }
+
+ if (request.fHelp || request.params.size() < 2 || request.params.size() > 6)
+ throw std::runtime_error(
+ "walletcreatefundedpsbt [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable ) ( options bip32derivs )\n"
+ "\nCreates and funds a transaction in the Partially Signed Transaction format. Inputs will be added if supplied inputs are not enough\n"
+ "Implements the Creator and Updater roles.\n"
+ "\nArguments:\n"
+ "1. \"inputs\" (array, required) A json array of json objects\n"
+ " [\n"
+ " {\n"
+ " \"txid\":\"id\", (string, required) The transaction id\n"
+ " \"vout\":n, (numeric, required) The output number\n"
+ " \"sequence\":n (numeric, optional) The sequence number\n"
+ " } \n"
+ " ,...\n"
+ " ]\n"
+ "2. \"outputs\" (array, required) a json array with outputs (key-value pairs)\n"
+ " [\n"
+ " {\n"
+ " \"address\": x.xxx, (obj, optional) A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT + "\n"
+ " },\n"
+ " {\n"
+ " \"data\": \"hex\" (obj, optional) A key-value pair. The key must be \"data\", the value is hex encoded data\n"
+ " }\n"
+ " ,... More key-value pairs of the above form. For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n"
+ " accepted as second parameter.\n"
+ " ]\n"
+ "3. locktime (numeric, optional, default=0) Raw locktime. Non-0 value also locktime-activates inputs\n"
+ "4. replaceable (boolean, optional, default=false) Marks this transaction as BIP125 replaceable.\n"
+ " Allows this transaction to be replaced by a transaction with higher fees. If provided, it is an error if explicit sequence numbers are incompatible.\n"
+ "5. options (object, optional)\n"
+ " {\n"
+ " \"changeAddress\" (string, optional, default pool address) The bitcoin address to receive the change\n"
+ " \"changePosition\" (numeric, optional, default random) The index of the change output\n"
+ " \"change_type\" (string, optional) The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\". Default is set by -changetype.\n"
+ " \"includeWatching\" (boolean, optional, default false) Also select inputs which are watch only\n"
+ " \"lockUnspents\" (boolean, optional, default false) Lock selected unspent outputs\n"
+ " \"feeRate\" (numeric, optional, default not set: makes wallet determine the fee) Set a specific fee rate in " + CURRENCY_UNIT + "/kB\n"
+ " \"subtractFeeFromOutputs\" (array, optional) A json array of integers.\n"
+ " The fee will be equally deducted from the amount of each specified output.\n"
+ " The outputs are specified by their zero-based index, before any change output is added.\n"
+ " Those recipients will receive less bitcoins than you enter in their corresponding amount field.\n"
+ " If no outputs are specified here, the sender pays the fee.\n"
+ " [vout_index,...]\n"
+ " \"replaceable\" (boolean, optional) Marks this transaction as BIP125 replaceable.\n"
+ " Allows this transaction to be replaced by a transaction with higher fees\n"
+ " \"conf_target\" (numeric, optional) Confirmation target (in blocks)\n"
+ " \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n"
+ " \"UNSET\"\n"
+ " \"ECONOMICAL\"\n"
+ " \"CONSERVATIVE\"\n"
+ " }\n"
+ "6. bip32derivs (boolean, optiona, default=false) If true, includes the BIP 32 derivation paths for public keys if we know them\n"
+ "\nResult:\n"
+ "{\n"
+ " \"psbt\": \"value\", (string) The resulting raw transaction (base64-encoded string)\n"
+ " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n"
+ " \"changepos\": n (numeric) The position of the added change output, or -1\n"
+ "}\n"
+ "\nExamples:\n"
+ "\nCreate a transaction with no inputs\n"
+ + HelpExampleCli("walletcreatefundedpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"")
+ );
+
+ RPCTypeCheck(request.params, {
+ UniValue::VARR,
+ UniValueType(), // ARR or OBJ, checked later
+ UniValue::VNUM,
+ UniValue::VBOOL,
+ UniValue::VOBJ
+ }, true
+ );
+
+ CAmount fee;
+ int change_position;
+ CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]);
+ FundTransaction(pwallet, rawTx, fee, change_position, request.params[4]);
+
+ // Make a blank psbt
+ PartiallySignedTransaction psbtx;
+ psbtx.tx = rawTx;
+ for (unsigned int i = 0; i < rawTx.vin.size(); ++i) {
+ psbtx.inputs.push_back(PSBTInput());
+ }
+ for (unsigned int i = 0; i < rawTx.vout.size(); ++i) {
+ psbtx.outputs.push_back(PSBTOutput());
+ }
+
+ // Use CTransaction for the constant parts of the
+ // transaction to avoid rehashing.
+ const CTransaction txConst(*psbtx.tx);
+
+ // Fill transaction with out data but don't sign
+ bool bip32derivs = request.params[5].isNull() ? false : request.params[5].get_bool();
+ FillPSBT(pwallet, psbtx, &txConst, 1, false, bip32derivs);
+
+ // Serialize the PSBT
+ CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
+ ssTx << psbtx;
+
+ UniValue result(UniValue::VOBJ);
+ result.pushKV("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()));
+ result.pushKV("fee", ValueFromAmount(fee));
+ result.pushKV("changepos", change_position);
+ return result;
+}
+
extern UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp
extern UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp
extern UniValue importprivkey(const JSONRPCRequest& request);
@@ -4406,6 +4732,8 @@ static const CRPCCommand commands[] =
{ // category name actor (function) argNames
// --------------------- ------------------------ ----------------------- ----------
{ "rawtransactions", "fundrawtransaction", &fundrawtransaction, {"hexstring","options","iswitness"} },
+ { "wallet", "walletprocesspsbt", &walletprocesspsbt, {"psbt","sign","sighashtype","bip32derivs"} },
+ { "wallet", "walletcreatefundedpsbt", &walletcreatefundedpsbt, {"inputs","outputs","locktime","replaceable","options","bip32derivs"} },
{ "hidden", "resendwallettransactions", &resendwallettransactions, {} },
{ "wallet", "abandontransaction", &abandontransaction, {"txid"} },
{ "wallet", "abortrescan", &abortrescan, {} },
diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h
index b841f3e424..64556b5824 100644
--- a/src/wallet/rpcwallet.h
+++ b/src/wallet/rpcwallet.h
@@ -11,6 +11,8 @@ class CRPCTable;
class CWallet;
class JSONRPCRequest;
class UniValue;
+struct PartiallySignedTransaction;
+class CTransaction;
void RegisterWalletRPCCommands(CRPCTable &t);
@@ -28,4 +30,5 @@ bool EnsureWalletIsAvailable(CWallet *, bool avoidException);
UniValue getaddressinfo(const JSONRPCRequest& request);
UniValue signrawtransactionwithwallet(const JSONRPCRequest& request);
+bool FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& psbtx, const CTransaction* txConst, int sighash_type = 1, bool sign = true, bool bip32derivs = false);
#endif //BITCOIN_WALLET_RPCWALLET_H