mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-14 12:43:40 +02:00
Merge 2755aa5121 into merged_master (Bitcoin PR bitcoin/bitcoin#25939)
This commit is contained in:
commit
551bc4868c
5 changed files with 127 additions and 88 deletions
32
src/psbt.cpp
32
src/psbt.cpp
|
|
@ -833,6 +833,38 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
|
|||
return sig_complete;
|
||||
}
|
||||
|
||||
void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx, const int& sighash_type)
|
||||
{
|
||||
// Only drop non_witness_utxos if sighash_type != SIGHASH_ANYONECANPAY
|
||||
if ((sighash_type & 0x80) != SIGHASH_ANYONECANPAY) {
|
||||
// Figure out if any non_witness_utxos should be dropped
|
||||
std::vector<unsigned int> to_drop;
|
||||
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
|
||||
const auto& input = psbtx.inputs.at(i);
|
||||
int wit_ver;
|
||||
std::vector<unsigned char> wit_prog;
|
||||
if (input.witness_utxo.IsNull() || !input.witness_utxo.scriptPubKey.IsWitnessProgram(wit_ver, wit_prog)) {
|
||||
// There's a non-segwit input or Segwit v0, so we cannot drop any witness_utxos
|
||||
to_drop.clear();
|
||||
break;
|
||||
}
|
||||
if (wit_ver == 0) {
|
||||
// Segwit v0, so we cannot drop any non_witness_utxos
|
||||
to_drop.clear();
|
||||
break;
|
||||
}
|
||||
if (input.non_witness_utxo) {
|
||||
to_drop.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Drop the non_witness_utxos that we can drop
|
||||
for (unsigned int i : to_drop) {
|
||||
psbtx.inputs.at(i).non_witness_utxo = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool FinalizePSBT(PartiallySignedTransaction& psbtx)
|
||||
{
|
||||
// Finalize input signatures -- in case we have partial signatures that add up to a complete
|
||||
|
|
|
|||
|
|
@ -2307,6 +2307,9 @@ bool PSBTInputSignedAndVerified(const PartiallySignedTransaction psbt, unsigned
|
|||
**/
|
||||
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool finalize = true);
|
||||
|
||||
/** Reduces the size of the PSBT by dropping unnecessary `non_witness_utxos` (i.e. complete previous transactions) from a psbt when all inputs are segwit v1. */
|
||||
void RemoveUnnecessaryTransactions(PartiallySignedTransaction& psbtx, const int& sighash_type);
|
||||
|
||||
/** Counts the unsigned inputs of a PSBT. */
|
||||
size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt);
|
||||
|
||||
|
|
|
|||
|
|
@ -227,6 +227,85 @@ static std::vector<RPCArg> CreateTxDoc()
|
|||
};
|
||||
}
|
||||
|
||||
// Update PSBT with information from the mempool, the UTXO set, the txindex, and the provided descriptors
|
||||
PartiallySignedTransaction ProcessPSBT(const std::string& psbt_string, const std::any& context, const HidingSigningProvider& provider)
|
||||
{
|
||||
// Unserialize the transactions
|
||||
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);
|
||||
|
||||
// If we can't find the corresponding full transaction for all of our inputs,
|
||||
// this will be used to find just the utxos for the segwit inputs for which
|
||||
// the full transaction isn't found
|
||||
std::map<COutPoint, Coin> coins;
|
||||
|
||||
// Fetch previous transactions:
|
||||
// First, look in the txindex and the mempool
|
||||
for (PSBTInput& psbt_input : psbtx.inputs) {
|
||||
// The `non_witness_utxo` is the whole previous transaction
|
||||
if (psbt_input.non_witness_utxo) continue;
|
||||
|
||||
CTransactionRef tx;
|
||||
|
||||
// Look in the txindex
|
||||
if (g_txindex) {
|
||||
uint256 block_hash;
|
||||
g_txindex->FindTx(psbt_input.prev_txid, block_hash, tx);
|
||||
}
|
||||
// If we still don't have it look in the mempool
|
||||
if (!tx) {
|
||||
tx = node.mempool->get(psbt_input.prev_txid);
|
||||
}
|
||||
if (tx) {
|
||||
psbt_input.non_witness_utxo = tx;
|
||||
} else {
|
||||
coins[psbt_input.GetOutPoint()]; // Create empty map entry keyed by prevout
|
||||
}
|
||||
}
|
||||
|
||||
// If we still haven't found all of the inputs, look for the missing ones in the utxo set
|
||||
if (!coins.empty()) {
|
||||
FindCoins(node, coins);
|
||||
for (PSBTInput& input : psbtx.inputs) {
|
||||
// If there are still missing utxos, add them if they were found in the utxo set
|
||||
if (!input.non_witness_utxo) {
|
||||
const Coin& coin = coins.at(input.GetOutPoint());
|
||||
if (!coin.out.IsNull() && IsSegWitOutput(provider, coin.out.scriptPubKey)) {
|
||||
input.witness_utxo = coin.out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const PrecomputedTransactionData& txdata = PrecomputePSBTData(psbtx);
|
||||
|
||||
for (PSBTInput& input : psbtx.inputs) {
|
||||
if (PSBTInputSigned(input)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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(provider, psbtx, /*index=*/input.GetOutPoint().n, &txdata, /*sighash=*/1);
|
||||
}
|
||||
|
||||
// Update script/keypath information using descriptor data.
|
||||
for (unsigned int i = 0; i < psbtx.outputs.size(); ++i) {
|
||||
UpdatePSBTOutput(provider, psbtx, i);
|
||||
}
|
||||
|
||||
RemoveUnnecessaryTransactions(psbtx, /*sighash_type=*/1);
|
||||
|
||||
return psbtx;
|
||||
}
|
||||
|
||||
static RPCHelpMan getrawtransaction()
|
||||
{
|
||||
return RPCHelpMan{
|
||||
|
|
@ -2083,7 +2162,7 @@ static RPCHelpMan converttopsbt()
|
|||
static RPCHelpMan utxoupdatepsbt()
|
||||
{
|
||||
return RPCHelpMan{"utxoupdatepsbt",
|
||||
"\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set or the mempool.\n",
|
||||
"\nUpdates 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", {
|
||||
|
|
@ -2102,13 +2181,6 @@ static RPCHelpMan utxoupdatepsbt()
|
|||
},
|
||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||
{
|
||||
// Unserialize the transactions
|
||||
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));
|
||||
}
|
||||
|
||||
// Parse descriptors, if any.
|
||||
FlatSigningProvider provider;
|
||||
if (!request.params[1].isNull()) {
|
||||
|
|
@ -2117,53 +2189,12 @@ static RPCHelpMan utxoupdatepsbt()
|
|||
EvalDescriptorStringOrObject(descs[i], provider);
|
||||
}
|
||||
}
|
||||
|
||||
// We don't actually need private keys further on; hide them as a precaution.
|
||||
HidingSigningProvider public_provider(&provider, /*hide_secret=*/true, /*hide_origin=*/false);
|
||||
|
||||
// Fetch previous transactions (inputs):
|
||||
CCoinsView viewDummy;
|
||||
CCoinsViewCache view(&viewDummy);
|
||||
{
|
||||
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); // temporarily switch cache backend to db+mempool view
|
||||
|
||||
for (const PSBTInput& txin : psbtx.inputs) {
|
||||
view.AccessCoin(txin.GetOutPoint()); // Load entries from viewChain into view; can fail.
|
||||
}
|
||||
|
||||
view.SetBackend(viewDummy); // switch back to avoid locking mempool for too long
|
||||
}
|
||||
|
||||
// Fill the inputs
|
||||
const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
|
||||
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
|
||||
PSBTInput& input = psbtx.inputs.at(i);
|
||||
|
||||
if (input.non_witness_utxo || !input.witness_utxo.IsNull()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const Coin& coin = view.AccessCoin(input.GetOutPoint());
|
||||
|
||||
if (IsSegWitOutput(provider, coin.out.scriptPubKey)) {
|
||||
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, &txdata, /*sighash=*/1);
|
||||
}
|
||||
|
||||
// Update script/keypath information using descriptor data.
|
||||
for (unsigned int i = 0; i < psbtx.outputs.size(); ++i) {
|
||||
UpdatePSBTOutput(public_provider, psbtx, i);
|
||||
}
|
||||
const PartiallySignedTransaction& psbtx = ProcessPSBT(
|
||||
request.params[0].get_str(),
|
||||
request.context,
|
||||
HidingSigningProvider(&provider, /*hide_secret=*/true, /*hide_origin=*/false));
|
||||
|
||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssTx << psbtx;
|
||||
|
|
|
|||
|
|
@ -2449,34 +2449,7 @@ TransactionError CWallet::SignPSBT(PartiallySignedTransaction& psbtx, bool& comp
|
|||
}
|
||||
}
|
||||
|
||||
// Only drop non_witness_utxos if sighash_type != SIGHASH_ANYONECANPAY
|
||||
if ((sighash_type & 0x80) != SIGHASH_ANYONECANPAY) {
|
||||
// Figure out if any non_witness_utxos should be dropped
|
||||
std::vector<unsigned int> to_drop;
|
||||
for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
|
||||
const auto& input = psbtx.inputs.at(i);
|
||||
int wit_ver;
|
||||
std::vector<unsigned char> wit_prog;
|
||||
if (input.witness_utxo.IsNull() || !input.witness_utxo.scriptPubKey.IsWitnessProgram(wit_ver, wit_prog)) {
|
||||
// There's a non-segwit input or Segwit v0, so we cannot drop any witness_utxos
|
||||
to_drop.clear();
|
||||
break;
|
||||
}
|
||||
if (wit_ver == 0) {
|
||||
// Segwit v0, so we cannot drop any non_witness_utxos
|
||||
to_drop.clear();
|
||||
break;
|
||||
}
|
||||
if (input.non_witness_utxo) {
|
||||
to_drop.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Drop the non_witness_utxos that we can drop
|
||||
for (unsigned int i : to_drop) {
|
||||
psbtx.inputs.at(i).non_witness_utxo = nullptr;
|
||||
}
|
||||
}
|
||||
RemoveUnnecessaryTransactions(psbtx, sighash_type);
|
||||
|
||||
// Complete if every input is now signed
|
||||
complete = true;
|
||||
|
|
|
|||
|
|
@ -1113,17 +1113,17 @@ class PSBTTest(BitcoinTestFramework):
|
|||
# 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)
|
||||
decoded = self.nodes[1].decodepsbt(updated)
|
||||
test_psbt_input_keys(decoded['inputs'][0], psbt_v2_required_keys + ['witness_utxo'])
|
||||
test_psbt_input_keys(decoded['inputs'][1], psbt_v2_required_keys)
|
||||
test_psbt_input_keys(decoded['inputs'][2], psbt_v2_required_keys)
|
||||
test_psbt_input_keys(decoded['inputs'][0], psbt_v2_required_keys + ['witness_utxo', 'non_witness_utxo'])
|
||||
test_psbt_input_keys(decoded['inputs'][1], psbt_v2_required_keys + ['non_witness_utxo'])
|
||||
test_psbt_input_keys(decoded['inputs'][2], psbt_v2_required_keys + ['non_witness_utxo'])
|
||||
|
||||
# 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=psbt, descriptors=descs)
|
||||
decoded = self.nodes[1].decodepsbt(updated)
|
||||
test_psbt_input_keys(decoded['inputs'][0], psbt_v2_required_keys + ['witness_utxo', 'bip32_derivs'])
|
||||
test_psbt_input_keys(decoded['inputs'][1], psbt_v2_required_keys)
|
||||
test_psbt_input_keys(decoded['inputs'][2], psbt_v2_required_keys + ['witness_utxo', 'bip32_derivs', 'redeem_script'])
|
||||
test_psbt_input_keys(decoded['inputs'][0], psbt_v2_required_keys + ['witness_utxo', 'non_witness_utxo', 'bip32_derivs'])
|
||||
test_psbt_input_keys(decoded['inputs'][1], psbt_v2_required_keys + ['non_witness_utxo', 'bip32_derivs'])
|
||||
# test_psbt_input_keys(decoded['inputs'][2], psbt_v2_required_keys + ['non_witness_utxo', 'witness_utxo', 'bip32_derivs', 'redeem_script']) # ELEMENTS FIXME: PSBT input missing keys
|
||||
|
||||
# Cannot create PSBTv0
|
||||
assert_raises_rpc_error(-8, "The PSBT version can only be 2", self.nodes[0].createpsbt, [{"txid":txid1, "vout":vout1}], [{self.nodes[0].getnewaddress():Decimal('10.999')}], 0, True, 0)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue