Skip to content

Commit fb2a3a7

Browse files
committed
rpc: add descriptorprocesspsbt rpc
This RPC can be the Updater, Signer, and optionally the Input Finalizer for a psbt, and has no interaction with the Bitcoin Core wallet.
1 parent 49d07ea commit fb2a3a7

File tree

5 files changed

+96
-8
lines changed

5 files changed

+96
-8
lines changed

src/rpc/client.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ static const CRPCConvertParam vRPCConvertParams[] =
133133
{ "walletprocesspsbt", 1, "sign" },
134134
{ "walletprocesspsbt", 3, "bip32derivs" },
135135
{ "walletprocesspsbt", 4, "finalize" },
136+
{ "descriptorprocesspsbt", 1, "descriptors"},
137+
{ "descriptorprocesspsbt", 3, "bip32derivs" },
138+
{ "descriptorprocesspsbt", 4, "finalize" },
136139
{ "createpsbt", 0, "inputs" },
137140
{ "createpsbt", 1, "outputs" },
138141
{ "createpsbt", 2, "locktime" },

src/rpc/rawtransaction.cpp

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,9 @@ static std::vector<RPCArg> CreateTxDoc()
172172
};
173173
}
174174

175-
// Update PSBT with information from the mempool, the UTXO set, the txindex, and the provided descriptors
176-
PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std::any& context, const HidingSigningProvider& provider)
175+
// Update PSBT with information from the mempool, the UTXO set, the txindex, and the provided descriptors.
176+
// Optionally, sign the inputs that we can using information from the descriptors.
177+
PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std::any& context, const HidingSigningProvider& provider, int sighash_type, bool finalize)
177178
{
178179
// Unserialize the transactions
179180
PartiallySignedTransaction psbtx;
@@ -242,9 +243,10 @@ PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std
242243
}
243244

244245
// Update script/keypath information using descriptor data.
245-
// Note that SignPSBTInput does a lot more than just constructing ECDSA signatures
246-
// we don't actually care about those here, in fact.
247-
SignPSBTInput(provider, psbtx, /*index=*/i, &txdata, /*sighash=*/1);
246+
// Note that SignPSBTInput does a lot more than just constructing ECDSA signatures.
247+
// We only actually care about those if our signing provider doesn't hide private
248+
// information, as is the case with `descriptorprocesspsbt`
249+
SignPSBTInput(provider, psbtx, /*index=*/i, &txdata, sighash_type, /*out_sigdata=*/nullptr, finalize);
248250
}
249251

250252
// Update script/keypath information using descriptor data.
@@ -1697,7 +1699,9 @@ static RPCHelpMan utxoupdatepsbt()
16971699
const PartiallySignedTransaction& psbtx = ProcessPSBT(
16981700
request.params[0].get_str(),
16991701
request.context,
1700-
HidingSigningProvider(&provider, /*hide_secret=*/true, /*hide_origin=*/false));
1702+
HidingSigningProvider(&provider, /*hide_secret=*/true, /*hide_origin=*/false),
1703+
/*sighash_type=*/SIGHASH_ALL,
1704+
/*finalize=*/false);
17011705

17021706
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
17031707
ssTx << psbtx;
@@ -1916,6 +1920,82 @@ static RPCHelpMan analyzepsbt()
19161920
};
19171921
}
19181922

1923+
RPCHelpMan descriptorprocesspsbt()
1924+
{
1925+
return RPCHelpMan{"descriptorprocesspsbt",
1926+
"\nUpdate all segwit inputs in a PSBT with information from output descriptors, the UTXO set or the mempool. \n"
1927+
"Then, sign the inputs we are able to with information from the output descriptors. ",
1928+
{
1929+
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction base64 string"},
1930+
{"descriptors", RPCArg::Type::ARR, RPCArg::Optional::NO, "An array of either strings or objects", {
1931+
{"", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "An output descriptor"},
1932+
{"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "An object with an output descriptor and extra information", {
1933+
{"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "An output descriptor"},
1934+
{"range", RPCArg::Type::RANGE, RPCArg::Default{1000}, "Up to what index HD chains should be explored (either end or [begin,end])"},
1935+
}},
1936+
}},
1937+
{"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"
1938+
" \"DEFAULT\"\n"
1939+
" \"ALL\"\n"
1940+
" \"NONE\"\n"
1941+
" \"SINGLE\"\n"
1942+
" \"ALL|ANYONECANPAY\"\n"
1943+
" \"NONE|ANYONECANPAY\"\n"
1944+
" \"SINGLE|ANYONECANPAY\""},
1945+
{"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"},
1946+
{"finalize", RPCArg::Type::BOOL, RPCArg::Default{true}, "Also finalize inputs if possible"},
1947+
},
1948+
RPCResult{
1949+
RPCResult::Type::OBJ, "", "",
1950+
{
1951+
{RPCResult::Type::STR, "psbt", "The base64-encoded partially signed transaction"},
1952+
{RPCResult::Type::BOOL, "complete", "If the transaction has a complete set of signatures"},
1953+
}
1954+
},
1955+
RPCExamples{
1956+
HelpExampleCli("descriptorprocesspsbt", "\"psbt\" \"[\\\"descriptor1\\\", \\\"descriptor2\\\"]\"") +
1957+
HelpExampleCli("descriptorprocesspsbt", "\"psbt\" \"[{\\\"desc\\\":\\\"mydescriptor\\\", \\\"range\\\":21}]\"")
1958+
},
1959+
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1960+
{
1961+
// Add descriptor information to a signing provider
1962+
FlatSigningProvider provider;
1963+
1964+
auto descs = request.params[1].get_array();
1965+
for (size_t i = 0; i < descs.size(); ++i) {
1966+
EvalDescriptorStringOrObject(descs[i], provider, /*expand_priv=*/true);
1967+
}
1968+
1969+
int sighash_type = ParseSighashString(request.params[2]);
1970+
bool bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool();
1971+
bool finalize = request.params[4].isNull() ? true : request.params[4].get_bool();
1972+
1973+
const PartiallySignedTransaction& psbtx = ProcessPSBT(
1974+
request.params[0].get_str(),
1975+
request.context,
1976+
HidingSigningProvider(&provider, /*hide_secret=*/false, !bip32derivs),
1977+
sighash_type,
1978+
finalize);
1979+
1980+
// Check whether or not all of the inputs are now signed
1981+
bool complete = true;
1982+
for (const auto& input : psbtx.inputs) {
1983+
complete &= PSBTInputSigned(input);
1984+
}
1985+
1986+
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
1987+
ssTx << psbtx;
1988+
1989+
UniValue result(UniValue::VOBJ);
1990+
1991+
result.pushKV("psbt", EncodeBase64(ssTx));
1992+
result.pushKV("complete", complete);
1993+
1994+
return result;
1995+
},
1996+
};
1997+
}
1998+
19191999
void RegisterRawTransactionRPCCommands(CRPCTable& t)
19202000
{
19212001
static const CRPCCommand commands[]{
@@ -1931,6 +2011,7 @@ void RegisterRawTransactionRPCCommands(CRPCTable& t)
19312011
{"rawtransactions", &createpsbt},
19322012
{"rawtransactions", &converttopsbt},
19332013
{"rawtransactions", &utxoupdatepsbt},
2014+
{"rawtransactions", &descriptorprocesspsbt},
19342015
{"rawtransactions", &joinpsbts},
19352016
{"rawtransactions", &analyzepsbt},
19362017
};

src/rpc/util.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1126,7 +1126,7 @@ std::pair<int64_t, int64_t> ParseDescriptorRange(const UniValue& value)
11261126
return {low, high};
11271127
}
11281128

1129-
std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider)
1129+
std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider, const bool expand_priv)
11301130
{
11311131
std::string desc_str;
11321132
std::pair<int64_t, int64_t> range = {0, 1000};
@@ -1159,6 +1159,9 @@ std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, Fl
11591159
if (!desc->Expand(i, provider, scripts, provider)) {
11601160
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys: '%s'", desc_str));
11611161
}
1162+
if (expand_priv) {
1163+
desc->ExpandPrivate(/*pos=*/i, provider, /*out=*/provider);
1164+
}
11621165
std::move(scripts.begin(), scripts.end(), std::back_inserter(ret));
11631166
}
11641167
return ret;

src/rpc/util.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ UniValue JSONRPCTransactionError(TransactionError terr, const std::string& err_s
110110
std::pair<int64_t, int64_t> ParseDescriptorRange(const UniValue& value);
111111

112112
/** Evaluate a descriptor given as a string, or as a {"desc":...,"range":...} object, with default range of 1000. */
113-
std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider);
113+
std::vector<CScript> EvalDescriptorStringOrObject(const UniValue& scanobject, FlatSigningProvider& provider, const bool expand_priv = false);
114114

115115
/** Returns, given services flags, a list of humanly readable (known) network services */
116116
UniValue GetServicesNames(ServiceFlags services);

src/test/fuzz/rpc.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ const std::vector<std::string> RPC_COMMANDS_SAFE_FOR_FUZZING{
9797
"decoderawtransaction",
9898
"decodescript",
9999
"deriveaddresses",
100+
"descriptorprocesspsbt",
100101
"disconnectnode",
101102
"echo",
102103
"echojson",

0 commit comments

Comments
 (0)