From 58a8e28918025c28f19ba19cbaa4a72374162942 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Wed, 27 Jun 2018 17:02:07 -0700 Subject: Refactor transaction creation and transaction funding logic In preparation for more create transaction and fund transcation RPCs, refactor the transaction creation and funding logic into separate functions. --- src/rpc/rawtransaction.cpp | 134 +++++++++++++++++++++++---------------------- src/rpc/rawtransaction.h | 3 + 2 files changed, 73 insertions(+), 64 deletions(-) (limited to 'src/rpc') diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 63548bff05..c27601e47f 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -332,80 +332,25 @@ static UniValue verifytxoutproof(const JSONRPCRequest& request) return res; } -static UniValue createrawtransaction(const JSONRPCRequest& request) +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf) { - if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) { - throw std::runtime_error( - // clang-format off - "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable )\n" - "\nCreate a transaction spending the given inputs and creating new outputs.\n" - "Outputs can be addresses or data.\n" - "Returns hex-encoded raw transaction.\n" - "Note that the transaction's inputs are not signed, and\n" - "it is not stored in the wallet or transmitted to the network.\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" - "\nResult:\n" - "\"transaction\" (string) hex string of the transaction\n" - - "\nExamples:\n" - + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"address\\\":0.01}]\"") - + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") - + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"address\\\":0.01}]\"") - + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"") - // clang-format on - ); - } - - RPCTypeCheck(request.params, { - UniValue::VARR, - UniValueType(), // ARR or OBJ, checked later - UniValue::VNUM, - UniValue::VBOOL - }, true - ); - if (request.params[0].isNull() || request.params[1].isNull()) + if (inputs_in.isNull() || outputs_in.isNull()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, arguments 1 and 2 must be non-null"); - UniValue inputs = request.params[0].get_array(); - const bool outputs_is_obj = request.params[1].isObject(); - UniValue outputs = outputs_is_obj ? - request.params[1].get_obj() : - request.params[1].get_array(); + UniValue inputs = inputs_in.get_array(); + const bool outputs_is_obj = outputs_in.isObject(); + UniValue outputs = outputs_is_obj ? outputs_in.get_obj() : outputs_in.get_array(); CMutableTransaction rawTx; - if (!request.params[2].isNull()) { - int64_t nLockTime = request.params[2].get_int64(); + if (!locktime.isNull()) { + int64_t nLockTime = locktime.get_int64(); if (nLockTime < 0 || nLockTime > std::numeric_limits::max()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, locktime out of range"); rawTx.nLockTime = nLockTime; } - bool rbfOptIn = request.params[3].isTrue(); + bool rbfOptIn = rbf.isTrue(); for (unsigned int idx = 0; idx < inputs.size(); idx++) { const UniValue& input = inputs[idx]; @@ -485,10 +430,71 @@ static UniValue createrawtransaction(const JSONRPCRequest& request) } } - if (!request.params[3].isNull() && rbfOptIn != SignalsOptInRBF(rawTx)) { + if (!rbf.isNull() && rbfOptIn != SignalsOptInRBF(rawTx)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option"); } + return rawTx; +} + +static UniValue createrawtransaction(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) { + throw std::runtime_error( + // clang-format off + "createrawtransaction [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable )\n" + "\nCreate a transaction spending the given inputs and creating new outputs.\n" + "Outputs can be addresses or data.\n" + "Returns hex-encoded raw transaction.\n" + "Note that the transaction's inputs are not signed, and\n" + "it is not stored in the wallet or transmitted to the network.\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" + "\nResult:\n" + "\"transaction\" (string) hex string of the transaction\n" + + "\nExamples:\n" + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"address\\\":0.01}]\"") + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"address\\\":0.01}]\"") + + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + // clang-format on + ); + } + + RPCTypeCheck(request.params, { + UniValue::VARR, + UniValueType(), // ARR or OBJ, checked later + UniValue::VNUM, + UniValue::VBOOL + }, true + ); + + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]); + return EncodeHexTx(rawTx); } diff --git a/src/rpc/rawtransaction.h b/src/rpc/rawtransaction.h index ec9d1f2cf0..52dccc90e8 100644 --- a/src/rpc/rawtransaction.h +++ b/src/rpc/rawtransaction.h @@ -12,4 +12,7 @@ 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); +/** Create a transaction from univalue parameters */ +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, const UniValue& rbf); + #endif // BITCOIN_RPC_RAWTRANSACTION_H -- cgit v1.2.3 From c27fe419efb3b6588c400d764122ffb33375e028 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Thu, 28 Jun 2018 19:04:40 -0700 Subject: Create utility RPCs for PSBT decodepsbt takes a PSBT and decodes it to JSON combinepsbt takes multiple PSBTs for the same tx and combines them. finalizepsbt takes a PSBT and finalizes the inputs. If all inputs are final, it extracts the network serialized transaction and returns that instead of a PSBT unless instructed otherwise. createpsbt is like createrawtransaction but for PSBTs instead of raw transactions. convertpsbt takes a network serialized transaction and converts it into a psbt. The resulting psbt will lose all signature data and an explicit flag must be set to allow transactions with signature data to be converted. --- src/rpc/client.cpp | 8 + src/rpc/rawtransaction.cpp | 553 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 561 insertions(+) (limited to 'src/rpc') diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 0f35fd3770..d97165b301 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -109,6 +109,14 @@ static const CRPCConvertParam vRPCConvertParams[] = { "combinerawtransaction", 0, "txs" }, { "fundrawtransaction", 1, "options" }, { "fundrawtransaction", 2, "iswitness" }, + { "createpsbt", 0, "inputs" }, + { "createpsbt", 1, "outputs" }, + { "createpsbt", 2, "locktime" }, + { "createpsbt", 3, "replaceable" }, + { "combinepsbt", 0, "txs"}, + { "finalizepsbt", 1, "extract"}, + { "converttopsbt", 1, "permitsigdata"}, + { "converttopsbt", 2, "iswitness"}, { "gettxout", 1, "n" }, { "gettxout", 2, "include_mempool" }, { "gettxoutproof", 0, "txids" }, diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index c27601e47f..797c8b6973 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -1259,6 +1260,553 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request) return result; } +static std::string WriteHDKeypath(std::vector& keypath) +{ + std::string keypath_str = "m"; + for (uint32_t num : keypath) { + keypath_str += "/"; + bool hardened = false; + if (num & 0x80000000) { + hardened = true; + num &= ~0x80000000; + } + + keypath_str += std::to_string(num); + if (hardened) { + keypath_str += "'"; + } + } + return keypath_str; +} + +UniValue decodepsbt(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "decodepsbt \"psbt\"\n" + "\nReturn a JSON object representing the serialized, base64-encoded partially signed Bitcoin transaction.\n" + + "\nArguments:\n" + "1. \"psbt\" (string, required) The PSBT base64 string\n" + + "\nResult:\n" + "{\n" + " \"tx\" : { (json object) The decoded network-serialized unsigned transaction.\n" + " ... The layout is the same as the output of decoderawtransaction.\n" + " },\n" + " \"unknown\" : { (json object) The unknown global fields\n" + " \"key\" : \"value\" (key-value pair) An unknown key-value pair\n" + " ...\n" + " },\n" + " \"inputs\" : [ (array of json objects)\n" + " {\n" + " \"non_witness_utxo\" : { (json object, optional) Decoded network transaction for non-witness UTXOs\n" + " ...\n" + " },\n" + " \"witness_utxo\" : { (json object, optional) Transaction output for witness UTXOs\n" + " \"amount\" : x.xxx, (numeric) The value in " + CURRENCY_UNIT + "\n" + " \"scriptPubKey\" : { (json object)\n" + " \"asm\" : \"asm\", (string) The asm\n" + " \"hex\" : \"hex\", (string) The hex\n" + " \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n" + " \"address\" : \"address\" (string) Bitcoin address if there is one\n" + " }\n" + " },\n" + " \"partial_signatures\" : { (json object, optional)\n" + " \"pubkey\" : \"signature\", (string) The public key and signature that corresponds to it.\n" + " ,...\n" + " }\n" + " \"sighash\" : \"type\", (string, optional) The sighash type to be used\n" + " \"redeem_script\" : { (json object, optional)\n" + " \"asm\" : \"asm\", (string) The asm\n" + " \"hex\" : \"hex\", (string) The hex\n" + " \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n" + " }\n" + " \"witness_script\" : { (json object, optional)\n" + " \"asm\" : \"asm\", (string) The asm\n" + " \"hex\" : \"hex\", (string) The hex\n" + " \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n" + " }\n" + " \"bip32_derivs\" : { (json object, optional)\n" + " \"pubkey\" : { (json object, optional) The public key with the derivation path as the value.\n" + " \"master_fingerprint\" : \"fingerprint\" (string) The fingerprint of the master key\n" + " \"path\" : \"path\", (string) The path\n" + " }\n" + " ,...\n" + " }\n" + " \"final_scriptsig\" : { (json object, optional)\n" + " \"asm\" : \"asm\", (string) The asm\n" + " \"hex\" : \"hex\", (string) The hex\n" + " }\n" + " \"final_scriptwitness\": [\"hex\", ...] (array of string) hex-encoded witness data (if any)\n" + " \"unknown\" : { (json object) The unknown global fields\n" + " \"key\" : \"value\" (key-value pair) An unknown key-value pair\n" + " ...\n" + " },\n" + " }\n" + " ,...\n" + " ]\n" + " \"outputs\" : [ (array of json objects)\n" + " {\n" + " \"redeem_script\" : { (json object, optional)\n" + " \"asm\" : \"asm\", (string) The asm\n" + " \"hex\" : \"hex\", (string) The hex\n" + " \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n" + " }\n" + " \"witness_script\" : { (json object, optional)\n" + " \"asm\" : \"asm\", (string) The asm\n" + " \"hex\" : \"hex\", (string) The hex\n" + " \"type\" : \"pubkeyhash\", (string) The type, eg 'pubkeyhash'\n" + " }\n" + " \"bip32_derivs\" : [ (array of json objects, optional)\n" + " {\n" + " \"pubkey\" : \"pubkey\", (string) The public key this path corresponds to\n" + " \"master_fingerprint\" : \"fingerprint\" (string) The fingerprint of the master key\n" + " \"path\" : \"path\", (string) The path\n" + " }\n" + " }\n" + " ,...\n" + " ],\n" + " \"unknown\" : { (json object) The unknown global fields\n" + " \"key\" : \"value\" (key-value pair) An unknown key-value pair\n" + " ...\n" + " },\n" + " }\n" + " ,...\n" + " ]\n" + " \"fee\" : fee (numeric, optional) The transaction fee paid if all UTXOs slots in the PSBT have been filled.\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("decodepsbt", "\"psbt\"") + ); + + RPCTypeCheck(request.params, {UniValue::VSTR}); + + // Unserialize the transactions + 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)); + } + + UniValue result(UniValue::VOBJ); + + // Add the decoded tx + UniValue tx_univ(UniValue::VOBJ); + TxToUniv(CTransaction(*psbtx.tx), uint256(), tx_univ, false); + result.pushKV("tx", tx_univ); + + // Unknown data + UniValue unknowns(UniValue::VOBJ); + for (auto entry : psbtx.unknown) { + unknowns.pushKV(HexStr(entry.first), HexStr(entry.second)); + } + result.pushKV("unknown", unknowns); + + // inputs + CAmount total_in = 0; + bool have_all_utxos = true; + UniValue inputs(UniValue::VARR); + for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) { + const PSBTInput& input = psbtx.inputs[i]; + UniValue in(UniValue::VOBJ); + // UTXOs + if (!input.witness_utxo.IsNull()) { + const CTxOut& txout = input.witness_utxo; + + UniValue out(UniValue::VOBJ); + + out.pushKV("amount", ValueFromAmount(txout.nValue)); + total_in += txout.nValue; + + UniValue o(UniValue::VOBJ); + ScriptToUniv(txout.scriptPubKey, o, true); + out.pushKV("scriptPubKey", o); + in.pushKV("witness_utxo", out); + } else if (input.non_witness_utxo) { + UniValue non_wit(UniValue::VOBJ); + TxToUniv(*input.non_witness_utxo, uint256(), non_wit, false); + in.pushKV("non_witness_utxo", non_wit); + total_in += input.non_witness_utxo->vout[psbtx.tx->vin[i].prevout.n].nValue; + } else { + have_all_utxos = false; + } + + // Partial sigs + if (!input.partial_sigs.empty()) { + UniValue partial_sigs(UniValue::VOBJ); + for (const auto& sig : input.partial_sigs) { + partial_sigs.pushKV(HexStr(sig.second.first), HexStr(sig.second.second)); + } + in.pushKV("partial_signatures", partial_sigs); + } + + // Sighash + if (input.sighash_type > 0) { + in.pushKV("sighash", SighashToStr((unsigned char)input.sighash_type)); + } + + // Redeem script and witness script + if (!input.redeem_script.empty()) { + UniValue r(UniValue::VOBJ); + ScriptToUniv(input.redeem_script, r, false); + in.pushKV("redeem_script", r); + } + if (!input.witness_script.empty()) { + UniValue r(UniValue::VOBJ); + ScriptToUniv(input.witness_script, r, false); + in.pushKV("witness_script", r); + } + + // keypaths + if (!input.hd_keypaths.empty()) { + UniValue keypaths(UniValue::VARR); + for (auto entry : input.hd_keypaths) { + UniValue keypath(UniValue::VOBJ); + keypath.pushKV("pubkey", HexStr(entry.first)); + + uint32_t fingerprint = entry.second.at(0); + keypath.pushKV("master_fingerprint", strprintf("%08x", bswap_32(fingerprint))); + + entry.second.erase(entry.second.begin()); + keypath.pushKV("path", WriteHDKeypath(entry.second)); + keypaths.push_back(keypath); + } + in.pushKV("bip32_derivs", keypaths); + } + + // Final scriptSig and scriptwitness + if (!input.final_script_sig.empty()) { + UniValue scriptsig(UniValue::VOBJ); + scriptsig.pushKV("asm", ScriptToAsmStr(input.final_script_sig, true)); + scriptsig.pushKV("hex", HexStr(input.final_script_sig)); + in.pushKV("final_scriptSig", scriptsig); + } + if (!input.final_script_witness.IsNull()) { + UniValue txinwitness(UniValue::VARR); + for (const auto& item : input.final_script_witness.stack) { + txinwitness.push_back(HexStr(item.begin(), item.end())); + } + in.pushKV("final_scriptwitness", txinwitness); + } + + // Unknown data + if (input.unknown.size() > 0) { + UniValue unknowns(UniValue::VOBJ); + for (auto entry : input.unknown) { + unknowns.pushKV(HexStr(entry.first), HexStr(entry.second)); + } + in.pushKV("unknown", unknowns); + } + + inputs.push_back(in); + } + result.pushKV("inputs", inputs); + + // outputs + CAmount output_value = 0; + UniValue outputs(UniValue::VARR); + for (unsigned int i = 0; i < psbtx.outputs.size(); ++i) { + const PSBTOutput& output = psbtx.outputs[i]; + UniValue out(UniValue::VOBJ); + // Redeem script and witness script + if (!output.redeem_script.empty()) { + UniValue r(UniValue::VOBJ); + ScriptToUniv(output.redeem_script, r, false); + out.pushKV("redeem_script", r); + } + if (!output.witness_script.empty()) { + UniValue r(UniValue::VOBJ); + ScriptToUniv(output.witness_script, r, false); + out.pushKV("witness_script", r); + } + + // keypaths + if (!output.hd_keypaths.empty()) { + UniValue keypaths(UniValue::VARR); + for (auto entry : output.hd_keypaths) { + UniValue keypath(UniValue::VOBJ); + keypath.pushKV("pubkey", HexStr(entry.first)); + + uint32_t fingerprint = entry.second.at(0); + keypath.pushKV("master_fingerprint", strprintf("%08x", bswap_32(fingerprint))); + + entry.second.erase(entry.second.begin()); + keypath.pushKV("path", WriteHDKeypath(entry.second)); + keypaths.push_back(keypath); + } + out.pushKV("bip32_derivs", keypaths); + } + + // Unknown data + if (output.unknown.size() > 0) { + UniValue unknowns(UniValue::VOBJ); + for (auto entry : output.unknown) { + unknowns.pushKV(HexStr(entry.first), HexStr(entry.second)); + } + out.pushKV("unknown", unknowns); + } + + outputs.push_back(out); + + // Fee calculation + output_value += psbtx.tx->vout[i].nValue; + } + result.pushKV("outputs", outputs); + if (have_all_utxos) { + result.pushKV("fee", ValueFromAmount(total_in - output_value)); + } + + return result; +} + +UniValue combinepsbt(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() != 1) + throw std::runtime_error( + "combinepsbt [\"psbt\",...]\n" + "\nCombine multiple partially signed Bitcoin transactions into one transaction.\n" + "Implements the Combiner role.\n" + "\nArguments:\n" + "1. \"txs\" (string) A json array of base64 strings of partially signed transactions\n" + " [\n" + " \"psbt\" (string) A base64 string of a PSBT\n" + " ,...\n" + " ]\n" + + "\nResult:\n" + " \"psbt\" (string) The base64-encoded partially signed transaction\n" + "\nExamples:\n" + + HelpExampleCli("combinepsbt", "[\"mybase64_1\", \"mybase64_2\", \"mybase64_3\"]") + ); + + RPCTypeCheck(request.params, {UniValue::VARR}, true); + + // Unserialize the transactions + std::vector psbtxs; + UniValue txs = request.params[0].get_array(); + for (unsigned int i = 0; i < txs.size(); ++i) { + PartiallySignedTransaction psbtx; + std::string error; + if (!DecodePSBT(psbtx, txs[i].get_str(), error)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); + } + psbtxs.push_back(psbtx); + } + + PartiallySignedTransaction merged_psbt(psbtxs[0]); // Copy the first one + + // Merge + for (auto it = std::next(psbtxs.begin()); it != psbtxs.end(); ++it) { + if (*it != merged_psbt) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "PSBTs do not refer to the same transactions."); + } + merged_psbt.Merge(*it); + } + if (!merged_psbt.IsSane()) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Merged PSBT is inconsistent"); + } + + UniValue result(UniValue::VOBJ); + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << merged_psbt; + return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()); +} + +UniValue finalizepsbt(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) + throw std::runtime_error( + "finalizepsbt \"psbt\" ( extract )\n" + "Finalize the inputs of a PSBT. If the transaction is fully signed, it will produce a\n" + "network serialized transaction which can be broadcast with sendrawtransaction. Otherwise a PSBT will be\n" + "created which has the final_scriptSig and final_scriptWitness fields filled for inputs that are complete.\n" + "Implements the Finalizer and Extractor roles.\n" + "\nArguments:\n" + "1. \"psbt\" (string) A base64 string of a PSBT\n" + "2. \"extract\" (boolean, optional, default=true) If true and the transaction is complete, \n" + " extract and return the complete transaction in normal network serialization instead of the PSBT.\n" + + "\nResult:\n" + "{\n" + " \"psbt\" : \"value\", (string) The base64-encoded partially signed transaction if not extracted\n" + " \"hex\" : \"value\", (string) The hex-encoded network transaction if extracted\n" + " \"complete\" : true|false, (boolean) If the transaction has a complete set of signatures\n" + " ]\n" + "}\n" + + "\nExamples:\n" + + HelpExampleCli("finalizepsbt", "\"psbt\"") + ); + + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true); + + // Unserialize the transactions + 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 all of the previous transactions + bool complete = true; + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { + PSBTInput& input = psbtx.inputs.at(i); + + SignatureData sigdata; + complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, *psbtx.tx, input, sigdata, i, 1); + } + + UniValue result(UniValue::VOBJ); + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + bool extract = request.params[1].isNull() || (!request.params[1].isNull() && request.params[1].get_bool()); + if (complete && extract) { + CMutableTransaction mtx(*psbtx.tx); + for (unsigned int i = 0; i < mtx.vin.size(); ++i) { + mtx.vin[i].scriptSig = psbtx.inputs[i].final_script_sig; + mtx.vin[i].scriptWitness = psbtx.inputs[i].final_script_witness; + } + ssTx << mtx; + result.push_back(Pair("hex", HexStr(ssTx.begin(), ssTx.end()))); + } else { + ssTx << psbtx; + result.push_back(Pair("psbt", EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()))); + } + result.push_back(Pair("complete", complete)); + + return result; +} + +UniValue createpsbt(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() < 2 || request.params.size() > 4) + throw std::runtime_error( + "createpsbt [{\"txid\":\"id\",\"vout\":n},...] [{\"address\":amount},{\"data\":\"hex\"},...] ( locktime ) ( replaceable )\n" + "\nCreates a transaction in the Partially Signed Transaction format.\n" + "Implements the Creator role.\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" + "\nResult:\n" + " \"psbt\" (string) The resulting raw transaction (base64-encoded string)\n" + "\nExamples:\n" + + HelpExampleCli("createpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + ); + + + RPCTypeCheck(request.params, { + UniValue::VARR, + UniValueType(), // ARR or OBJ, checked later + UniValue::VNUM, + UniValue::VBOOL, + }, true + ); + + CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], request.params[3]); + + // 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()); + } + + // Serialize the PSBT + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + + return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()); +} + +UniValue converttopsbt(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) + throw std::runtime_error( + "converttopsbt \"hexstring\" ( permitsigdata iswitness )\n" + "\nConverts a network serialized transaction to a PSBT. This should be used only with createrawtransaction and fundrawtransaction\n" + "createpsbt and walletcreatefundedpsbt should be used for new applications.\n" + "\nArguments:\n" + "1. \"hexstring\" (string, required) The hex string of a raw transaction\n" + "2. permitsigdata (boolean, optional, default=false) If true, any signatures in the input will be discarded and conversion.\n" + " will continue. If false, RPC will fail if any signatures are present.\n" + "3. iswitness (boolean, optional) Whether the transaction hex is a serialized witness transaction.\n" + " If iswitness is not present, heuristic tests will be used in decoding. If true, only witness deserializaion\n" + " will be tried. If false, only non-witness deserialization wil be tried. Only has an effect if\n" + " permitsigdata is true.\n" + "\nResult:\n" + " \"psbt\" (string) The resulting raw transaction (base64-encoded string)\n" + "\nExamples:\n" + "\nCreate a transaction\n" + + HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") + + "\nConvert the transaction to a PSBT\n" + + HelpExampleCli("converttopsbt", "\"rawtransaction\"") + ); + + + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VBOOL}, true); + + // parse hex string from parameter + CMutableTransaction tx; + bool permitsigdata = request.params[1].isNull() ? false : request.params[1].get_bool(); + bool witness_specified = !request.params[2].isNull(); + bool iswitness = witness_specified ? request.params[2].get_bool() : false; + bool try_witness = permitsigdata ? (witness_specified ? iswitness : true) : false; + bool try_no_witness = permitsigdata ? (witness_specified ? !iswitness : true) : true; + if (!DecodeHexTx(tx, request.params[0].get_str(), try_no_witness, try_witness)) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); + } + + // Remove all scriptSigs and scriptWitnesses from inputs + for (CTxIn& input : tx.vin) { + if ((!input.scriptSig.empty() || !input.scriptWitness.IsNull()) && (request.params[1].isNull() || (!request.params[1].isNull() && request.params[1].get_bool()))) { + throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Inputs must not have scriptSigs and scriptWitnesses"); + } + input.scriptSig.clear(); + input.scriptWitness.SetNull(); + } + + // Make a blank psbt + PartiallySignedTransaction psbtx; + psbtx.tx = tx; + for (unsigned int i = 0; i < tx.vin.size(); ++i) { + psbtx.inputs.push_back(PSBTInput()); + } + for (unsigned int i = 0; i < tx.vout.size(); ++i) { + psbtx.outputs.push_back(PSBTOutput()); + } + + // Serialize the PSBT + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + + return EncodeBase64((unsigned char*)ssTx.data(), ssTx.size()); +} + static const CRPCCommand commands[] = { // category name actor (function) argNames // --------------------- ------------------------ ----------------------- ---------- @@ -1271,6 +1819,11 @@ static const CRPCCommand commands[] = { "rawtransactions", "signrawtransaction", &signrawtransaction, {"hexstring","prevtxs","privkeys","sighashtype"} }, /* uses wallet if enabled */ { "rawtransactions", "signrawtransactionwithkey", &signrawtransactionwithkey, {"hexstring","privkeys","prevtxs","sighashtype"} }, { "rawtransactions", "testmempoolaccept", &testmempoolaccept, {"rawtxs","allowhighfees"} }, + { "rawtransactions", "decodepsbt", &decodepsbt, {"psbt"} }, + { "rawtransactions", "combinepsbt", &combinepsbt, {"txs"} }, + { "rawtransactions", "finalizepsbt", &finalizepsbt, {"psbt", "extract"} }, + { "rawtransactions", "createpsbt", &createpsbt, {"inputs","outputs","locktime","replaceable"} }, + { "rawtransactions", "converttopsbt", &converttopsbt, {"hexstring","permitsigdata","iswitness"} }, { "blockchain", "gettxoutproof", &gettxoutproof, {"txids", "blockhash"} }, { "blockchain", "verifytxoutproof", &verifytxoutproof, {"proof"} }, -- cgit v1.2.3 From a4b06fb42eb0ad94e562ca839391b57e69285136 Mon Sep 17 00:00:00 2001 From: Andrew Chow Date: Thu, 28 Jun 2018 19:05:05 -0700 Subject: Create wallet RPCs for PSBT walletprocesspsbt takes a PSBT format transaction, updates the PSBT with any inputs related to this wallet, signs, and finalizes the transaction. There is also an option to not sign and just update. walletcreatefundedpsbt creates a PSBT from user provided data in the same form as createrawtransaction. It also funds the transaction and takes an options argument in the same form as fundrawtransaction. The resulting PSBT is blank with no input or output data filled in. --- src/rpc/client.cpp | 8 ++++++++ src/rpc/rawtransaction.cpp | 18 +----------------- 2 files changed, 9 insertions(+), 17 deletions(-) (limited to 'src/rpc') diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index d97165b301..96e0a96ab5 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -109,6 +109,14 @@ static const CRPCConvertParam vRPCConvertParams[] = { "combinerawtransaction", 0, "txs" }, { "fundrawtransaction", 1, "options" }, { "fundrawtransaction", 2, "iswitness" }, + { "walletcreatefundedpsbt", 0, "inputs" }, + { "walletcreatefundedpsbt", 1, "outputs" }, + { "walletcreatefundedpsbt", 2, "locktime" }, + { "walletcreatefundedpsbt", 3, "replaceable" }, + { "walletcreatefundedpsbt", 4, "options" }, + { "walletcreatefundedpsbt", 5, "bip32derivs" }, + { "walletprocesspsbt", 1, "sign" }, + { "walletprocesspsbt", 3, "bip32derivs" }, { "createpsbt", 0, "inputs" }, { "createpsbt", 1, "outputs" }, { "createpsbt", 2, "locktime" }, diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 797c8b6973..82dfe4d561 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -840,23 +840,7 @@ UniValue SignTransaction(CMutableTransaction& mtx, const UniValue& prevTxsUnival } } - int nHashType = SIGHASH_ALL; - if (!hashType.isNull()) { - static std::map mapSigHashValues = { - {std::string("ALL"), int(SIGHASH_ALL)}, - {std::string("ALL|ANYONECANPAY"), int(SIGHASH_ALL|SIGHASH_ANYONECANPAY)}, - {std::string("NONE"), int(SIGHASH_NONE)}, - {std::string("NONE|ANYONECANPAY"), int(SIGHASH_NONE|SIGHASH_ANYONECANPAY)}, - {std::string("SINGLE"), int(SIGHASH_SINGLE)}, - {std::string("SINGLE|ANYONECANPAY"), int(SIGHASH_SINGLE|SIGHASH_ANYONECANPAY)}, - }; - std::string strHashType = hashType.get_str(); - if (mapSigHashValues.count(strHashType)) { - nHashType = mapSigHashValues[strHashType]; - } else { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid sighash param"); - } - } + int nHashType = ParseSighashString(hashType); bool fHashSingle = ((nHashType & ~SIGHASH_ANYONECANPAY) == SIGHASH_SINGLE); -- cgit v1.2.3