#include <base58.h>
#include <chain.h>
#include <coins.h>
#include <consensus/amount.h>
#include <consensus/validation.h>
#include <core_io.h>
#include <index/txindex.h>
#include <key_io.h>
#include <node/blockstorage.h>
#include <node/coin.h>
#include <node/context.h>
#include <node/psbt.h>
#include <node/transaction.h>
#include <node/types.h>
#include <policy/packages.h>
#include <policy/policy.h>
#include <policy/rbf.h>
#include <primitives/transaction.h>
#include <psbt.h>
#include <random.h>
#include <rpc/blockchain.h>
#include <rpc/rawtransaction_util.h>
#include <rpc/server.h>
#include <rpc/server_util.h>
#include <rpc/util.h>
#include <script/script.h>
#include <script/sign.h>
#include <script/signingprovider.h>
#include <script/solver.h>
#include <uint256.h>
#include <undo.h>
#include <util/bip32.h>
#include <util/check.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/vector.h>
#include <validation.h>
#include <validationinterface.h>
#include <cstdint>
#include <numeric>
#include <univalue.h>
using node::AnalyzePSBT;
using node::FindCoins;
using node::GetTransaction;
using node::NodeContext;
using node::PSBTAnalysis;
static constexpr decltype(CTransaction::version) DEFAULT_RAWTX_VERSION{CTransaction::CURRENT_VERSION};
static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry,
Chainstate& active_chainstate, const CTxUndo* txundo = nullptr,
TxVerbosity verbosity = TxVerbosity::SHOW_DETAILS)
{
CHECK_NONFATAL(verbosity >= TxVerbosity::SHOW_DETAILS);
TxToUniv(tx, uint256(), entry, true, txundo, verbosity);
if (!hashBlock.IsNull()) {
LOCK(cs_main);
entry.pushKV("blockhash", hashBlock.GetHex());
const CBlockIndex* pindex = active_chainstate.m_blockman.LookupBlockIndex(hashBlock);
if (pindex) {
if (active_chainstate.m_chain.Contains(*pindex)) {
entry.pushKV("confirmations", 1 + active_chainstate.m_chain.Height() - pindex->nHeight);
entry.pushKV("time", pindex->GetBlockTime());
entry.pushKV("blocktime", pindex->GetBlockTime());
}
else
entry.pushKV("confirmations", 0);
}
}
}
static std::vector<RPCArg> CreateTxDoc()
{
return {
{"inputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The inputs",
{
{"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
{"sequence", RPCArg::Type::NUM, RPCArg::DefaultHint{"depends on the value of the 'replaceable' and 'locktime' arguments"}, "The sequence number"},
},
},
},
},
{"outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The outputs specified as key-value pairs.\n"
"Each key may only appear once, i.e. there can only be one 'data' output, and no address may be duplicated.\n"
"At least one output of either type must be specified.\n"
"For compatibility reasons, a dictionary, which holds the key-value pairs directly, is also\n"
" accepted as second parameter.",
{
{"", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED, "",
{
{"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the bitcoin address, the value (float or string) is the amount in " + CURRENCY_UNIT},
},
},
{"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
{
{"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A key-value pair. The key must be \"data\", the value is hex-encoded data that becomes a part of an OP_RETURN output"},
},
},
},
RPCArgOptions{.skip_type_check = true}},
{"locktime", RPCArg::Type::NUM, RPCArg::Default{0}, "Raw locktime. Non-0 value also locktime-activates inputs"},
{"replaceable", RPCArg::Type::BOOL, RPCArg::Default{true}, "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."},
{"version", RPCArg::Type::NUM, RPCArg::Default{DEFAULT_RAWTX_VERSION}, "Transaction version"},
};
}
PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std::any& context, const HidingSigningProvider& provider, std::optional<int> sighash_type, bool finalize)
{
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, psbt_string, error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
}
if (g_txindex) g_txindex->BlockUntilSyncedToCurrentChain();
const NodeContext& node = EnsureAnyNodeContext(context);
std::map<COutPoint, Coin> coins;
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
PSBTInput& psbt_input = psbtx.inputs.at(i);
const CTxIn& tx_in = psbtx.tx->vin.at(i);
if (psbt_input.non_witness_utxo) continue;
CTransactionRef tx;
if (g_txindex) {
uint256 block_hash;
g_txindex->FindTx(tx_in.prevout.hash, block_hash, tx);
}
if (!tx) {
tx = node.mempool->get(tx_in.prevout.hash);
}
if (tx) {
psbt_input.non_witness_utxo = tx;
} else {
coins[tx_in.prevout]; }
}
if (!coins.empty()) {
FindCoins(node, coins);
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
PSBTInput& input = psbtx.inputs.at(i);
if (!input.non_witness_utxo) {
const CTxIn& tx_in = psbtx.tx->vin.at(i);
const Coin& coin = coins.at(tx_in.prevout);
if (!coin.out.IsNull() && IsSegWitOutput(provider, coin.out.scriptPubKey)) {
input.witness_utxo = coin.out;
}
}
}
}
const PrecomputedTransactionData& txdata = PrecomputePSBTData(psbtx);
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
if (PSBTInputSigned(psbtx.inputs.at(i))) {
continue;
}
if (SignPSBTInput(provider, psbtx, i, &txdata, {.sighash_type = sighash_type, .finalize = finalize}, nullptr) == common::PSBTError::SIGHASH_MISMATCH) {
throw JSONRPCPSBTError(common::PSBTError::SIGHASH_MISMATCH);
}
}
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
UpdatePSBTOutput(provider, psbtx, i);
}
RemoveUnnecessaryTransactions(psbtx);
return psbtx;
}
static RPCMethod getrawtransaction()
{
return RPCMethod{
"getrawtransaction",
"By default, this call only returns a transaction if it is in the mempool. If -txindex is enabled\n"
"and no blockhash argument is passed, it will return the transaction if it is in the mempool or any block.\n"
"If a blockhash argument is passed, it will return the transaction if\n"
"the specified block is available and the transaction is in that block.\n\n"
"Hint: Use gettransaction for wallet transactions.\n\n"
"If verbosity is 0 or omitted, returns the serialized transaction as a hex-encoded string.\n"
"If verbosity is 1, returns a JSON Object with information about the transaction.\n"
"If verbosity is 2, returns a JSON Object with information about the transaction, including fee and prevout information.",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
{"verbosity|verbose", RPCArg::Type::NUM, RPCArg::Default{0}, "0 for hex-encoded data, 1 for a JSON object, and 2 for JSON object with fee and prevout",
RPCArgOptions{.skip_type_check = true}},
{"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "The block in which to look for the transaction"},
},
{
RPCResult{"if verbosity is not set or set to 0",
RPCResult::Type::STR, "data", "The serialized transaction as a hex-encoded string for 'txid'"
},
RPCResult{"if verbosity is set to 1",
RPCResult::Type::OBJ, "", "",
Cat<std::vector<RPCResult>>(
{
{RPCResult::Type::BOOL, "in_active_chain", true, "Whether specified block is in the active chain or not (only present with explicit \"blockhash\" argument)"},
{RPCResult::Type::STR_HEX, "blockhash", true, "the block hash"},
{RPCResult::Type::NUM, "confirmations", true, "The confirmations"},
{RPCResult::Type::NUM_TIME, "blocktime", true, "The block time expressed in " + UNIX_EPOCH_TIME},
{RPCResult::Type::NUM, "time", true, "Same as \"blocktime\""},
{RPCResult::Type::STR_HEX, "hex", "The serialized, hex-encoded data for 'txid'"},
},
TxDoc({.txid_field_doc="The transaction id (same as provided)"})),
},
RPCResult{"for verbosity = 2",
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::ELISION, "", "Same output as verbosity = 1"},
{RPCResult::Type::NUM, "fee", true, "transaction fee in " + CURRENCY_UNIT + ", omitted if block undo data is not available"},
{RPCResult::Type::ARR, "vin", "",
{
{RPCResult::Type::OBJ, "", "utxo being spent",
{
{RPCResult::Type::ELISION, "", "Same output as verbosity = 1"},
{RPCResult::Type::OBJ, "prevout", true, "The previous output, omitted if block undo data is not available",
{
{RPCResult::Type::BOOL, "generated", "Coinbase or not"},
{RPCResult::Type::NUM, "height", "The height of the prevout"},
{RPCResult::Type::STR_AMOUNT, "value", "The value in " + CURRENCY_UNIT},
{RPCResult::Type::OBJ, "scriptPubKey", "", ScriptPubKeyDoc()},
}},
}},
}},
}},
},
RPCExamples{
HelpExampleCli("getrawtransaction", "\"mytxid\"")
+ HelpExampleCli("getrawtransaction", "\"mytxid\" 1")
+ HelpExampleRpc("getrawtransaction", "\"mytxid\", 1")
+ HelpExampleCli("getrawtransaction", "\"mytxid\" 0 \"myblockhash\"")
+ HelpExampleCli("getrawtransaction", "\"mytxid\" 1 \"myblockhash\"")
+ HelpExampleCli("getrawtransaction", "\"mytxid\" 2 \"myblockhash\"")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
const NodeContext& node = EnsureAnyNodeContext(request.context);
ChainstateManager& chainman = EnsureChainman(node);
auto txid{Txid::FromUint256(ParseHashV(request.params[0], "parameter 1"))};
const CBlockIndex* blockindex = nullptr;
if (txid.ToUint256() == chainman.GetParams().GenesisBlock().hashMerkleRoot) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The genesis block coinbase is not considered an ordinary transaction and cannot be retrieved");
}
int verbosity{ParseVerbosity(request.params[1], 0, true)};
if (!request.params[2].isNull()) {
LOCK(cs_main);
uint256 blockhash = ParseHashV(request.params[2], "parameter 3");
blockindex = chainman.m_blockman.LookupBlockIndex(blockhash);
if (!blockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block hash not found");
}
}
bool f_txindex_ready = false;
if (g_txindex && !blockindex) {
f_txindex_ready = g_txindex->BlockUntilSyncedToCurrentChain();
}
uint256 hash_block;
const CTransactionRef tx = GetTransaction(blockindex, node.mempool.get(), txid, chainman.m_blockman, hash_block);
if (!tx) {
std::string errmsg;
if (blockindex) {
const bool block_has_data = WITH_LOCK(::cs_main, return blockindex->nStatus & BLOCK_HAVE_DATA);
if (!block_has_data) {
throw JSONRPCError(RPC_MISC_ERROR, "Block not available");
}
errmsg = "No such transaction found in the provided block";
} else if (!g_txindex) {
errmsg = "No such mempool transaction. Use -txindex or provide a block hash to enable blockchain transaction queries";
} else if (!f_txindex_ready) {
errmsg = "No such mempool transaction. Blockchain transactions are still in the process of being indexed";
} else {
errmsg = "No such mempool or blockchain transaction";
}
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, errmsg + ". Use gettransaction for wallet transactions.");
}
if (verbosity <= 0) {
return EncodeHexTx(*tx);
}
UniValue result(UniValue::VOBJ);
if (blockindex) {
LOCK(cs_main);
result.pushKV("in_active_chain", chainman.ActiveChain().Contains(*blockindex));
}
if (request.params[2].isNull()) {
LOCK(cs_main);
blockindex = chainman.m_blockman.LookupBlockIndex(hash_block); }
if (verbosity == 1) {
TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate());
return result;
}
CBlockUndo blockUndo;
CBlock block;
if (tx->IsCoinBase() || !blockindex || WITH_LOCK(::cs_main, return !(blockindex->nStatus & BLOCK_HAVE_MASK))) {
TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate());
return result;
}
if (!chainman.m_blockman.ReadBlockUndo(blockUndo, *blockindex)) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Undo data expected but can't be read. This could be due to disk corruption or a conflict with a pruning event.");
}
if (!chainman.m_blockman.ReadBlock(block, *blockindex)) {
throw JSONRPCError(RPC_INTERNAL_ERROR, "Block data expected but can't be read. This could be due to disk corruption or a conflict with a pruning event.");
}
CTxUndo* undoTX {nullptr};
auto it = std::find_if(block.vtx.begin(), block.vtx.end(), [tx](CTransactionRef t){ return *t == *tx; });
if (it != block.vtx.end()) {
undoTX = &blockUndo.vtxundo.at(it - block.vtx.begin() - 1);
}
TxToJSON(*tx, hash_block, result, chainman.ActiveChainstate(), undoTX, TxVerbosity::SHOW_DETAILS_AND_PREVOUT);
return result;
},
};
}
static RPCMethod createrawtransaction()
{
return RPCMethod{
"createrawtransaction",
"Create 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",
CreateTxDoc(),
RPCResult{
RPCResult::Type::STR_HEX, "transaction", "hex string of the transaction"
},
RPCExamples{
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\\\"}]\"")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
std::optional<bool> rbf;
if (!request.params[3].isNull()) {
rbf = request.params[3].get_bool();
}
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf, self.Arg<uint32_t>("version"));
return EncodeHexTx(CTransaction(rawTx));
},
};
}
static RPCMethod decoderawtransaction()
{
return RPCMethod{"decoderawtransaction",
"Return a JSON object representing the serialized, hex-encoded transaction.",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction hex string"},
{"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"depends on heuristic tests"}, "Whether the transaction hex is a serialized witness transaction.\n"
"If iswitness is not present, heuristic tests will be used in decoding.\n"
"If true, only witness deserialization will be tried.\n"
"If false, only non-witness deserialization will be tried.\n"
"This boolean should reflect whether the transaction has inputs\n"
"(e.g. fully valid, or on-chain transactions), if known by the caller."
},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
TxDoc(),
},
RPCExamples{
HelpExampleCli("decoderawtransaction", "\"hexstring\"")
+ HelpExampleRpc("decoderawtransaction", "\"hexstring\"")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
CMutableTransaction mtx;
bool try_witness = request.params[1].isNull() ? true : request.params[1].get_bool();
bool try_no_witness = request.params[1].isNull() ? true : !request.params[1].get_bool();
if (!DecodeHexTx(mtx, request.params[0].get_str(), try_no_witness, try_witness)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
UniValue result(UniValue::VOBJ);
TxToUniv(CTransaction(std::move(mtx)), uint256(), result, false);
return result;
},
};
}
static RPCMethod decodescript()
{
return RPCMethod{
"decodescript",
"Decode a hex-encoded script.\n",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hex-encoded script"},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "asm", "Disassembly of the script"},
{RPCResult::Type::STR, "desc", "Inferred descriptor for the script"},
{RPCResult::Type::STR, "type", "The output type (e.g. " + GetAllOutputTypes() + ")"},
{RPCResult::Type::STR, "address", true, "The Bitcoin address (only if a well-defined address exists)"},
{RPCResult::Type::STR, "p2sh", true,
"address of P2SH script wrapping this redeem script (not returned for types that should not be wrapped)"},
{RPCResult::Type::OBJ, "segwit", true,
"Result of a witness output script wrapping this redeem script (not returned for types that should not be wrapped)",
{
{RPCResult::Type::STR, "asm", "Disassembly of the output script"},
{RPCResult::Type::STR_HEX, "hex", "The raw output script bytes, hex-encoded"},
{RPCResult::Type::STR, "type", "The type of the output script (e.g. witness_v0_keyhash or witness_v0_scripthash)"},
{RPCResult::Type::STR, "address", true, "The Bitcoin address (only if a well-defined address exists)"},
{RPCResult::Type::STR, "desc", "Inferred descriptor for the script"},
{RPCResult::Type::STR, "p2sh-segwit", "address of the P2SH script wrapping this witness redeem script"},
}},
},
},
RPCExamples{
HelpExampleCli("decodescript", "\"hexstring\"")
+ HelpExampleRpc("decodescript", "\"hexstring\"")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
UniValue r(UniValue::VOBJ);
CScript script;
if (request.params[0].get_str().size() > 0){
std::vector<unsigned char> scriptData(ParseHexV(request.params[0], "argument"));
script = CScript(scriptData.begin(), scriptData.end());
} else {
}
ScriptToUniv(script, r, false, true);
std::vector<std::vector<unsigned char>> solutions_data;
const TxoutType which_type{Solver(script, solutions_data)};
const bool can_wrap{[&] {
switch (which_type) {
case TxoutType::MULTISIG:
case TxoutType::NONSTANDARD:
case TxoutType::PUBKEY:
case TxoutType::PUBKEYHASH:
case TxoutType::WITNESS_V0_KEYHASH:
case TxoutType::WITNESS_V0_SCRIPTHASH:
break;
case TxoutType::NULL_DATA:
case TxoutType::SCRIPTHASH:
case TxoutType::WITNESS_UNKNOWN:
case TxoutType::WITNESS_V1_TAPROOT:
case TxoutType::ANCHOR:
return false;
} if (!script.HasValidOps() || script.IsUnspendable()) {
return false;
}
for (CScript::const_iterator it{script.begin()}; it != script.end();) {
opcodetype op;
CHECK_NONFATAL(script.GetOp(it, op));
if (op == OP_CHECKSIGADD || IsOpSuccess(op)) {
return false;
}
}
return true;
}()};
if (can_wrap) {
r.pushKV("p2sh", EncodeDestination(ScriptHash(script)));
const bool can_wrap_P2WSH{[&] {
switch (which_type) {
case TxoutType::MULTISIG:
case TxoutType::PUBKEY:
for (const auto& solution : solutions_data) {
if ((solution.size() != 1) && !CPubKey(solution).IsCompressed()) {
return false;
}
}
return true;
case TxoutType::NONSTANDARD:
case TxoutType::PUBKEYHASH:
return true;
case TxoutType::NULL_DATA:
case TxoutType::SCRIPTHASH:
case TxoutType::WITNESS_UNKNOWN:
case TxoutType::WITNESS_V0_KEYHASH:
case TxoutType::WITNESS_V0_SCRIPTHASH:
case TxoutType::WITNESS_V1_TAPROOT:
case TxoutType::ANCHOR:
return false;
} NONFATAL_UNREACHABLE();
}()};
if (can_wrap_P2WSH) {
UniValue sr(UniValue::VOBJ);
CScript segwitScr;
FlatSigningProvider provider;
if (which_type == TxoutType::PUBKEY) {
segwitScr = GetScriptForDestination(WitnessV0KeyHash(Hash160(solutions_data[0])));
} else if (which_type == TxoutType::PUBKEYHASH) {
segwitScr = GetScriptForDestination(WitnessV0KeyHash(uint160{solutions_data[0]}));
} else {
provider.scripts[CScriptID(script)] = script;
segwitScr = GetScriptForDestination(WitnessV0ScriptHash(script));
}
ScriptToUniv(segwitScr, sr, true, true, &provider);
sr.pushKV("p2sh-segwit", EncodeDestination(ScriptHash(segwitScr)));
r.pushKV("segwit", std::move(sr));
}
}
return r;
},
};
}
static RPCMethod combinerawtransaction()
{
return RPCMethod{
"combinerawtransaction",
"Combine multiple partially signed transactions into one transaction.\n"
"The combined transaction may be another partially signed transaction or a \n"
"fully signed transaction.",
{
{"txs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The hex strings of partially signed transactions",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "A hex-encoded raw transaction"},
},
},
},
RPCResult{
RPCResult::Type::STR, "", "The hex-encoded raw transaction with signature(s)"
},
RPCExamples{
HelpExampleCli("combinerawtransaction", R"('["myhex1", "myhex2", "myhex3"]')")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
UniValue txs = request.params[0].get_array();
std::vector<CMutableTransaction> txVariants(txs.size());
for (unsigned int idx = 0; idx < txs.size(); idx++) {
if (!DecodeHexTx(txVariants[idx], txs[idx].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed for tx %d. Make sure the tx has at least one input.", idx));
}
}
if (txVariants.empty()) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Missing transactions");
}
CMutableTransaction mergedTx(txVariants[0]);
CCoinsViewCache view{&CoinsViewEmpty::Get()};
{
NodeContext& node = EnsureAnyNodeContext(request.context);
const CTxMemPool& mempool = EnsureMemPool(node);
ChainstateManager& chainman = EnsureChainman(node);
LOCK2(cs_main, mempool.cs);
CCoinsViewCache &viewChain = chainman.ActiveChainstate().CoinsTip();
CCoinsViewMemPool viewMempool(&viewChain, mempool);
view.SetBackend(viewMempool);
for (const CTxIn& txin : mergedTx.vin) {
view.AccessCoin(txin.prevout); }
view.SetBackend(CoinsViewEmpty::Get()); }
const CTransaction txConst(mergedTx);
for (unsigned int i = 0; i < mergedTx.vin.size(); i++) {
CTxIn& txin = mergedTx.vin[i];
const Coin& coin = view.AccessCoin(txin.prevout);
if (coin.IsSpent()) {
throw JSONRPCError(RPC_VERIFY_ERROR, "Input not found or already spent");
}
SignatureData sigdata;
for (const CMutableTransaction& txv : txVariants) {
if (txv.vin.size() > i) {
sigdata.MergeSignatureData(DataFromTransaction(txv, i, coin.out));
}
}
ProduceSignature(DUMMY_SIGNING_PROVIDER, MutableTransactionSignatureCreator(mergedTx, i, coin.out.nValue, {.sighash_type = SIGHASH_ALL}), coin.out.scriptPubKey, sigdata);
UpdateInput(txin, sigdata);
}
return EncodeHexTx(CTransaction(mergedTx));
},
};
}
static RPCMethod signrawtransactionwithkey()
{
return RPCMethod{
"signrawtransactionwithkey",
"Sign 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",
{
{"hexstring", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction hex string"},
{"privkeys", RPCArg::Type::ARR, RPCArg::Optional::NO, "The base58-encoded private keys for signing",
{
{"privatekey", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "private key in base58-encoding"},
},
},
{"prevtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "The previous dependent transaction outputs",
{
{"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
{
{"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"},
{"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"},
{"scriptPubKey", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "output script"},
{"redeemScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2SH) redeem script"},
{"witnessScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2WSH or P2SH-P2WSH) witness script"},
{"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::OMITTED, "(required for Segwit inputs) the amount spent"},
},
},
},
},
{"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT for Taproot, ALL otherwise"}, "The signature hash type. Must be one of:\n"
" \"DEFAULT\"\n"
" \"ALL\"\n"
" \"NONE\"\n"
" \"SINGLE\"\n"
" \"ALL|ANYONECANPAY\"\n"
" \"NONE|ANYONECANPAY\"\n"
" \"SINGLE|ANYONECANPAY\"\n"
},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "hex", "The hex-encoded raw transaction with signature(s)"},
{RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"},
{RPCResult::Type::ARR, "errors", true, "Script verification errors (if there are any)",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "txid", "The hash of the referenced, previous transaction"},
{RPCResult::Type::NUM, "vout", "The index of the output to spent and used as input"},
{RPCResult::Type::ARR, "witness", "",
{
{RPCResult::Type::STR_HEX, "witness", ""},
}},
{RPCResult::Type::STR_HEX, "scriptSig", "The hex-encoded signature script"},
{RPCResult::Type::NUM, "sequence", "Script sequence number"},
{RPCResult::Type::STR, "error", "Verification or signing error related to the input"},
}},
}},
}
},
RPCExamples{
HelpExampleCli("signrawtransactionwithkey", "\"myhex\" \"[\\\"key1\\\",\\\"key2\\\"]\"")
+ HelpExampleRpc("signrawtransactionwithkey", "\"myhex\", \"[\\\"key1\\\",\\\"key2\\\"]\"")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
CMutableTransaction mtx;
if (!DecodeHexTx(mtx, request.params[0].get_str())) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
}
FlatSigningProvider keystore;
const UniValue& keys = request.params[1].get_array();
for (unsigned int idx = 0; idx < keys.size(); ++idx) {
UniValue k = keys[idx];
CKey key = DecodeSecret(k.get_str());
if (!key.IsValid()) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
}
CPubKey pubkey = key.GetPubKey();
CKeyID key_id = pubkey.GetID();
keystore.pubkeys.emplace(key_id, pubkey);
keystore.keys.emplace(key_id, key);
}
std::map<COutPoint, Coin> coins;
for (const CTxIn& txin : mtx.vin) {
coins[txin.prevout]; }
NodeContext& node = EnsureAnyNodeContext(request.context);
FindCoins(node, coins);
ParsePrevouts(request.params[2], &keystore, coins);
UniValue result(UniValue::VOBJ);
SignTransaction(mtx, &keystore, coins, request.params[3], result);
return result;
},
};
}
const RPCResult& DecodePSBTInputs()
{
static const RPCResult decodepsbt_inputs{
RPCResult::Type::ARR, "inputs", "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::OBJ, "non_witness_utxo", true, "Decoded network transaction for non-witness UTXOs",
TxDoc({.elision_description="The layout is the same as the output of decoderawtransaction."})
},
{RPCResult::Type::OBJ, "witness_utxo", true, "Transaction output for witness UTXOs",
{
{RPCResult::Type::NUM, "amount", "The value in " + CURRENCY_UNIT},
{RPCResult::Type::OBJ, "scriptPubKey", "",
{
{RPCResult::Type::STR, "asm", "Disassembly of the output script"},
{RPCResult::Type::STR, "desc", "Inferred descriptor for the output"},
{RPCResult::Type::STR_HEX, "hex", "The raw output script bytes, hex-encoded"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
{RPCResult::Type::STR, "address", true, "The Bitcoin address (only if a well-defined address exists)"},
}},
}},
{RPCResult::Type::OBJ_DYN, "partial_signatures", true, "",
{
{RPCResult::Type::STR, "pubkey", "The public key and signature that corresponds to it."},
}},
{RPCResult::Type::STR, "sighash", true, "The sighash type to be used"},
{RPCResult::Type::OBJ, "redeem_script", true, "",
{
{RPCResult::Type::STR, "asm", "Disassembly of the redeem script"},
{RPCResult::Type::STR_HEX, "hex", "The raw redeem script bytes, hex-encoded"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
}},
{RPCResult::Type::OBJ, "witness_script", true, "",
{
{RPCResult::Type::STR, "asm", "Disassembly of the witness script"},
{RPCResult::Type::STR_HEX, "hex", "The raw witness script bytes, hex-encoded"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
}},
{RPCResult::Type::ARR, "bip32_derivs", true, "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "pubkey", "The public key with the derivation path as the value."},
{RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"},
{RPCResult::Type::STR, "path", "The path"},
}},
}},
{RPCResult::Type::OBJ, "final_scriptSig", true, "",
{
{RPCResult::Type::STR, "asm", "Disassembly of the final signature script"},
{RPCResult::Type::STR_HEX, "hex", "The raw final signature script bytes, hex-encoded"},
}},
{RPCResult::Type::ARR, "final_scriptwitness", true, "",
{
{RPCResult::Type::STR_HEX, "", "hex-encoded witness data (if any)"},
}},
{RPCResult::Type::OBJ_DYN, "ripemd160_preimages", true, "",
{
{RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."},
}},
{RPCResult::Type::OBJ_DYN, "sha256_preimages", true, "",
{
{RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."},
}},
{RPCResult::Type::OBJ_DYN, "hash160_preimages", true, "",
{
{RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."},
}},
{RPCResult::Type::OBJ_DYN, "hash256_preimages", true, "",
{
{RPCResult::Type::STR, "hash", "The hash and preimage that corresponds to it."},
}},
{RPCResult::Type::STR_HEX, "taproot_key_path_sig", true, "hex-encoded signature for the Taproot key path spend"},
{RPCResult::Type::ARR, "taproot_script_path_sigs", true, "",
{
{RPCResult::Type::OBJ, "signature", true, "The signature for the pubkey and leaf hash combination",
{
{RPCResult::Type::STR, "pubkey", "The x-only pubkey for this signature"},
{RPCResult::Type::STR, "leaf_hash", "The leaf hash for this signature"},
{RPCResult::Type::STR, "sig", "The signature itself"},
}},
}},
{RPCResult::Type::ARR, "taproot_scripts", true, "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "script", "A leaf script"},
{RPCResult::Type::NUM, "leaf_ver", "The version number for the leaf script"},
{RPCResult::Type::ARR, "control_blocks", "The control blocks for this script",
{
{RPCResult::Type::STR_HEX, "control_block", "A hex-encoded control block for this script"},
}},
}},
}},
{RPCResult::Type::ARR, "taproot_bip32_derivs", true, "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "pubkey", "The x-only public key this path corresponds to"},
{RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"},
{RPCResult::Type::STR, "path", "The path"},
{RPCResult::Type::ARR, "leaf_hashes", "The hashes of the leaves this pubkey appears in",
{
{RPCResult::Type::STR_HEX, "hash", "The hash of a leaf this pubkey appears in"},
}},
}},
}},
{RPCResult::Type::STR_HEX, "taproot_internal_key", true, "The hex-encoded Taproot x-only internal key"},
{RPCResult::Type::STR_HEX, "taproot_merkle_root", true, "The hex-encoded Taproot merkle root"},
{RPCResult::Type::ARR, "musig2_participant_pubkeys", true, "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "aggregate_pubkey", "The compressed aggregate public key for which the participants create."},
{RPCResult::Type::ARR, "participant_pubkeys", "",
{
{RPCResult::Type::STR_HEX, "pubkey", "The compressed public keys that are aggregated for aggregate_pubkey."},
}},
}},
}},
{RPCResult::Type::ARR, "musig2_pubnonces", true, "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "participant_pubkey", "The compressed public key of the participant that created this pubnonce."},
{RPCResult::Type::STR_HEX, "aggregate_pubkey", "The compressed aggregate public key for which this pubnonce is for."},
{RPCResult::Type::STR_HEX, "leaf_hash", true, "The hash of the leaf script that contains the aggregate pubkey being signed for. Omitted when signing for the internal key."},
{RPCResult::Type::STR_HEX, "pubnonce", "The public nonce itself."},
}},
}},
{RPCResult::Type::ARR, "musig2_partial_sigs", true, "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "participant_pubkey", "The compressed public key of the participant that created this partial signature."},
{RPCResult::Type::STR_HEX, "aggregate_pubkey", "The compressed aggregate public key for which this partial signature is for."},
{RPCResult::Type::STR_HEX, "leaf_hash", true, "The hash of the leaf script that contains the aggregate pubkey being signed for. Omitted when signing for the internal key."},
{RPCResult::Type::STR_HEX, "partial_sig", "The partial signature itself."},
}},
}},
{RPCResult::Type::OBJ_DYN, "unknown", true, "The unknown input fields",
{
{RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"},
}},
{RPCResult::Type::ARR, "proprietary", true, "The input proprietary map",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "identifier", "The hex string for the proprietary identifier"},
{RPCResult::Type::NUM, "subtype", "The number for the subtype"},
{RPCResult::Type::STR_HEX, "key", "The hex for the key"},
{RPCResult::Type::STR_HEX, "value", "The hex for the value"},
}},
}},
}},
}
};
return decodepsbt_inputs;
}
const RPCResult& DecodePSBTOutputs()
{
static const RPCResult decodepsbt_outputs{
RPCResult::Type::ARR, "outputs", "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::OBJ, "redeem_script", true, "",
{
{RPCResult::Type::STR, "asm", "Disassembly of the redeem script"},
{RPCResult::Type::STR_HEX, "hex", "The raw redeem script bytes, hex-encoded"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
}},
{RPCResult::Type::OBJ, "witness_script", true, "",
{
{RPCResult::Type::STR, "asm", "Disassembly of the witness script"},
{RPCResult::Type::STR_HEX, "hex", "The raw witness script bytes, hex-encoded"},
{RPCResult::Type::STR, "type", "The type, eg 'pubkeyhash'"},
}},
{RPCResult::Type::ARR, "bip32_derivs", true, "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "pubkey", "The public key this path corresponds to"},
{RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"},
{RPCResult::Type::STR, "path", "The path"},
}},
}},
{RPCResult::Type::STR_HEX, "taproot_internal_key", true, "The hex-encoded Taproot x-only internal key"},
{RPCResult::Type::ARR, "taproot_tree", true, "The tuples that make up the Taproot tree, in depth first search order",
{
{RPCResult::Type::OBJ, "tuple", true, "A single leaf script in the taproot tree",
{
{RPCResult::Type::NUM, "depth", "The depth of this element in the tree"},
{RPCResult::Type::NUM, "leaf_ver", "The version of this leaf"},
{RPCResult::Type::STR, "script", "The hex-encoded script itself"},
}},
}},
{RPCResult::Type::ARR, "taproot_bip32_derivs", true, "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "pubkey", "The x-only public key this path corresponds to"},
{RPCResult::Type::STR, "master_fingerprint", "The fingerprint of the master key"},
{RPCResult::Type::STR, "path", "The path"},
{RPCResult::Type::ARR, "leaf_hashes", "The hashes of the leaves this pubkey appears in",
{
{RPCResult::Type::STR_HEX, "hash", "The hash of a leaf this pubkey appears in"},
}},
}},
}},
{RPCResult::Type::ARR, "musig2_participant_pubkeys", true, "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "aggregate_pubkey", "The compressed aggregate public key for which the participants create."},
{RPCResult::Type::ARR, "participant_pubkeys", "",
{
{RPCResult::Type::STR_HEX, "pubkey", "The compressed public keys that are aggregated for aggregate_pubkey."},
}},
}},
}},
{RPCResult::Type::OBJ_DYN, "unknown", true, "The unknown output fields",
{
{RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"},
}},
{RPCResult::Type::ARR, "proprietary", true, "The output proprietary map",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "identifier", "The hex string for the proprietary identifier"},
{RPCResult::Type::NUM, "subtype", "The number for the subtype"},
{RPCResult::Type::STR_HEX, "key", "The hex for the key"},
{RPCResult::Type::STR_HEX, "value", "The hex for the value"},
}},
}},
}},
}
};
return decodepsbt_outputs;
}
static RPCMethod decodepsbt()
{
return RPCMethod{
"decodepsbt",
"Return a JSON object representing the serialized, base64-encoded partially signed Bitcoin transaction.",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The PSBT base64 string"},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::OBJ, "tx", "The decoded network-serialized unsigned transaction.",
TxDoc({.elision_description="The layout is the same as the output of decoderawtransaction."})
},
{RPCResult::Type::ARR, "global_xpubs", "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "xpub", "The extended public key this path corresponds to"},
{RPCResult::Type::STR_HEX, "master_fingerprint", "The fingerprint of the master key"},
{RPCResult::Type::STR, "path", "The path"},
}},
}},
{RPCResult::Type::NUM, "psbt_version", "The PSBT version number. Not to be confused with the unsigned transaction version"},
{RPCResult::Type::ARR, "proprietary", "The global proprietary map",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR_HEX, "identifier", "The hex string for the proprietary identifier"},
{RPCResult::Type::NUM, "subtype", "The number for the subtype"},
{RPCResult::Type::STR_HEX, "key", "The hex for the key"},
{RPCResult::Type::STR_HEX, "value", "The hex for the value"},
}},
}},
{RPCResult::Type::OBJ_DYN, "unknown", "The unknown global fields",
{
{RPCResult::Type::STR_HEX, "key", "(key-value pair) An unknown key-value pair"},
}},
DecodePSBTInputs(),
DecodePSBTOutputs(),
{RPCResult::Type::STR_AMOUNT, "fee", true, "The transaction fee paid if all UTXOs slots in the PSBT have been filled."},
}
},
RPCExamples{
HelpExampleCli("decodepsbt", "\"psbt\"")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
}
UniValue result(UniValue::VOBJ);
UniValue tx_univ(UniValue::VOBJ);
TxToUniv(CTransaction(*psbtx.tx), uint256(), tx_univ, false);
result.pushKV("tx", std::move(tx_univ));
UniValue global_xpubs(UniValue::VARR);
for (std::pair<KeyOriginInfo, std::set<CExtPubKey>> xpub_pair : psbtx.m_xpubs) {
for (auto& xpub : xpub_pair.second) {
std::vector<unsigned char> ser_xpub;
ser_xpub.assign(BIP32_EXTKEY_WITH_VERSION_SIZE, 0);
xpub.EncodeWithVersion(ser_xpub.data());
UniValue keypath(UniValue::VOBJ);
keypath.pushKV("xpub", EncodeBase58Check(ser_xpub));
keypath.pushKV("master_fingerprint", HexStr(std::span<unsigned char>(xpub_pair.first.fingerprint, xpub_pair.first.fingerprint + 4)));
keypath.pushKV("path", WriteHDKeypath(xpub_pair.first.path));
global_xpubs.push_back(std::move(keypath));
}
}
result.pushKV("global_xpubs", std::move(global_xpubs));
result.pushKV("psbt_version", psbtx.GetVersion());
UniValue proprietary(UniValue::VARR);
for (const auto& entry : psbtx.m_proprietary) {
UniValue this_prop(UniValue::VOBJ);
this_prop.pushKV("identifier", HexStr(entry.identifier));
this_prop.pushKV("subtype", entry.subtype);
this_prop.pushKV("key", HexStr(entry.key));
this_prop.pushKV("value", HexStr(entry.value));
proprietary.push_back(std::move(this_prop));
}
result.pushKV("proprietary", std::move(proprietary));
UniValue unknowns(UniValue::VOBJ);
for (auto entry : psbtx.unknown) {
unknowns.pushKV(HexStr(entry.first), HexStr(entry.second));
}
result.pushKV("unknown", std::move(unknowns));
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);
bool have_a_utxo = false;
CTxOut txout;
if (!input.witness_utxo.IsNull()) {
txout = input.witness_utxo;
UniValue o(UniValue::VOBJ);
ScriptToUniv(txout.scriptPubKey, o, true, true);
UniValue out(UniValue::VOBJ);
out.pushKV("amount", ValueFromAmount(txout.nValue));
out.pushKV("scriptPubKey", std::move(o));
in.pushKV("witness_utxo", std::move(out));
have_a_utxo = true;
}
if (input.non_witness_utxo) {
txout = input.non_witness_utxo->vout[psbtx.tx->vin[i].prevout.n];
UniValue non_wit(UniValue::VOBJ);
TxToUniv(*input.non_witness_utxo, uint256(), non_wit, false);
in.pushKV("non_witness_utxo", std::move(non_wit));
have_a_utxo = true;
}
if (have_a_utxo) {
if (MoneyRange(txout.nValue) && MoneyRange(total_in + txout.nValue)) {
total_in += txout.nValue;
} else {
have_all_utxos = false;
}
} else {
have_all_utxos = false;
}
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", std::move(partial_sigs));
}
if (input.sighash_type != std::nullopt) {
in.pushKV("sighash", SighashToStr((unsigned char)*input.sighash_type));
}
if (!input.redeem_script.empty()) {
UniValue r(UniValue::VOBJ);
ScriptToUniv(input.redeem_script, r);
in.pushKV("redeem_script", std::move(r));
}
if (!input.witness_script.empty()) {
UniValue r(UniValue::VOBJ);
ScriptToUniv(input.witness_script, r);
in.pushKV("witness_script", std::move(r));
}
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));
keypath.pushKV("master_fingerprint", strprintf("%08x", ReadBE32(entry.second.fingerprint)));
keypath.pushKV("path", WriteHDKeypath(entry.second.path));
keypaths.push_back(std::move(keypath));
}
in.pushKV("bip32_derivs", std::move(keypaths));
}
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", std::move(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));
}
in.pushKV("final_scriptwitness", std::move(txinwitness));
}
if (!input.ripemd160_preimages.empty()) {
UniValue ripemd160_preimages(UniValue::VOBJ);
for (const auto& [hash, preimage] : input.ripemd160_preimages) {
ripemd160_preimages.pushKV(HexStr(hash), HexStr(preimage));
}
in.pushKV("ripemd160_preimages", std::move(ripemd160_preimages));
}
if (!input.sha256_preimages.empty()) {
UniValue sha256_preimages(UniValue::VOBJ);
for (const auto& [hash, preimage] : input.sha256_preimages) {
sha256_preimages.pushKV(HexStr(hash), HexStr(preimage));
}
in.pushKV("sha256_preimages", std::move(sha256_preimages));
}
if (!input.hash160_preimages.empty()) {
UniValue hash160_preimages(UniValue::VOBJ);
for (const auto& [hash, preimage] : input.hash160_preimages) {
hash160_preimages.pushKV(HexStr(hash), HexStr(preimage));
}
in.pushKV("hash160_preimages", std::move(hash160_preimages));
}
if (!input.hash256_preimages.empty()) {
UniValue hash256_preimages(UniValue::VOBJ);
for (const auto& [hash, preimage] : input.hash256_preimages) {
hash256_preimages.pushKV(HexStr(hash), HexStr(preimage));
}
in.pushKV("hash256_preimages", std::move(hash256_preimages));
}
if (!input.m_tap_key_sig.empty()) {
in.pushKV("taproot_key_path_sig", HexStr(input.m_tap_key_sig));
}
if (!input.m_tap_script_sigs.empty()) {
UniValue script_sigs(UniValue::VARR);
for (const auto& [pubkey_leaf, sig] : input.m_tap_script_sigs) {
const auto& [xonly, leaf_hash] = pubkey_leaf;
UniValue sigobj(UniValue::VOBJ);
sigobj.pushKV("pubkey", HexStr(xonly));
sigobj.pushKV("leaf_hash", HexStr(leaf_hash));
sigobj.pushKV("sig", HexStr(sig));
script_sigs.push_back(std::move(sigobj));
}
in.pushKV("taproot_script_path_sigs", std::move(script_sigs));
}
if (!input.m_tap_scripts.empty()) {
UniValue tap_scripts(UniValue::VARR);
for (const auto& [leaf, control_blocks] : input.m_tap_scripts) {
const auto& [script, leaf_ver] = leaf;
UniValue script_info(UniValue::VOBJ);
script_info.pushKV("script", HexStr(script));
script_info.pushKV("leaf_ver", leaf_ver);
UniValue control_blocks_univ(UniValue::VARR);
for (const auto& control_block : control_blocks) {
control_blocks_univ.push_back(HexStr(control_block));
}
script_info.pushKV("control_blocks", std::move(control_blocks_univ));
tap_scripts.push_back(std::move(script_info));
}
in.pushKV("taproot_scripts", std::move(tap_scripts));
}
if (!input.m_tap_bip32_paths.empty()) {
UniValue keypaths(UniValue::VARR);
for (const auto& [xonly, leaf_origin] : input.m_tap_bip32_paths) {
const auto& [leaf_hashes, origin] = leaf_origin;
UniValue path_obj(UniValue::VOBJ);
path_obj.pushKV("pubkey", HexStr(xonly));
path_obj.pushKV("master_fingerprint", strprintf("%08x", ReadBE32(origin.fingerprint)));
path_obj.pushKV("path", WriteHDKeypath(origin.path));
UniValue leaf_hashes_arr(UniValue::VARR);
for (const auto& leaf_hash : leaf_hashes) {
leaf_hashes_arr.push_back(HexStr(leaf_hash));
}
path_obj.pushKV("leaf_hashes", std::move(leaf_hashes_arr));
keypaths.push_back(std::move(path_obj));
}
in.pushKV("taproot_bip32_derivs", std::move(keypaths));
}
if (!input.m_tap_internal_key.IsNull()) {
in.pushKV("taproot_internal_key", HexStr(input.m_tap_internal_key));
}
if (!input.m_tap_merkle_root.IsNull()) {
in.pushKV("taproot_merkle_root", HexStr(input.m_tap_merkle_root));
}
if (!input.m_musig2_participants.empty()) {
UniValue musig_pubkeys(UniValue::VARR);
for (const auto& [agg, parts] : input.m_musig2_participants) {
UniValue musig_part(UniValue::VOBJ);
musig_part.pushKV("aggregate_pubkey", HexStr(agg));
UniValue part_pubkeys(UniValue::VARR);
for (const auto& pub : parts) {
part_pubkeys.push_back(HexStr(pub));
}
musig_part.pushKV("participant_pubkeys", part_pubkeys);
musig_pubkeys.push_back(musig_part);
}
in.pushKV("musig2_participant_pubkeys", musig_pubkeys);
}
if (!input.m_musig2_pubnonces.empty()) {
UniValue musig_pubnonces(UniValue::VARR);
for (const auto& [agg_lh, part_pubnonce] : input.m_musig2_pubnonces) {
const auto& [agg, lh] = agg_lh;
for (const auto& [part, pubnonce] : part_pubnonce) {
UniValue info(UniValue::VOBJ);
info.pushKV("participant_pubkey", HexStr(part));
info.pushKV("aggregate_pubkey", HexStr(agg));
if (!lh.IsNull()) info.pushKV("leaf_hash", HexStr(lh));
info.pushKV("pubnonce", HexStr(pubnonce));
musig_pubnonces.push_back(info);
}
}
in.pushKV("musig2_pubnonces", musig_pubnonces);
}
if (!input.m_musig2_partial_sigs.empty()) {
UniValue musig_partial_sigs(UniValue::VARR);
for (const auto& [agg_lh, part_psig] : input.m_musig2_partial_sigs) {
const auto& [agg, lh] = agg_lh;
for (const auto& [part, psig] : part_psig) {
UniValue info(UniValue::VOBJ);
info.pushKV("participant_pubkey", HexStr(part));
info.pushKV("aggregate_pubkey", HexStr(agg));
if (!lh.IsNull()) info.pushKV("leaf_hash", HexStr(lh));
info.pushKV("partial_sig", HexStr(psig));
musig_partial_sigs.push_back(info);
}
}
in.pushKV("musig2_partial_sigs", musig_partial_sigs);
}
if (!input.m_proprietary.empty()) {
UniValue proprietary(UniValue::VARR);
for (const auto& entry : input.m_proprietary) {
UniValue this_prop(UniValue::VOBJ);
this_prop.pushKV("identifier", HexStr(entry.identifier));
this_prop.pushKV("subtype", entry.subtype);
this_prop.pushKV("key", HexStr(entry.key));
this_prop.pushKV("value", HexStr(entry.value));
proprietary.push_back(std::move(this_prop));
}
in.pushKV("proprietary", std::move(proprietary));
}
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", std::move(unknowns));
}
inputs.push_back(std::move(in));
}
result.pushKV("inputs", std::move(inputs));
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);
if (!output.redeem_script.empty()) {
UniValue r(UniValue::VOBJ);
ScriptToUniv(output.redeem_script, r);
out.pushKV("redeem_script", std::move(r));
}
if (!output.witness_script.empty()) {
UniValue r(UniValue::VOBJ);
ScriptToUniv(output.witness_script, r);
out.pushKV("witness_script", std::move(r));
}
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));
keypath.pushKV("master_fingerprint", strprintf("%08x", ReadBE32(entry.second.fingerprint)));
keypath.pushKV("path", WriteHDKeypath(entry.second.path));
keypaths.push_back(std::move(keypath));
}
out.pushKV("bip32_derivs", std::move(keypaths));
}
if (!output.m_tap_internal_key.IsNull()) {
out.pushKV("taproot_internal_key", HexStr(output.m_tap_internal_key));
}
if (!output.m_tap_tree.empty()) {
UniValue tree(UniValue::VARR);
for (const auto& [depth, leaf_ver, script] : output.m_tap_tree) {
UniValue elem(UniValue::VOBJ);
elem.pushKV("depth", depth);
elem.pushKV("leaf_ver", leaf_ver);
elem.pushKV("script", HexStr(script));
tree.push_back(std::move(elem));
}
out.pushKV("taproot_tree", std::move(tree));
}
if (!output.m_tap_bip32_paths.empty()) {
UniValue keypaths(UniValue::VARR);
for (const auto& [xonly, leaf_origin] : output.m_tap_bip32_paths) {
const auto& [leaf_hashes, origin] = leaf_origin;
UniValue path_obj(UniValue::VOBJ);
path_obj.pushKV("pubkey", HexStr(xonly));
path_obj.pushKV("master_fingerprint", strprintf("%08x", ReadBE32(origin.fingerprint)));
path_obj.pushKV("path", WriteHDKeypath(origin.path));
UniValue leaf_hashes_arr(UniValue::VARR);
for (const auto& leaf_hash : leaf_hashes) {
leaf_hashes_arr.push_back(HexStr(leaf_hash));
}
path_obj.pushKV("leaf_hashes", std::move(leaf_hashes_arr));
keypaths.push_back(std::move(path_obj));
}
out.pushKV("taproot_bip32_derivs", std::move(keypaths));
}
if (!output.m_musig2_participants.empty()) {
UniValue musig_pubkeys(UniValue::VARR);
for (const auto& [agg, parts] : output.m_musig2_participants) {
UniValue musig_part(UniValue::VOBJ);
musig_part.pushKV("aggregate_pubkey", HexStr(agg));
UniValue part_pubkeys(UniValue::VARR);
for (const auto& pub : parts) {
part_pubkeys.push_back(HexStr(pub));
}
musig_part.pushKV("participant_pubkeys", part_pubkeys);
musig_pubkeys.push_back(musig_part);
}
out.pushKV("musig2_participant_pubkeys", musig_pubkeys);
}
if (!output.m_proprietary.empty()) {
UniValue proprietary(UniValue::VARR);
for (const auto& entry : output.m_proprietary) {
UniValue this_prop(UniValue::VOBJ);
this_prop.pushKV("identifier", HexStr(entry.identifier));
this_prop.pushKV("subtype", entry.subtype);
this_prop.pushKV("key", HexStr(entry.key));
this_prop.pushKV("value", HexStr(entry.value));
proprietary.push_back(std::move(this_prop));
}
out.pushKV("proprietary", std::move(proprietary));
}
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", std::move(unknowns));
}
outputs.push_back(std::move(out));
if (MoneyRange(psbtx.tx->vout[i].nValue) && MoneyRange(output_value + psbtx.tx->vout[i].nValue)) {
output_value += psbtx.tx->vout[i].nValue;
} else {
have_all_utxos = false;
}
}
result.pushKV("outputs", std::move(outputs));
if (have_all_utxos) {
result.pushKV("fee", ValueFromAmount(total_in - output_value));
}
return result;
},
};
}
static RPCMethod combinepsbt()
{
return RPCMethod{
"combinepsbt",
"Combine multiple partially signed Bitcoin transactions into one transaction.\n"
"Implements the Combiner role.\n",
{
{"txs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The base64 strings of partially signed transactions",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A base64 string of a PSBT"},
},
},
},
RPCResult{
RPCResult::Type::STR, "", "The base64-encoded partially signed transaction"
},
RPCExamples{
HelpExampleCli("combinepsbt", R"('["mybase64_1", "mybase64_2", "mybase64_3"]')")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
std::vector<PartiallySignedTransaction> psbtxs;
UniValue txs = request.params[0].get_array();
if (txs.empty()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter 'txs' cannot be empty");
}
for (unsigned int i = 0; i < txs.size(); ++i) {
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
}
psbtxs.push_back(psbtx);
}
PartiallySignedTransaction merged_psbt;
if (!CombinePSBTs(merged_psbt, psbtxs)) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "PSBTs not compatible (different transactions)");
}
DataStream ssTx{};
ssTx << merged_psbt;
return EncodeBase64(ssTx);
},
};
}
static RPCMethod finalizepsbt()
{
return RPCMethod{"finalizepsbt",
"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",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"},
{"extract", RPCArg::Type::BOOL, RPCArg::Default{true}, "If true and the transaction is complete,\n"
" extract and return the complete transaction in normal network serialization instead of the PSBT."},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "psbt", true, "The base64-encoded partially signed transaction if not extracted"},
{RPCResult::Type::STR_HEX, "hex", true, "The hex-encoded network transaction if extracted"},
{RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"},
}
},
RPCExamples{
HelpExampleCli("finalizepsbt", "\"psbt\"")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
}
bool extract = request.params[1].isNull() || (!request.params[1].isNull() && request.params[1].get_bool());
CMutableTransaction mtx;
bool complete = FinalizeAndExtractPSBT(psbtx, mtx);
UniValue result(UniValue::VOBJ);
DataStream ssTx{};
std::string result_str;
if (complete && extract) {
ssTx << TX_WITH_WITNESS(mtx);
result_str = HexStr(ssTx);
result.pushKV("hex", result_str);
} else {
ssTx << psbtx;
result_str = EncodeBase64(ssTx.str());
result.pushKV("psbt", result_str);
}
result.pushKV("complete", complete);
return result;
},
};
}
static RPCMethod createpsbt()
{
return RPCMethod{
"createpsbt",
"Creates a transaction in the Partially Signed Transaction format.\n"
"Implements the Creator role.\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",
CreateTxDoc(),
RPCResult{
RPCResult::Type::STR, "", "The resulting raw transaction (base64-encoded string)"
},
RPCExamples{
HelpExampleCli("createpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"address\\\":0.01}]\"")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
std::optional<bool> rbf;
if (!request.params[3].isNull()) {
rbf = request.params[3].get_bool();
}
CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf, self.Arg<uint32_t>("version"));
PartiallySignedTransaction psbtx;
psbtx.tx = rawTx;
for (unsigned int i = 0; i < rawTx.vin.size(); ++i) {
psbtx.inputs.emplace_back();
}
for (unsigned int i = 0; i < rawTx.vout.size(); ++i) {
psbtx.outputs.emplace_back();
}
DataStream ssTx{};
ssTx << psbtx;
return EncodeBase64(ssTx);
},
};
}
static RPCMethod converttopsbt()
{
return RPCMethod{
"converttopsbt",
"Converts 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",
{
{"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of a raw transaction"},
{"permitsigdata", RPCArg::Type::BOOL, RPCArg::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."},
{"iswitness", RPCArg::Type::BOOL, RPCArg::DefaultHint{"depends on heuristic tests"}, "Whether the transaction hex is a serialized witness transaction.\n"
"If iswitness is not present, heuristic tests will be used in decoding.\n"
"If true, only witness deserialization will be tried.\n"
"If false, only non-witness deserialization will be tried.\n"
"This boolean should reflect whether the transaction has inputs\n"
"(e.g. fully valid, or on-chain transactions), if known by the caller."
},
},
RPCResult{
RPCResult::Type::STR, "", "The resulting raw transaction (base64-encoded string)"
},
RPCExamples{
"\nCreate a transaction\n"
+ HelpExampleCli("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") +
"\nConvert the transaction to a PSBT\n"
+ HelpExampleCli("converttopsbt", "\"rawtransaction\"")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
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;
const bool try_witness = witness_specified ? iswitness : true;
const bool try_no_witness = witness_specified ? !iswitness : true;
if (!DecodeHexTx(tx, request.params[0].get_str(), try_no_witness, try_witness)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
}
for (CTxIn& input : tx.vin) {
if ((!input.scriptSig.empty() || !input.scriptWitness.IsNull()) && !permitsigdata) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Inputs must not have scriptSigs and scriptWitnesses");
}
input.scriptSig.clear();
input.scriptWitness.SetNull();
}
PartiallySignedTransaction psbtx;
psbtx.tx = tx;
for (unsigned int i = 0; i < tx.vin.size(); ++i) {
psbtx.inputs.emplace_back();
}
for (unsigned int i = 0; i < tx.vout.size(); ++i) {
psbtx.outputs.emplace_back();
}
DataStream ssTx{};
ssTx << psbtx;
return EncodeBase64(ssTx);
},
};
}
static RPCMethod utxoupdatepsbt()
{
return RPCMethod{
"utxoupdatepsbt",
"Updates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set, txindex, or the mempool.\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"},
{"descriptors", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "An array of either strings or objects", {
{"", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"},
{"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with an output descriptor and extra information", {
{"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"},
{"range", RPCArg::Type::RANGE, RPCArg::Default{1000}, "Up to what index HD chains should be explored (either end or [begin,end])"},
}},
}},
},
RPCResult {
RPCResult::Type::STR, "", "The base64-encoded partially signed transaction with inputs updated"
},
RPCExamples {
HelpExampleCli("utxoupdatepsbt", "\"psbt\"")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
FlatSigningProvider provider;
if (!request.params[1].isNull()) {
auto descs = request.params[1].get_array();
for (size_t i = 0; i < descs.size(); ++i) {
EvalDescriptorStringOrObject(descs[i], provider);
}
}
const PartiallySignedTransaction& psbtx = ProcessPSBT(
request.params[0].get_str(),
request.context,
HidingSigningProvider(&provider, true, false),
std::nullopt,
false);
DataStream ssTx{};
ssTx << psbtx;
return EncodeBase64(ssTx);
},
};
}
static RPCMethod joinpsbts()
{
return RPCMethod{
"joinpsbts",
"Joins multiple distinct PSBTs with different inputs and outputs into one PSBT with inputs and outputs from all of the PSBTs\n"
"No input in any of the PSBTs can be in more than one of the PSBTs.\n",
{
{"txs", RPCArg::Type::ARR, RPCArg::Optional::NO, "The base64 strings of partially signed transactions",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"}
}}
},
RPCResult {
RPCResult::Type::STR, "", "The base64-encoded partially signed transaction"
},
RPCExamples {
HelpExampleCli("joinpsbts", "\"psbt\"")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
std::vector<PartiallySignedTransaction> psbtxs;
UniValue txs = request.params[0].get_array();
if (txs.size() <= 1) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "At least two PSBTs are required to join PSBTs.");
}
uint32_t best_version = 1;
uint32_t best_locktime = 0xffffffff;
for (unsigned int i = 0; i < txs.size(); ++i) {
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
}
psbtxs.push_back(psbtx);
if (psbtx.tx->version > best_version) {
best_version = psbtx.tx->version;
}
if (psbtx.tx->nLockTime < best_locktime) {
best_locktime = psbtx.tx->nLockTime;
}
}
PartiallySignedTransaction merged_psbt;
merged_psbt.tx = CMutableTransaction();
merged_psbt.tx->version = best_version;
merged_psbt.tx->nLockTime = best_locktime;
for (auto& psbt : psbtxs) {
for (unsigned int i = 0; i < psbt.tx->vin.size(); ++i) {
if (!merged_psbt.AddInput(psbt.tx->vin[i], psbt.inputs[i])) {
throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Input %s:%d exists in multiple PSBTs", psbt.tx->vin[i].prevout.hash.ToString(), psbt.tx->vin[i].prevout.n));
}
}
for (unsigned int i = 0; i < psbt.tx->vout.size(); ++i) {
merged_psbt.AddOutput(psbt.tx->vout[i], psbt.outputs[i]);
}
for (auto& xpub_pair : psbt.m_xpubs) {
if (!merged_psbt.m_xpubs.contains(xpub_pair.first)) {
merged_psbt.m_xpubs[xpub_pair.first] = xpub_pair.second;
} else {
merged_psbt.m_xpubs[xpub_pair.first].insert(xpub_pair.second.begin(), xpub_pair.second.end());
}
}
merged_psbt.unknown.insert(psbt.unknown.begin(), psbt.unknown.end());
}
std::vector<int> input_indices(merged_psbt.inputs.size());
std::iota(input_indices.begin(), input_indices.end(), 0);
std::vector<int> output_indices(merged_psbt.outputs.size());
std::iota(output_indices.begin(), output_indices.end(), 0);
std::shuffle(input_indices.begin(), input_indices.end(), FastRandomContext());
std::shuffle(output_indices.begin(), output_indices.end(), FastRandomContext());
PartiallySignedTransaction shuffled_psbt;
shuffled_psbt.tx = CMutableTransaction();
shuffled_psbt.tx->version = merged_psbt.tx->version;
shuffled_psbt.tx->nLockTime = merged_psbt.tx->nLockTime;
for (int i : input_indices) {
shuffled_psbt.AddInput(merged_psbt.tx->vin[i], merged_psbt.inputs[i]);
}
for (int i : output_indices) {
shuffled_psbt.AddOutput(merged_psbt.tx->vout[i], merged_psbt.outputs[i]);
}
shuffled_psbt.unknown.insert(merged_psbt.unknown.begin(), merged_psbt.unknown.end());
DataStream ssTx{};
ssTx << shuffled_psbt;
return EncodeBase64(ssTx);
},
};
}
static RPCMethod analyzepsbt()
{
return RPCMethod{
"analyzepsbt",
"Analyzes and provides information about the current status of a PSBT and its inputs\n",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"}
},
RPCResult {
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::ARR, "inputs", true, "",
{
{RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::BOOL, "has_utxo", "Whether a UTXO is provided"},
{RPCResult::Type::BOOL, "is_final", "Whether the input is finalized"},
{RPCResult::Type::OBJ, "missing", true, "Things that are missing that are required to complete this input",
{
{RPCResult::Type::ARR, "pubkeys", true, "",
{
{RPCResult::Type::STR_HEX, "keyid", "Public key ID, hash160 of the public key, of a public key whose BIP 32 derivation path is missing"},
}},
{RPCResult::Type::ARR, "signatures", true, "",
{
{RPCResult::Type::STR_HEX, "keyid", "Public key ID, hash160 of the public key, of a public key whose signature is missing"},
}},
{RPCResult::Type::STR_HEX, "redeemscript", true, "Hash160 of the redeem script that is missing"},
{RPCResult::Type::STR_HEX, "witnessscript", true, "SHA256 of the witness script that is missing"},
}},
{RPCResult::Type::STR, "next", true, "Role of the next person that this input needs to go to"},
}},
}},
{RPCResult::Type::NUM, "estimated_vsize", true, "Estimated vsize of the final signed transaction"},
{RPCResult::Type::STR_AMOUNT, "estimated_feerate", true, "Estimated feerate of the final signed transaction in " + CURRENCY_UNIT + "/kvB. Shown only if all UTXO slots in the PSBT have been filled"},
{RPCResult::Type::STR_AMOUNT, "fee", true, "The transaction fee paid. Shown only if all UTXO slots in the PSBT have been filled"},
{RPCResult::Type::STR, "next", "Role of the next person that this psbt needs to go to"},
{RPCResult::Type::STR, "error", true, "Error message (if there is one)"},
}
},
RPCExamples {
HelpExampleCli("analyzepsbt", "\"psbt\"")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
PartiallySignedTransaction psbtx;
std::string error;
if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) {
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
}
PSBTAnalysis psbta = AnalyzePSBT(psbtx);
UniValue result(UniValue::VOBJ);
UniValue inputs_result(UniValue::VARR);
for (const auto& input : psbta.inputs) {
UniValue input_univ(UniValue::VOBJ);
UniValue missing(UniValue::VOBJ);
input_univ.pushKV("has_utxo", input.has_utxo);
input_univ.pushKV("is_final", input.is_final);
input_univ.pushKV("next", PSBTRoleName(input.next));
if (!input.missing_pubkeys.empty()) {
UniValue missing_pubkeys_univ(UniValue::VARR);
for (const CKeyID& pubkey : input.missing_pubkeys) {
missing_pubkeys_univ.push_back(HexStr(pubkey));
}
missing.pushKV("pubkeys", std::move(missing_pubkeys_univ));
}
if (!input.missing_redeem_script.IsNull()) {
missing.pushKV("redeemscript", HexStr(input.missing_redeem_script));
}
if (!input.missing_witness_script.IsNull()) {
missing.pushKV("witnessscript", HexStr(input.missing_witness_script));
}
if (!input.missing_sigs.empty()) {
UniValue missing_sigs_univ(UniValue::VARR);
for (const CKeyID& pubkey : input.missing_sigs) {
missing_sigs_univ.push_back(HexStr(pubkey));
}
missing.pushKV("signatures", std::move(missing_sigs_univ));
}
if (!missing.getKeys().empty()) {
input_univ.pushKV("missing", std::move(missing));
}
inputs_result.push_back(std::move(input_univ));
}
if (!inputs_result.empty()) result.pushKV("inputs", std::move(inputs_result));
if (psbta.estimated_vsize != std::nullopt) {
result.pushKV("estimated_vsize", *psbta.estimated_vsize);
}
if (psbta.estimated_feerate != std::nullopt) {
result.pushKV("estimated_feerate", ValueFromAmount(psbta.estimated_feerate->GetFeePerK()));
}
if (psbta.fee != std::nullopt) {
result.pushKV("fee", ValueFromAmount(*psbta.fee));
}
result.pushKV("next", PSBTRoleName(psbta.next));
if (!psbta.error.empty()) {
result.pushKV("error", psbta.error);
}
return result;
},
};
}
RPCMethod descriptorprocesspsbt()
{
return RPCMethod{
"descriptorprocesspsbt",
"Update all segwit inputs in a PSBT with information from output descriptors, the UTXO set or the mempool. \n"
"Then, sign the inputs we are able to with information from the output descriptors. ",
{
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction base64 string"},
{"descriptors", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of either strings or objects", {
{"", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"},
{"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with an output descriptor and extra information", {
{"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"},
{"range", RPCArg::Type::RANGE, RPCArg::Default{1000}, "Up to what index HD chains should be explored (either end or [begin,end])"},
}},
}},
{"sighashtype", RPCArg::Type::STR, RPCArg::Default{"DEFAULT for Taproot, ALL otherwise"}, "The signature hash type to sign with if not specified by the PSBT. Must be one of\n"
" \"DEFAULT\"\n"
" \"ALL\"\n"
" \"NONE\"\n"
" \"SINGLE\"\n"
" \"ALL|ANYONECANPAY\"\n"
" \"NONE|ANYONECANPAY\"\n"
" \"SINGLE|ANYONECANPAY\""},
{"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"},
{"finalize", RPCArg::Type::BOOL, RPCArg::Default{true}, "Also finalize inputs if possible"},
},
RPCResult{
RPCResult::Type::OBJ, "", "",
{
{RPCResult::Type::STR, "psbt", "The base64-encoded partially signed transaction"},
{RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"},
{RPCResult::Type::STR_HEX, "hex", true, "The hex-encoded network transaction if complete"},
}
},
RPCExamples{
HelpExampleCli("descriptorprocesspsbt", "\"psbt\" \"[\\\"descriptor1\\\", \\\"descriptor2\\\"]\"") +
HelpExampleCli("descriptorprocesspsbt", "\"psbt\" \"[{\\\"desc\\\":\\\"mydescriptor\\\", \\\"range\\\":21}]\"")
},
[](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
{
FlatSigningProvider provider;
auto descs = request.params[1].get_array();
for (size_t i = 0; i < descs.size(); ++i) {
EvalDescriptorStringOrObject(descs[i], provider, true);
}
std::optional<int> sighash_type = ParseSighashString(request.params[2]);
bool bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool();
bool finalize = request.params[4].isNull() ? true : request.params[4].get_bool();
const PartiallySignedTransaction& psbtx = ProcessPSBT(
request.params[0].get_str(),
request.context,
HidingSigningProvider(&provider, false, !bip32derivs),
sighash_type,
finalize);
bool complete = true;
for (const auto& input : psbtx.inputs) {
complete &= PSBTInputSigned(input);
}
DataStream ssTx{};
ssTx << psbtx;
UniValue result(UniValue::VOBJ);
result.pushKV("psbt", EncodeBase64(ssTx));
result.pushKV("complete", complete);
if (complete) {
CMutableTransaction mtx;
PartiallySignedTransaction psbtx_copy = psbtx;
CHECK_NONFATAL(FinalizeAndExtractPSBT(psbtx_copy, mtx));
DataStream ssTx_final;
ssTx_final << TX_WITH_WITNESS(mtx);
result.pushKV("hex", HexStr(ssTx_final));
}
return result;
},
};
}
void RegisterRawTransactionRPCCommands(CRPCTable& t)
{
static const CRPCCommand commands[]{
{"rawtransactions", &getrawtransaction},
{"rawtransactions", &createrawtransaction},
{"rawtransactions", &decoderawtransaction},
{"rawtransactions", &decodescript},
{"rawtransactions", &combinerawtransaction},
{"rawtransactions", &signrawtransactionwithkey},
{"rawtransactions", &decodepsbt},
{"rawtransactions", &combinepsbt},
{"rawtransactions", &finalizepsbt},
{"rawtransactions", &createpsbt},
{"rawtransactions", &converttopsbt},
{"rawtransactions", &utxoupdatepsbt},
{"rawtransactions", &descriptorprocesspsbt},
{"rawtransactions", &joinpsbts},
{"rawtransactions", &analyzepsbt},
};
for (const auto& c : commands) {
t.appendCommand(c.name, &c);
}
}