Add support for descriptors to utxoupdatepsbt

This adds a descriptors argument to the utxoupdatepsbt RPC. This means:
* Input and output scripts and keys will be filled in when known
* P2SH-witness outputs will be filled in from the UTXO set when a descriptor
  is provided to show they're segwit outputs.
This commit is contained in:
Pieter Wuille 2019-02-16 14:59:16 -08:00
parent 3135c1a2d2
commit 26fe9b9909
2 changed files with 55 additions and 13 deletions

View file

@ -1490,12 +1490,19 @@ UniValue converttopsbt(const JSONRPCRequest& request)
UniValue utxoupdatepsbt(const JSONRPCRequest& request) UniValue utxoupdatepsbt(const JSONRPCRequest& request)
{ {
if (request.fHelp || request.params.size() != 1) { if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) {
throw std::runtime_error( throw std::runtime_error(
RPCHelpMan{"utxoupdatepsbt", RPCHelpMan{"utxoupdatepsbt",
"\nUpdates a PSBT with witness UTXOs retrieved from the UTXO set or the mempool.\n", "\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set or the mempool.\n",
{ {
{"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"} {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"},
{"descriptors", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "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, "1000", "Up to what index HD chains should be explored (either end or [begin,end])"},
}},
}},
}, },
RPCResult { RPCResult {
" \"psbt\" (string) The base64-encoded partially signed transaction with inputs updated\n" " \"psbt\" (string) The base64-encoded partially signed transaction with inputs updated\n"
@ -1505,7 +1512,7 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request)
}}.ToString()); }}.ToString());
} }
RPCTypeCheck(request.params, {UniValue::VSTR}, true); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}, true);
// Unserialize the transactions // Unserialize the transactions
PartiallySignedTransaction psbtx; PartiallySignedTransaction psbtx;
@ -1514,6 +1521,17 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request)
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
} }
// Parse descriptors, if any.
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);
}
}
// We don't actually need private keys further on; hide them as a precaution.
HidingSigningProvider public_provider(&provider, /* nosign */ true, /* nobip32derivs */ false);
// Fetch previous transactions (inputs): // Fetch previous transactions (inputs):
CCoinsView viewDummy; CCoinsView viewDummy;
CCoinsViewCache view(&viewDummy); CCoinsViewCache view(&viewDummy);
@ -1540,9 +1558,19 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request)
const Coin& coin = view.AccessCoin(psbtx.tx->vin[i].prevout); const Coin& coin = view.AccessCoin(psbtx.tx->vin[i].prevout);
if (IsSegWitOutput(DUMMY_SIGNING_PROVIDER, coin.out.scriptPubKey)) { if (IsSegWitOutput(provider, coin.out.scriptPubKey)) {
input.witness_utxo = coin.out; input.witness_utxo = coin.out;
} }
// Update script/keypath information using descriptor data.
// Note that SignPSBTInput does a lot more than just constructing ECDSA signatures
// we don't actually care about those here, in fact.
SignPSBTInput(public_provider, psbtx, i, /* sighash_type */ 1);
}
// Update script/keypath information using descriptor data.
for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) {
UpdatePSBTOutput(public_provider, psbtx, i);
} }
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);

View file

@ -314,18 +314,32 @@ class PSBTTest(BitcoinTestFramework):
vout3 = find_output(self.nodes[0], txid3, 11) vout3 = find_output(self.nodes[0], txid3, 11)
self.sync_all() self.sync_all()
# Update a PSBT with UTXOs from the node def test_psbt_input_keys(psbt_input, keys):
# Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness """Check that the psbt input has only the expected keys."""
assert_equal(set(keys), set(psbt_input.keys()))
# Create a PSBT. None of the inputs are filled initially
psbt = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1},{"txid":txid2, "vout":vout2},{"txid":txid3, "vout":vout3}], {self.nodes[0].getnewaddress():32.999}) psbt = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1},{"txid":txid2, "vout":vout2},{"txid":txid3, "vout":vout3}], {self.nodes[0].getnewaddress():32.999})
decoded = self.nodes[1].decodepsbt(psbt) decoded = self.nodes[1].decodepsbt(psbt)
assert "witness_utxo" not in decoded['inputs'][0] and "non_witness_utxo" not in decoded['inputs'][0] test_psbt_input_keys(decoded['inputs'][0], [])
assert "witness_utxo" not in decoded['inputs'][1] and "non_witness_utxo" not in decoded['inputs'][1] test_psbt_input_keys(decoded['inputs'][1], [])
assert "witness_utxo" not in decoded['inputs'][2] and "non_witness_utxo" not in decoded['inputs'][2] test_psbt_input_keys(decoded['inputs'][2], [])
# Update a PSBT with UTXOs from the node
# Bech32 inputs should be filled with witness UTXO. Other inputs should not be filled because they are non-witness
updated = self.nodes[1].utxoupdatepsbt(psbt) updated = self.nodes[1].utxoupdatepsbt(psbt)
decoded = self.nodes[1].decodepsbt(updated) decoded = self.nodes[1].decodepsbt(updated)
assert "witness_utxo" in decoded['inputs'][0] and "non_witness_utxo" not in decoded['inputs'][0] test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo'])
assert "witness_utxo" not in decoded['inputs'][1] and "non_witness_utxo" not in decoded['inputs'][1] test_psbt_input_keys(decoded['inputs'][1], [])
assert "witness_utxo" not in decoded['inputs'][2] and "non_witness_utxo" not in decoded['inputs'][2] test_psbt_input_keys(decoded['inputs'][2], [])
# Try again, now while providing descriptors, making P2SH-segwit work, and causing bip32_derivs and redeem_script to be filled in
descs = [self.nodes[1].getaddressinfo(addr)['desc'] for addr in [addr1,addr2,addr3]]
updated = self.nodes[1].utxoupdatepsbt(psbt, descs)
decoded = self.nodes[1].decodepsbt(updated)
test_psbt_input_keys(decoded['inputs'][0], ['witness_utxo', 'bip32_derivs'])
test_psbt_input_keys(decoded['inputs'][1], [])
test_psbt_input_keys(decoded['inputs'][2], ['witness_utxo', 'bip32_derivs', 'redeem_script'])
# Two PSBTs with a common input should not be joinable # Two PSBTs with a common input should not be joinable
psbt1 = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1}], {self.nodes[0].getnewaddress():Decimal('10.999')}) psbt1 = self.nodes[1].createpsbt([{"txid":txid1, "vout":vout1}], {self.nodes[0].getnewaddress():Decimal('10.999')})