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.
This commit is contained in:
parent
8b5ef27937
commit
c27fe419ef
5 changed files with 606 additions and 0 deletions
|
@ -14,6 +14,7 @@ class CBlock;
|
|||
class CScript;
|
||||
class CTransaction;
|
||||
struct CMutableTransaction;
|
||||
struct PartiallySignedTransaction;
|
||||
class uint256;
|
||||
class UniValue;
|
||||
|
||||
|
@ -24,12 +25,15 @@ bool DecodeHexTx(CMutableTransaction& tx, const std::string& hex_tx, bool try_no
|
|||
bool DecodeHexBlk(CBlock&, const std::string& strHexBlk);
|
||||
uint256 ParseHashStr(const std::string&, const std::string& strName);
|
||||
std::vector<unsigned char> ParseHexUV(const UniValue& v, const std::string& strName);
|
||||
bool DecodePSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error);
|
||||
|
||||
// core_write.cpp
|
||||
UniValue ValueFromAmount(const CAmount& amount);
|
||||
std::string FormatScript(const CScript& script);
|
||||
std::string EncodeHexTx(const CTransaction& tx, const int serializeFlags = 0);
|
||||
std::string SighashToStr(unsigned char sighash_type);
|
||||
void ScriptPubKeyToUniv(const CScript& scriptPubKey, UniValue& out, bool fIncludeHex);
|
||||
void ScriptToUniv(const CScript& script, UniValue& out, bool include_address);
|
||||
void TxToUniv(const CTransaction& tx, const uint256& hashBlock, UniValue& entry, bool include_hex = true, int serialize_flags = 0);
|
||||
|
||||
#endif // BITCOIN_CORE_IO_H
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <primitives/block.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <script/script.h>
|
||||
#include <script/sign.h>
|
||||
#include <serialize.h>
|
||||
#include <streams.h>
|
||||
#include <univalue.h>
|
||||
|
@ -160,6 +161,23 @@ bool DecodeHexBlk(CBlock& block, const std::string& strHexBlk)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DecodePSBT(PartiallySignedTransaction& psbt, const std::string& base64_tx, std::string& error)
|
||||
{
|
||||
std::vector<unsigned char> tx_data = DecodeBase64(base64_tx.c_str());
|
||||
CDataStream ss_data(tx_data, SER_NETWORK, PROTOCOL_VERSION);
|
||||
try {
|
||||
ss_data >> psbt;
|
||||
if (!ss_data.empty()) {
|
||||
error = "extra data after PSBT";
|
||||
return false;
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
error = e.what();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint256 ParseHashStr(const std::string& strHex, const std::string& strName)
|
||||
{
|
||||
if (!IsHex(strHex)) // Note: IsHex("") is false
|
||||
|
|
|
@ -70,6 +70,13 @@ const std::map<unsigned char, std::string> mapSigHashTypes = {
|
|||
{static_cast<unsigned char>(SIGHASH_SINGLE|SIGHASH_ANYONECANPAY), std::string("SINGLE|ANYONECANPAY")},
|
||||
};
|
||||
|
||||
std::string SighashToStr(unsigned char sighash_type)
|
||||
{
|
||||
const auto& it = mapSigHashTypes.find(sighash_type);
|
||||
if (it == mapSigHashTypes.end()) return "";
|
||||
return it->second;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the assembly string representation of a CScript object.
|
||||
* @param[in] script CScript object to convert into the asm string representation.
|
||||
|
@ -128,6 +135,22 @@ std::string EncodeHexTx(const CTransaction& tx, const int serializeFlags)
|
|||
return HexStr(ssTx.begin(), ssTx.end());
|
||||
}
|
||||
|
||||
void ScriptToUniv(const CScript& script, UniValue& out, bool include_address)
|
||||
{
|
||||
out.pushKV("asm", ScriptToAsmStr(script));
|
||||
out.pushKV("hex", HexStr(script.begin(), script.end()));
|
||||
|
||||
std::vector<std::vector<unsigned char>> solns;
|
||||
txnouttype type;
|
||||
Solver(script, type, solns);
|
||||
out.pushKV("type", GetTxnOutputType(type));
|
||||
|
||||
CTxDestination address;
|
||||
if (include_address && ExtractDestination(script, address)) {
|
||||
out.pushKV("address", EncodeDestination(address));
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptPubKeyToUniv(const CScript& scriptPubKey,
|
||||
UniValue& out, bool fIncludeHex)
|
||||
{
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <chain.h>
|
||||
#include <coins.h>
|
||||
#include <compat/byteswap.h>
|
||||
#include <consensus/validation.h>
|
||||
#include <core_io.h>
|
||||
#include <index/txindex.h>
|
||||
|
@ -1259,6 +1260,553 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request)
|
|||
return result;
|
||||
}
|
||||
|
||||
static std::string WriteHDKeypath(std::vector<uint32_t>& 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<PartiallySignedTransaction> 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"} },
|
||||
|
|
Loading…
Reference in a new issue