diff --git a/doc/release-notes.md b/doc/release-notes.md --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -3,3 +3,5 @@ This release includes the following features and fixes: + - Add `signrawtransactionwithkey` and `signrawtransactionwithwallet` RPCs. + These are specialized subsets of the `signrawtransaction` RPC. \ No newline at end of file diff --git a/src/Makefile.am b/src/Makefile.am --- a/src/Makefile.am +++ b/src/Makefile.am @@ -170,6 +170,7 @@ rpc/mining.h \ rpc/misc.h \ rpc/protocol.h \ + rpc/rawtransaction.h \ rpc/safemode.h \ rpc/server.h \ rpc/tojson.h \ diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -68,6 +68,7 @@ << "importmulti" << "signmessagewithprivkey" << "signrawtransaction" + << "signrawtransactionwithkey" << "walletpassphrase" << "walletpassphrasechange" << "encryptwallet"; diff --git a/src/qt/test/rpcnestedtests.cpp b/src/qt/test/rpcnestedtests.cpp --- a/src/qt/test/rpcnestedtests.cpp +++ b/src/qt/test/rpcnestedtests.cpp @@ -108,9 +108,9 @@ RPCConsole::RPCParseCommandLine(result, "signmessagewithprivkey abc,def", false, &filtered); QVERIFY(filtered == "signmessagewithprivkey(…)"); - RPCConsole::RPCParseCommandLine(result, "signrawtransaction(abc)", false, - &filtered); - QVERIFY(filtered == "signrawtransaction(…)"); + RPCConsole::RPCParseCommandLine(result, "signrawtransactionwithkey(abc)", + false, &filtered); + QVERIFY(filtered == "signrawtransactionwithkey(…)"); RPCConsole::RPCParseCommandLine(result, "walletpassphrase(help())", false, &filtered); QVERIFY(filtered == "walletpassphrase(…)"); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -90,6 +90,9 @@ {"createrawtransaction", 2, "locktime"}, {"signrawtransaction", 1, "prevtxs"}, {"signrawtransaction", 2, "privkeys"}, + {"signrawtransactionwithkey", 1, "privkeys"}, + {"signrawtransactionwithkey", 2, "prevtxs"}, + {"signrawtransactionwithwallet", 1, "prevtxs"}, {"sendrawtransaction", 1, "allowhighfees"}, {"combinerawtransaction", 0, "txs"}, {"fundrawtransaction", 1, "options"}, diff --git a/src/rpc/rawtransaction.h b/src/rpc/rawtransaction.h new file mode 100644 --- /dev/null +++ b/src/rpc/rawtransaction.h @@ -0,0 +1,17 @@ +// Copyright (c) 2017 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_RPC_RAWTRANSACTION_H +#define BITCOIN_RPC_RAWTRANSACTION_H + +class CBasicKeyStore; +class CMutableTransaction; +class UniValue; + +/** Sign a transaction with the given keystore and previous transactions */ +UniValue SignTransaction(CMutableTransaction &mtx, const UniValue &prevTxs, + CBasicKeyStore *keystore, bool tempKeystore, + const UniValue &hashType); + +#endif // BITCOIN_RPC_RAWTRANSACTION_H diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -3,6 +3,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include "rpc/rawtransaction.h" #include "base58.h" #include "chain.h" #include "coins.h" @@ -29,7 +30,6 @@ #include "validation.h" #ifdef ENABLE_WALLET #include "wallet/rpcwallet.h" -#include "wallet/wallet.h" #endif #include @@ -659,8 +659,8 @@ vErrorsRet.push_back(entry); } -UniValue combinerawtransaction(const Config &config, - const JSONRPCRequest &request) { +static UniValue combinerawtransaction(const Config &config, + const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( @@ -758,125 +758,15 @@ return EncodeHexTx(CTransaction(mergedTx)); } -static UniValue signrawtransaction(const Config &config, - const JSONRPCRequest &request) { -#ifdef ENABLE_WALLET - CWallet *const pwallet = GetWalletForJSONRPCRequest(request); -#endif - - if (request.fHelp || request.params.size() < 1 || - request.params.size() > 4) { - throw std::runtime_error( - "signrawtransaction \"hexstring\" ( " - "[{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\"," - "\"redeemScript\":\"hex\"},...] [\"privatekey1\",...] sighashtype " - ")\n" - "\nSign inputs for raw transaction (serialized, hex-encoded).\n" - "The second optional argument (may be null) is an array of " - "previous transaction outputs that\n" - "this transaction depends on but may not yet be in the block " - "chain.\n" - "The third optional argument (may be null) is an array of " - "base58-encoded private\n" - "keys that, if given, will be the only keys used to sign the " - "transaction.\n" -#ifdef ENABLE_WALLET - + HelpRequiringPassphrase(pwallet) + - "\n" -#endif - - "\nArguments:\n" - "1. \"hexstring\" (string, required) The transaction hex " - "string\n" - "2. \"prevtxs\" (string, optional) An json array of previous " - "dependent transaction outputs\n" - " [ (json array of json objects, or 'null' if " - "none provided)\n" - " {\n" - " \"txid\":\"id\", (string, required) The " - "transaction id\n" - " \"vout\":n, (numeric, required) The " - "output number\n" - " \"scriptPubKey\": \"hex\", (string, required) script " - "key\n" - " \"redeemScript\": \"hex\", (string, required for P2SH " - "or P2WSH) redeem script\n" - " \"amount\": value (numeric, required) The " - "amount spent\n" - " }\n" - " ,...\n" - " ]\n" - "3. \"privkeys\" (string, optional) A json array of " - "base58-encoded private keys for signing\n" - " [ (json array of strings, or 'null' if none " - "provided)\n" - " \"privatekey\" (string) private key in base58-encoding\n" - " ,...\n" - " ]\n" - "4. \"sighashtype\" (string, optional, default=ALL) The " - "signature hash type. Must be one of\n" - " \"ALL\"\n" - " \"NONE\"\n" - " \"SINGLE\"\n" - " \"ALL|ANYONECANPAY\"\n" - " \"NONE|ANYONECANPAY\"\n" - " \"SINGLE|ANYONECANPAY\"\n" - " \"ALL|FORKID\"\n" - " \"NONE|FORKID\"\n" - " \"SINGLE|FORKID\"\n" - " \"ALL|FORKID|ANYONECANPAY\"\n" - " \"NONE|FORKID|ANYONECANPAY\"\n" - " \"SINGLE|FORKID|ANYONECANPAY\"\n" - - "\nResult:\n" - "{\n" - " \"hex\" : \"value\", (string) The hex-encoded raw " - "transaction with signature(s)\n" - " \"complete\" : true|false, (boolean) If the transaction has a " - "complete set of signatures\n" - " \"errors\" : [ (json array of objects) Script " - "verification errors (if there are any)\n" - " {\n" - " \"txid\" : \"hash\", (string) The hash of the " - "referenced, previous transaction\n" - " \"vout\" : n, (numeric) The index of the " - "output to spent and used as input\n" - " \"scriptSig\" : \"hex\", (string) The hex-encoded " - "signature script\n" - " \"sequence\" : n, (numeric) Script sequence " - "number\n" - " \"error\" : \"text\" (string) Verification or " - "signing error related to the input\n" - " }\n" - " ,...\n" - " ]\n" - "}\n" - - "\nExamples:\n" + - HelpExampleCli("signrawtransaction", "\"myhex\"") + - HelpExampleRpc("signrawtransaction", "\"myhex\"")); - } - - ObserveSafeMode(); -#ifdef ENABLE_WALLET - LOCK2(cs_main, pwallet ? &pwallet->cs_wallet : nullptr); -#else - LOCK(cs_main); -#endif - RPCTypeCheck( - request.params, - {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true); - - CMutableTransaction mtx; - if (!DecodeHexTx(mtx, request.params[0].get_str())) { - throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); - } - +UniValue SignTransaction(CMutableTransaction &mtx, + const UniValue &prevTxsUnival, + CBasicKeyStore *keystore, bool is_temp_keystore, + const UniValue &hashType) { // Fetch previous transactions (inputs): CCoinsView viewDummy; CCoinsViewCache view(&viewDummy); { - LOCK(g_mempool.cs); + LOCK2(cs_main, g_mempool.cs); CCoinsViewCache &viewChain = *pcoinsTip; CCoinsViewMemPool viewMempool(&viewChain, g_mempool); // Temporarily switch cache backend to db+mempool view. @@ -891,39 +781,10 @@ view.SetBackend(viewDummy); } - bool fGivenKeys = false; - CBasicKeyStore tempKeystore; - if (request.params.size() > 2 && !request.params[2].isNull()) { - fGivenKeys = true; - UniValue keys = request.params[2].get_array(); - for (size_t idx = 0; idx < keys.size(); idx++) { - UniValue k = keys[idx]; - CBitcoinSecret vchSecret; - bool fGood = vchSecret.SetString(k.get_str()); - if (!fGood) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - "Invalid private key"); - } - - CKey key = vchSecret.GetKey(); - if (!key.IsValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, - "Private key outside allowed range"); - } - - tempKeystore.AddKey(key); - } - } -#ifdef ENABLE_WALLET - else if (pwallet) { - EnsureWalletIsUnlocked(pwallet); - } -#endif - // Add previous txouts given in the RPC call: - if (request.params.size() > 1 && !request.params[1].isNull()) { - UniValue prevTxs = request.params[1].get_array(); - for (size_t idx = 0; idx < prevTxs.size(); idx++) { + if (!prevTxsUnival.isNull()) { + UniValue prevTxs = prevTxsUnival.get_array(); + for (size_t idx = 0; idx < prevTxs.size(); ++idx) { const UniValue &p = prevTxs[idx]; if (!p.isObject()) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, @@ -989,9 +850,9 @@ } // If redeemScript given and not using the local wallet (private - // keys given), add redeemScript to the tempKeystore so it can be + // keys given), add redeemScript to the keystore so it can be // signed: - if (fGivenKeys && scriptPubKey.IsPayToScriptHash()) { + if (is_temp_keystore && scriptPubKey.IsPayToScriptHash()) { RPCTypeCheckObj( prevOut, { {"txid", UniValueType(UniValue::VSTR)}, @@ -1003,21 +864,14 @@ if (!v.isNull()) { std::vector rsData(ParseHexV(v, "redeemScript")); CScript redeemScript(rsData.begin(), rsData.end()); - tempKeystore.AddCScript(redeemScript); + keystore->AddCScript(redeemScript); } } } } -#ifdef ENABLE_WALLET - const CKeyStore &keystore = - ((fGivenKeys || !pwallet) ? tempKeystore : *pwallet); -#else - const CKeyStore &keystore = tempKeystore; -#endif - SigHashType sigHashType = SigHashType().withForkId(); - if (request.params.size() > 3 && !request.params[3].isNull()) { + if (!hashType.isNull()) { static std::map mapSigHashValues = { {"ALL", SIGHASH_ALL}, {"ALL|ANYONECANPAY", SIGHASH_ALL | SIGHASH_ANYONECANPAY}, @@ -1035,7 +889,7 @@ {"SINGLE|FORKID|ANYONECANPAY", SIGHASH_SINGLE | SIGHASH_FORKID | SIGHASH_ANYONECANPAY}, }; - std::string strHashType = request.params[3].get_str(); + std::string strHashType = hashType.get_str(); if (!mapSigHashValues.count(strHashType)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid sighash param"); } @@ -1070,7 +924,7 @@ if ((sigHashType.getBaseType() != BaseSigHashType::SINGLE) || (i < mtx.vout.size())) { ProduceSignature(MutableTransactionSignatureCreator( - &keystore, &mtx, i, amount, sigHashType), + keystore, &mtx, i, amount, sigHashType), prevPubKey, sigdata); } sigdata = CombineSignatures( @@ -1108,6 +962,243 @@ return result; } +static UniValue signrawtransactionwithkey(const Config &config, + const JSONRPCRequest &request) { + if (request.fHelp || request.params.size() < 2 || + request.params.size() > 4) { + throw std::runtime_error( + "signrawtransactionwithkey \"hexstring\" [\"privatekey1\",...] ( " + "[{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\"," + "\"redeemScript\":\"hex\"},...] sighashtype )\n" + "\nSign inputs for raw transaction (serialized, hex-encoded).\n" + "The second argument is an array of base58-encoded private\n" + "keys that will be the only keys used to sign the transaction.\n" + "The third optional argument (may be null) is an array of previous " + "transaction outputs that\n" + "this transaction depends on but may not yet be in the block " + "chain.\n" + + "\nArguments:\n" + "1. \"hexstring\" (string, required) The " + "transaction hex string\n" + "2. \"privkeys\" (string, required) A json " + "array of base58-encoded private keys for signing\n" + " [ (json array of strings)\n" + " \"privatekey\" (string) private key in " + "base58-encoding\n" + " ,...\n" + " ]\n" + "3. \"prevtxs\" (string, optional) An json " + "array of previous dependent transaction outputs\n" + " [ (json array of json objects, " + "or 'null' if none provided)\n" + " {\n" + " \"txid\":\"id\", (string, required) The " + "transaction id\n" + " \"vout\":n, (numeric, required) The " + "output number\n" + " \"scriptPubKey\": \"hex\", (string, required) script " + "key\n" + " \"redeemScript\": \"hex\", (string, required for " + "P2SH) redeem script\n" + " \"amount\": value (numeric, required) The " + "amount spent\n" + " }\n" + " ,...\n" + " ]\n" + "4. \"sighashtype\" (string, optional, " + "default=ALL) The signature hash type. Must be one of\n" + " \"ALL|FORKID\"\n" + " \"NONE|FORKID\"\n" + " \"SINGLE|FORKID\"\n" + " \"ALL|FORKID|ANYONECANPAY\"\n" + " \"NONE|FORKID|ANYONECANPAY\"\n" + " \"SINGLE|FORKID|ANYONECANPAY\"\n" + + "\nResult:\n" + "{\n" + " \"hex\" : \"value\", (string) The hex-encoded " + "raw transaction with signature(s)\n" + " \"complete\" : true|false, (boolean) If the " + "transaction has a complete set of signatures\n" + " \"errors\" : [ (json array of objects) " + "Script verification errors (if there are any)\n" + " {\n" + " \"txid\" : \"hash\", (string) The hash of the " + "referenced, previous transaction\n" + " \"vout\" : n, (numeric) The index of the " + "output to spent and used as input\n" + " \"scriptSig\" : \"hex\", (string) The hex-encoded " + "signature script\n" + " \"sequence\" : n, (numeric) Script sequence " + "number\n" + " \"error\" : \"text\" (string) Verification or " + "signing error related to the input\n" + " }\n" + " ,...\n" + " ]\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("signrawtransactionwithkey", "\"myhex\"") + + HelpExampleRpc("signrawtransactionwithkey", "\"myhex\"")); + } + + RPCTypeCheck( + request.params, + {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true); + + CMutableTransaction mtx; + if (!DecodeHexTx(mtx, request.params[0].get_str())) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + } + + CBasicKeyStore keystore; + const UniValue &keys = request.params[1].get_array(); + for (size_t idx = 0; idx < keys.size(); ++idx) { + UniValue k = keys[idx]; + CBitcoinSecret vchSecret; + if (!vchSecret.SetString(k.get_str())) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Invalid private key"); + } + CKey key = vchSecret.GetKey(); + if (!key.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, + "Private key outside allowed range"); + } + keystore.AddKey(key); + } + + return SignTransaction(mtx, request.params[2], &keystore, true, + request.params[3]); +} + +static UniValue signrawtransaction(const Config &config, + const JSONRPCRequest &request) { +#ifdef ENABLE_WALLET + CWallet *const pwallet = GetWalletForJSONRPCRequest(request); +#endif + + if (request.fHelp || request.params.size() < 1 || + request.params.size() > 4) { + throw std::runtime_error( + "signrawtransaction \"hexstring\" ( " + "[{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\"," + "\"redeemScript\":\"hex\"},...] [\"privatekey1\",...] sighashtype " + ")\n" + "\nSign inputs for raw transaction (serialized, hex-encoded).\n" + "The second optional argument (may be null) is an array of " + "previous transaction outputs that\n" + "this transaction depends on but may not yet be in the block " + "chain.\n" + "The third optional argument (may be null) is an array of " + "base58-encoded private\n" + "keys that, if given, will be the only keys used to sign the " + "transaction.\n" +#ifdef ENABLE_WALLET + + HelpRequiringPassphrase(pwallet) + + "\n" +#endif + "\nArguments:\n" + "1. \"hexstring\" (string, required) The transaction hex " + "string\n" + "2. \"prevtxs\" (string, optional) An json array of previous " + "dependent transaction outputs\n" + " [ (json array of json objects, or 'null' if " + "none provided)\n" + " {\n" + " \"txid\":\"id\", (string, required) The " + "transaction id\n" + " \"vout\":n, (numeric, required) The " + "output number\n" + " \"scriptPubKey\": \"hex\", (string, required) script " + "key\n" + " \"redeemScript\": \"hex\", (string, required for P2SH) " + "redeem script\n" + " \"amount\": value (numeric, required) The " + "amount spent\n" + " }\n" + " ,...\n" + " ]\n" + "3. \"privkeys\" (string, optional) A json array of " + "base58-encoded private keys for signing\n" + " [ (json array of strings, or 'null' if none " + "provided)\n" + " \"privatekey\" (string) private key in base58-encoding\n" + " ,...\n" + " ]\n" + "4. \"sighashtype\" (string, optional, default=ALL) The " + "signature hash type. Must be one of\n" + " \"ALL|FORKID\"\n" + " \"NONE|FORKID\"\n" + " \"SINGLE|FORKID\"\n" + " \"ALL|FORKID|ANYONECANPAY\"\n" + " \"NONE|FORKID|ANYONECANPAY\"\n" + " \"SINGLE|FORKID|ANYONECANPAY\"\n" + + "\nResult:\n" + "{\n" + " \"hex\" : \"value\", (string) The hex-encoded raw " + "transaction with signature(s)\n" + " \"complete\" : true|false, (boolean) If the transaction has a " + "complete set of signatures\n" + " \"errors\" : [ (json array of objects) Script " + "verification errors (if there are any)\n" + " {\n" + " \"txid\" : \"hash\", (string) The hash of the " + "referenced, previous transaction\n" + " \"vout\" : n, (numeric) The index of the " + "output to spent and used as input\n" + " \"scriptSig\" : \"hex\", (string) The hex-encoded " + "signature script\n" + " \"sequence\" : n, (numeric) Script sequence " + "number\n" + " \"error\" : \"text\" (string) Verification or " + "signing error related to the input\n" + " }\n" + " ,...\n" + " ]\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("signrawtransaction", "\"myhex\"") + + HelpExampleRpc("signrawtransaction", "\"myhex\"")); + } + + RPCTypeCheck( + request.params, + {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true); + + // Make a JSONRPCRequest to pass on to the right signrawtransaction* command + JSONRPCRequest new_request; + new_request.id = request.id; + new_request.params.setArray(); + + // For signing with private keys + if (!request.params[2].isNull()) { + new_request.params.push_back(request.params[0]); + // Note: the prevtxs and privkeys are reversed for + // signrawtransactionwithkey + new_request.params.push_back(request.params[2]); + new_request.params.push_back(request.params[1]); + new_request.params.push_back(request.params[3]); + return signrawtransactionwithkey(config, new_request); + } +// Otherwise sign with the wallet which does not take a privkeys parameter +#ifdef ENABLE_WALLET + else { + new_request.params.push_back(request.params[0]); + new_request.params.push_back(request.params[1]); + new_request.params.push_back(request.params[3]); + return signrawtransactionwithwallet(config, new_request); + } +#endif + // If we have made it this far, then wallet is disabled and no private keys + // were given, so fail here. + throw JSONRPCError(RPC_INVALID_PARAMETER, "No private keys available."); +} + static UniValue sendrawtransaction(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() < 1 || @@ -1203,18 +1294,19 @@ // clang-format off static const ContextFreeRPCCommand commands[] = { - // category name actor (function) argNames - // ------------------- ------------------------ ---------------------- ---------- - { "rawtransactions", "getrawtransaction", getrawtransaction, {"txid","verbose"} }, - { "rawtransactions", "createrawtransaction", createrawtransaction, {"inputs","outputs","locktime"} }, - { "rawtransactions", "decoderawtransaction", decoderawtransaction, {"hexstring"} }, - { "rawtransactions", "decodescript", decodescript, {"hexstring"} }, - { "rawtransactions", "sendrawtransaction", sendrawtransaction, {"hexstring","allowhighfees"} }, - { "rawtransactions", "combinerawtransaction", combinerawtransaction, {"txs"} }, - { "rawtransactions", "signrawtransaction", signrawtransaction, {"hexstring","prevtxs","privkeys","sighashtype"} }, /* uses wallet if enabled */ - - { "blockchain", "gettxoutproof", gettxoutproof, {"txids", "blockhash"} }, - { "blockchain", "verifytxoutproof", verifytxoutproof, {"proof"} }, + // category name actor (function) argNames + // ------------------- ------------------------ ---------------------- ---------- + { "rawtransactions", "getrawtransaction", getrawtransaction, {"txid","verbose"} }, + { "rawtransactions", "createrawtransaction", createrawtransaction, {"inputs","outputs","locktime"} }, + { "rawtransactions", "decoderawtransaction", decoderawtransaction, {"hexstring"} }, + { "rawtransactions", "decodescript", decodescript, {"hexstring"} }, + { "rawtransactions", "sendrawtransaction", sendrawtransaction, {"hexstring","allowhighfees"} }, + { "rawtransactions", "combinerawtransaction", combinerawtransaction, {"txs"} }, + { "rawtransactions", "signrawtransaction", signrawtransaction, {"hexstring","prevtxs","privkeys","sighashtype"} }, /* uses wallet if enabled */ + { "rawtransactions", "signrawtransactionwithkey", signrawtransactionwithkey, {"hexstring","privkeys","prevtxs","sighashtype"} }, + + { "blockchain", "gettxoutproof", gettxoutproof, {"txids", "blockhash"} }, + { "blockchain", "verifytxoutproof", verifytxoutproof, {"proof"} }, }; // clang-format on diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -81,18 +81,6 @@ r = CallRPC(std::string("decoderawtransaction ") + rawtx + " extra"), std::runtime_error); - BOOST_CHECK_THROW(CallRPC("signrawtransaction"), std::runtime_error); - BOOST_CHECK_THROW(CallRPC("signrawtransaction null"), std::runtime_error); - BOOST_CHECK_THROW(CallRPC("signrawtransaction ff00"), std::runtime_error); - BOOST_CHECK_NO_THROW(CallRPC(std::string("signrawtransaction ") + rawtx)); - BOOST_CHECK_NO_THROW(CallRPC(std::string("signrawtransaction ") + rawtx + - " null null NONE|FORKID|ANYONECANPAY")); - BOOST_CHECK_NO_THROW(CallRPC(std::string("signrawtransaction ") + rawtx + - " [] [] NONE|FORKID|ANYONECANPAY")); - BOOST_CHECK_THROW(CallRPC(std::string("signrawtransaction ") + rawtx + - " null null badenum"), - std::runtime_error); - // Only check failure cases for sendrawtransaction, there's no network to // send to... BOOST_CHECK_THROW(CallRPC("sendrawtransaction"), std::runtime_error); @@ -145,11 +133,11 @@ "\"KzsXybp9jX64P5ekX1KUxRQ79Jht9uzW7LorgwE65i5rWACL6LQe\""; std::string privkey2 = "\"Kyhdf5LuKTRx4ge69ybABsiUAWjVRK4XGxAKk2FQLp2HjGMy87Z4\""; - r = CallRPC(std::string("signrawtransaction ") + notsigned + " " + prevout + - " " + "[]"); + r = CallRPC(std::string("signrawtransactionwithkey ") + notsigned + " [] " + + prevout); BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == false); - r = CallRPC(std::string("signrawtransaction ") + notsigned + " " + prevout + - " " + "[" + privkey1 + "," + privkey2 + "]"); + r = CallRPC(std::string("signrawtransactionwithkey ") + notsigned + " [" + + privkey1 + "," + privkey2 + "] " + prevout); BOOST_CHECK(find_value(r.get_obj(), "complete").get_bool() == true); } @@ -180,8 +168,8 @@ bool exceptionThrownDueToMissingAmount = false, errorWasMissingAmount = false; try { - r = CallRPC(std::string("signrawtransaction ") + notsigned + " " + - prevout + " " + "[" + privkey1 + "," + privkey2 + "]"); + r = CallRPC(std::string("signrawtransactionwithkey ") + notsigned + + " [" + privkey1 + "," + privkey2 + "] " + prevout); } catch (const std::runtime_error &e) { exceptionThrownDueToMissingAmount = true; if (std::string(e.what()).find("amount") != std::string::npos) { diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h --- a/src/wallet/rpcwallet.h +++ b/src/wallet/rpcwallet.h @@ -11,6 +11,8 @@ class CWallet; class JSONRPCRequest; class CWallet; +class UniValue; +class Config; void RegisterWalletRPCCommands(CRPCTable &t); @@ -26,4 +28,6 @@ void EnsureWalletIsUnlocked(CWallet *); bool EnsureWalletIsAvailable(CWallet *, bool avoidException); +UniValue signrawtransactionwithwallet(const Config &config, + const JSONRPCRequest &request); #endif // BITCOIN_WALLET_RPCWALLET_H diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -15,6 +15,7 @@ #include "policy/policy.h" #include "rpc/mining.h" #include "rpc/misc.h" +#include "rpc/rawtransaction.h" #include "rpc/safemode.h" #include "rpc/server.h" #include "timedata.h" @@ -3610,6 +3611,100 @@ return result; } +UniValue signrawtransactionwithwallet(const Config &config, + 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() > 3) { + throw std::runtime_error( + "signrawtransactionwithwallet \"hexstring\" ( " + "[{\"txid\":\"id\",\"vout\":n,\"scriptPubKey\":\"hex\"," + "\"redeemScript\":\"hex\"},...] sighashtype )\n" + "\nSign inputs for raw transaction (serialized, hex-encoded).\n" + "The second optional argument (may be null) is an array of " + "previous transaction outputs that\n" + "this transaction depends on but may not yet be in the block " + "chain.\n" + + HelpRequiringPassphrase(pwallet) + + "\n" + + "\nArguments:\n" + "1. \"hexstring\" (string, required) The " + "transaction hex string\n" + "2. \"prevtxs\" (string, optional) An json " + "array of previous dependent transaction outputs\n" + " [ (json array of json objects, " + "or 'null' if none provided)\n" + " {\n" + " \"txid\":\"id\", (string, required) The " + "transaction id\n" + " \"vout\":n, (numeric, required) The " + "output number\n" + " \"scriptPubKey\": \"hex\", (string, required) script " + "key\n" + " \"redeemScript\": \"hex\", (string, required for " + "P2SH) redeem script\n" + " \"amount\": value (numeric, required) The " + "amount spent\n" + " }\n" + " ,...\n" + " ]\n" + "3. \"sighashtype\" (string, optional, " + "default=ALL) The signature hash type. Must be one of\n" + " \"ALL|FORKID\"\n" + " \"NONE|FORKID\"\n" + " \"SINGLE|FORKID\"\n" + " \"ALL|FORKID|ANYONECANPAY\"\n" + " \"NONE|FORKID|ANYONECANPAY\"\n" + " \"SINGLE|FORKID|ANYONECANPAY\"\n" + + "\nResult:\n" + "{\n" + " \"hex\" : \"value\", (string) The hex-encoded " + "raw transaction with signature(s)\n" + " \"complete\" : true|false, (boolean) If the " + "transaction has a complete set of signatures\n" + " \"errors\" : [ (json array of objects) " + "Script verification errors (if there are any)\n" + " {\n" + " \"txid\" : \"hash\", (string) The hash of the " + "referenced, previous transaction\n" + " \"vout\" : n, (numeric) The index of the " + "output to spent and used as input\n" + " \"scriptSig\" : \"hex\", (string) The hex-encoded " + "signature script\n" + " \"sequence\" : n, (numeric) Script sequence " + "number\n" + " \"error\" : \"text\" (string) Verification or " + "signing error related to the input\n" + " }\n" + " ,...\n" + " ]\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + + HelpExampleRpc("signrawtransactionwithwallet", "\"myhex\"")); + } + + RPCTypeCheck(request.params, + {UniValue::VSTR, UniValue::VARR, UniValue::VSTR}, true); + + CMutableTransaction mtx; + if (!DecodeHexTx(mtx, request.params[0].get_str())) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + } + + // Sign the transaction + LOCK2(cs_main, pwallet->cs_wallet); + return SignTransaction(mtx, request.params[1], pwallet, false, + request.params[2]); +} + static UniValue generate(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); @@ -3745,52 +3840,53 @@ // clang-format off static const ContextFreeRPCCommand commands[] = { - // category name actor (function) argNames - // ------------------- ------------------------ ---------------------- ---------- - { "rawtransactions", "fundrawtransaction", fundrawtransaction, {"hexstring","options"} }, - { "hidden", "resendwallettransactions", resendwallettransactions, {} }, - { "wallet", "abandontransaction", abandontransaction, {"txid"} }, - { "wallet", "addmultisigaddress", addmultisigaddress, {"nrequired","keys","label|account"} }, - { "wallet", "backupwallet", backupwallet, {"destination"} }, - { "wallet", "encryptwallet", encryptwallet, {"passphrase"} }, - { "wallet", "getaccountaddress", getlabeladdress, {"account"} }, - { "wallet", "getlabeladdress", getlabeladdress, {"label"} }, - { "wallet", "getaccount", getaccount, {"address"} }, - { "wallet", "getaddressesbyaccount", getaddressesbyaccount, {"account"} }, - { "wallet", "getbalance", getbalance, {"account","minconf","include_watchonly"} }, - { "wallet", "getnewaddress", getnewaddress, {"label|account"} }, - { "wallet", "getrawchangeaddress", getrawchangeaddress, {} }, - { "wallet", "getreceivedbylabel", getreceivedbylabel, {"label","minconf"} }, - { "wallet", "getreceivedbyaccount", getreceivedbylabel, {"account","minconf"} }, - { "wallet", "getreceivedbyaddress", getreceivedbyaddress, {"address","minconf"} }, - { "wallet", "gettransaction", gettransaction, {"txid","include_watchonly"} }, - { "wallet", "getunconfirmedbalance", getunconfirmedbalance, {} }, - { "wallet", "getwalletinfo", getwalletinfo, {} }, - { "wallet", "keypoolrefill", keypoolrefill, {"newsize"} }, - { "wallet", "listaccounts", listaccounts, {"minconf","include_watchonly"} }, - { "wallet", "listaddressgroupings", listaddressgroupings, {} }, - { "wallet", "listlockunspent", listlockunspent, {} }, - { "wallet", "listreceivedbylabel", listreceivedbylabel, {"minconf","include_empty","include_watchonly"} }, - { "wallet", "listreceivedbyaccount", listreceivedbylabel, {"minconf","include_empty","include_watchonly"} }, - { "wallet", "listreceivedbyaddress", listreceivedbyaddress, {"minconf","include_empty","include_watchonly","address_filter"} }, - { "wallet", "listsinceblock", listsinceblock, {"blockhash","target_confirmations","include_watchonly"} }, - { "wallet", "listtransactions", listtransactions, {"account","count","skip","include_watchonly"} }, - { "wallet", "listunspent", listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, - { "wallet", "listwallets", listwallets, {} }, - { "wallet", "lockunspent", lockunspent, {"unlock","transactions"} }, - { "wallet", "move", movecmd, {"fromaccount","toaccount","amount","minconf","comment"} }, - { "wallet", "rescanblockchain", rescanblockchain, {"start_height", "stop_height"} }, - { "wallet", "sendfrom", sendfrom, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} }, - { "wallet", "sendmany", sendmany, {"fromaccount","amounts","minconf","comment","subtractfeefrom"} }, - { "wallet", "sendtoaddress", sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount"} }, - { "wallet", "setlabel", setlabel, {"address","label"} }, - { "wallet", "setaccount", setlabel, {"address","account"} }, - { "wallet", "settxfee", settxfee, {"amount"} }, - { "wallet", "signmessage", signmessage, {"address","message"} }, - { "wallet", "walletlock", walletlock, {} }, - { "wallet", "walletpassphrasechange", walletpassphrasechange, {"oldpassphrase","newpassphrase"} }, - { "wallet", "walletpassphrase", walletpassphrase, {"passphrase","timeout"} }, - { "generating", "generate", generate, {"nblocks","maxtries"} }, + // category name actor (function) argNames + // ------------------- ------------------------ ---------------------- ---------- + { "rawtransactions", "fundrawtransaction", fundrawtransaction, {"hexstring","options"} }, + { "hidden", "resendwallettransactions", resendwallettransactions, {} }, + { "wallet", "abandontransaction", abandontransaction, {"txid"} }, + { "wallet", "addmultisigaddress", addmultisigaddress, {"nrequired","keys","label|account"} }, + { "wallet", "backupwallet", backupwallet, {"destination"} }, + { "wallet", "encryptwallet", encryptwallet, {"passphrase"} }, + { "wallet", "getaccountaddress", getlabeladdress, {"account"} }, + { "wallet", "getlabeladdress", getlabeladdress, {"label"} }, + { "wallet", "getaccount", getaccount, {"address"} }, + { "wallet", "getaddressesbyaccount", getaddressesbyaccount, {"account"} }, + { "wallet", "getbalance", getbalance, {"account","minconf","include_watchonly"} }, + { "wallet", "getnewaddress", getnewaddress, {"label|account"} }, + { "wallet", "getrawchangeaddress", getrawchangeaddress, {} }, + { "wallet", "getreceivedbylabel", getreceivedbylabel, {"label","minconf"} }, + { "wallet", "getreceivedbyaccount", getreceivedbylabel, {"account","minconf"} }, + { "wallet", "getreceivedbyaddress", getreceivedbyaddress, {"address","minconf"} }, + { "wallet", "gettransaction", gettransaction, {"txid","include_watchonly"} }, + { "wallet", "getunconfirmedbalance", getunconfirmedbalance, {} }, + { "wallet", "getwalletinfo", getwalletinfo, {} }, + { "wallet", "keypoolrefill", keypoolrefill, {"newsize"} }, + { "wallet", "listaccounts", listaccounts, {"minconf","include_watchonly"} }, + { "wallet", "listaddressgroupings", listaddressgroupings, {} }, + { "wallet", "listlockunspent", listlockunspent, {} }, + { "wallet", "listreceivedbylabel", listreceivedbylabel, {"minconf","include_empty","include_watchonly"} }, + { "wallet", "listreceivedbyaccount", listreceivedbylabel, {"minconf","include_empty","include_watchonly"} }, + { "wallet", "listreceivedbyaddress", listreceivedbyaddress, {"minconf","include_empty","include_watchonly","address_filter"} }, + { "wallet", "listsinceblock", listsinceblock, {"blockhash","target_confirmations","include_watchonly"} }, + { "wallet", "listtransactions", listtransactions, {"account","count","skip","include_watchonly"} }, + { "wallet", "listunspent", listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, + { "wallet", "listwallets", listwallets, {} }, + { "wallet", "lockunspent", lockunspent, {"unlock","transactions"} }, + { "wallet", "move", movecmd, {"fromaccount","toaccount","amount","minconf","comment"} }, + { "wallet", "rescanblockchain", rescanblockchain, {"start_height", "stop_height"} }, + { "wallet", "sendfrom", sendfrom, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} }, + { "wallet", "sendmany", sendmany, {"fromaccount","amounts","minconf","comment","subtractfeefrom"} }, + { "wallet", "sendtoaddress", sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount"} }, + { "wallet", "setlabel", setlabel, {"address","label"} }, + { "wallet", "setaccount", setlabel, {"address","account"} }, + { "wallet", "settxfee", settxfee, {"amount"} }, + { "wallet", "signmessage", signmessage, {"address","message"} }, + { "wallet", "signrawtransactionwithwallet", signrawtransactionwithwallet, {"hextring","prevtxs","sighashtype"} }, + { "wallet", "walletlock", walletlock, {} }, + { "wallet", "walletpassphrasechange", walletpassphrasechange, {"oldpassphrase","newpassphrase"} }, + { "wallet", "walletpassphrase", walletpassphrase, {"passphrase","timeout"} }, + { "generating", "generate", generate, {"nblocks","maxtries"} }, }; // clang-format on diff --git a/test/functional/abc-high_priority_transaction.py b/test/functional/abc-high_priority_transaction.py --- a/test/functional/abc-high_priority_transaction.py +++ b/test/functional/abc-high_priority_transaction.py @@ -29,8 +29,8 @@ change = t['amount'] - fee outputs[addr] = satoshi_round(change) rawtx = node.createrawtransaction(inputs, outputs) - signresult = node.signrawtransaction( - rawtx, None, None, "NONE|FORKID") + signresult = node.signrawtransactionwithwallet( + rawtx, None, "NONE|FORKID") txid = node.sendrawtransaction(signresult["hex"], True) txids.append(txid) return txids diff --git a/test/functional/rpc_signrawtransaction.py b/test/functional/rpc_signrawtransaction.py --- a/test/functional/rpc_signrawtransaction.py +++ b/test/functional/rpc_signrawtransaction.py @@ -2,6 +2,7 @@ # Copyright (c) 2015-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. +"""Test transaction signing using the signrawtransactionwithwallet RPC.""" from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal, assert_raises_rpc_error @@ -35,7 +36,8 @@ outputs = {'mpLQjfK79b7CCV4VMJWEWAj5Mpx8Up5zxB': 0.1} rawTx = self.nodes[0].createrawtransaction(inputs, outputs) - rawTxSigned = self.nodes[0].signrawtransaction(rawTx, inputs, privKeys) + rawTxSigned = self.nodes[0].signrawtransactionwithkey( + rawTx, privKeys, inputs) # 1) The transaction has a complete set of signatures assert 'complete' in rawTxSigned @@ -92,8 +94,8 @@ assert_raises_rpc_error(-22, "TX decode failed", self.nodes[0].decoderawtransaction, rawTx + "00") - rawTxSigned = self.nodes[0].signrawtransaction( - rawTx, scripts, privKeys) + rawTxSigned = self.nodes[0].signrawtransactionwithkey( + rawTx, privKeys, scripts) # 3) The transaction has no complete set of signatures assert 'complete' in rawTxSigned diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -194,8 +194,8 @@ CScript([OP_DUP, OP_HASH160, addrHash, OP_EQUALVERIFY, OP_CHECKSIG]))) # Create a proper fee for the transaction to be mined ctx.vout[1].nValue -= int(fee_multiplier * node.calculate_fee(ctx)) - signresult = node.signrawtransaction( - ToHex(ctx), None, None, "NONE|FORKID") + signresult = node.signrawtransactionwithwallet( + ToHex(ctx), None, "NONE|FORKID") txid = node.sendrawtransaction(signresult["hex"], True) txids.append(txid) return txids diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -576,7 +576,7 @@ outputs[to_node.getnewaddress()] = float(amount) rawtx = from_node.createrawtransaction(inputs, outputs) - signresult = from_node.signrawtransaction(rawtx) + signresult = from_node.signrawtransactionwithwallet(rawtx) txid = from_node.sendrawtransaction(signresult["hex"], True) return (txid, signresult["hex"], fee) @@ -630,7 +630,8 @@ newtx = rawtx[0:92] newtx = newtx + txouts newtx = newtx + rawtx[94:] - signresult = node.signrawtransaction(newtx, None, None, "NONE|FORKID") + signresult = node.signrawtransactionwithwallet( + newtx, None, "NONE|FORKID") txid = node.sendrawtransaction(signresult["hex"], True) txids.append(txid) return txids diff --git a/test/functional/wallet_txn_clone.py b/test/functional/wallet_txn_clone.py --- a/test/functional/wallet_txn_clone.py +++ b/test/functional/wallet_txn_clone.py @@ -83,8 +83,8 @@ # Use a different signature hash type to sign. This creates an equivalent but malleated clone. # Don't send the clone anywhere yet - tx1_clone = self.nodes[0].signrawtransaction( - clone_raw, None, None, "ALL|FORKID|ANYONECANPAY") + tx1_clone = self.nodes[0].signrawtransactionwithwallet( + clone_raw, None, "ALL|FORKID|ANYONECANPAY") assert_equal(tx1_clone["complete"], True) # Have node0 mine a block, if requested: