mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-14 11:26:09 -05:00
Merge f3e2a7f7ff
into 85f96b01b7
This commit is contained in:
commit
7aa7f31bc5
6 changed files with 50 additions and 2 deletions
|
@ -39,8 +39,10 @@ Generally, each of the above (excluding Creator and Extractor) will simply
|
||||||
add more and more data to a particular PSBT, until all inputs are fully signed.
|
add more and more data to a particular PSBT, until all inputs are fully signed.
|
||||||
In a naive workflow, they all have to operate sequentially, passing the PSBT
|
In a naive workflow, they all have to operate sequentially, passing the PSBT
|
||||||
from one to the next, until the Extractor can convert it to a real transaction.
|
from one to the next, until the Extractor can convert it to a real transaction.
|
||||||
In order to permit parallel operation, **Combiners** can be employed which merge
|
In order to permit parallel operation, **Combiners** can be employed which
|
||||||
metadata from different PSBTs for the same unsigned transaction.
|
merge metadata from different PSBTs for the same unsigned transaction. They can
|
||||||
|
also optionally remove bip32 derivation information from all inputs and outputs
|
||||||
|
to increase privacy.
|
||||||
|
|
||||||
The names above in bold are the names of the roles defined in BIP174. They're
|
The names above in bold are the names of the roles defined in BIP174. They're
|
||||||
useful in understanding the underlying steps, but in practice, software and
|
useful in understanding the underlying steps, but in practice, software and
|
||||||
|
|
13
src/psbt.cpp
13
src/psbt.cpp
|
@ -66,6 +66,19 @@ bool PartiallySignedTransaction::AddOutput(const CTxOut& txout, const PSBTOutput
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PartiallySignedTransaction::StripDerivationPaths()
|
||||||
|
{
|
||||||
|
// Loop over the inputs and strip m_tap_bip32_paths
|
||||||
|
for (unsigned int i = 0; i < inputs.size(); ++i) {
|
||||||
|
inputs[i].hd_keypaths.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop over the outputs and strip m_tap_bip32_paths
|
||||||
|
for (unsigned int i = 0; i < outputs.size(); ++i) {
|
||||||
|
outputs[i].hd_keypaths.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool PartiallySignedTransaction::GetInputUTXO(CTxOut& utxo, int input_index) const
|
bool PartiallySignedTransaction::GetInputUTXO(CTxOut& utxo, int input_index) const
|
||||||
{
|
{
|
||||||
const PSBTInput& input = inputs[input_index];
|
const PSBTInput& input = inputs[input_index];
|
||||||
|
|
|
@ -968,6 +968,7 @@ struct PartiallySignedTransaction
|
||||||
bool AddInput(const CTxIn& txin, PSBTInput& psbtin);
|
bool AddInput(const CTxIn& txin, PSBTInput& psbtin);
|
||||||
bool AddOutput(const CTxOut& txout, const PSBTOutput& psbtout);
|
bool AddOutput(const CTxOut& txout, const PSBTOutput& psbtout);
|
||||||
PartiallySignedTransaction() = default;
|
PartiallySignedTransaction() = default;
|
||||||
|
void StripDerivationPaths();
|
||||||
explicit PartiallySignedTransaction(const CMutableTransaction& tx);
|
explicit PartiallySignedTransaction(const CMutableTransaction& tx);
|
||||||
/**
|
/**
|
||||||
* Finds the UTXO for a given input index
|
* Finds the UTXO for a given input index
|
||||||
|
|
|
@ -181,6 +181,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||||
{ "createpsbt", 2, "locktime" },
|
{ "createpsbt", 2, "locktime" },
|
||||||
{ "createpsbt", 3, "replaceable" },
|
{ "createpsbt", 3, "replaceable" },
|
||||||
{ "combinepsbt", 0, "txs"},
|
{ "combinepsbt", 0, "txs"},
|
||||||
|
{ "combinepsbt", 1, "stripderivs"},
|
||||||
{ "joinpsbts", 0, "txs"},
|
{ "joinpsbts", 0, "txs"},
|
||||||
{ "finalizepsbt", 1, "extract"},
|
{ "finalizepsbt", 1, "extract"},
|
||||||
{ "converttopsbt", 1, "permitsigdata"},
|
{ "converttopsbt", 1, "permitsigdata"},
|
||||||
|
|
|
@ -1453,6 +1453,7 @@ static RPCHelpMan combinepsbt()
|
||||||
{"psbt", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A base64 string of a PSBT"},
|
{"psbt", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "A base64 string of a PSBT"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{"stripderivs", RPCArg::Type::BOOL, RPCArg::Default{false}, "Strip BIP 32 derivation paths for out inputs and outputs if they are present"},
|
||||||
},
|
},
|
||||||
RPCResult{
|
RPCResult{
|
||||||
RPCResult::Type::STR, "", "The base64-encoded partially signed transaction"
|
RPCResult::Type::STR, "", "The base64-encoded partially signed transaction"
|
||||||
|
@ -1462,6 +1463,8 @@ static RPCHelpMan combinepsbt()
|
||||||
},
|
},
|
||||||
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
[&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
|
||||||
{
|
{
|
||||||
|
bool strip_derivation_paths = request.params[1].isNull() ? false : request.params[1].get_bool();
|
||||||
|
|
||||||
// Unserialize the transactions
|
// Unserialize the transactions
|
||||||
std::vector<PartiallySignedTransaction> psbtxs;
|
std::vector<PartiallySignedTransaction> psbtxs;
|
||||||
UniValue txs = request.params[0].get_array();
|
UniValue txs = request.params[0].get_array();
|
||||||
|
@ -1474,6 +1477,9 @@ static RPCHelpMan combinepsbt()
|
||||||
if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) {
|
if (!DecodeBase64PSBT(psbtx, txs[i].get_str(), error)) {
|
||||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
|
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
|
||||||
}
|
}
|
||||||
|
if (strip_derivation_paths) {
|
||||||
|
psbtx.StripDerivationPaths();
|
||||||
|
}
|
||||||
psbtxs.push_back(psbtx);
|
psbtxs.push_back(psbtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -128,10 +128,35 @@ class WalletMultisigDescriptorPSBTTest(BitcoinTestFramework):
|
||||||
psbts.append(partially_signed_psbt["psbt"])
|
psbts.append(partially_signed_psbt["psbt"])
|
||||||
|
|
||||||
self.log.info("Finally, collect the signed PSBTs with combinepsbt, finalizepsbt, then broadcast the resulting transaction...")
|
self.log.info("Finally, collect the signed PSBTs with combinepsbt, finalizepsbt, then broadcast the resulting transaction...")
|
||||||
|
|
||||||
combined = coordinator_wallet.combinepsbt(psbts)
|
combined = coordinator_wallet.combinepsbt(psbts)
|
||||||
|
combined_stripped = coordinator_wallet.combinepsbt(psbts, stripderivs=True)
|
||||||
|
|
||||||
|
combined_decoded = coordinator_wallet.decodepsbt(combined)
|
||||||
|
combined_decoded_stripped = coordinator_wallet.decodepsbt(combined_stripped)
|
||||||
|
|
||||||
|
self.log.info("Check that we can strip bip32 derivation information from all inputs and outputs using combinepsbt")
|
||||||
|
for key in ['inputs', 'outputs']:
|
||||||
|
# Test for presence
|
||||||
|
for item in combined_decoded[key]:
|
||||||
|
# Only check these those that are not empty
|
||||||
|
if item:
|
||||||
|
assert "bip32_derivs" in item, f"{key} must contain 'bip32_derivs'"
|
||||||
|
# Test for absence
|
||||||
|
for item in combined_decoded_stripped[key]:
|
||||||
|
if item:
|
||||||
|
assert "bip32_derivs" not in item, f"'bip32_derivs' should not be in {key}"
|
||||||
|
|
||||||
finalized = coordinator_wallet.finalizepsbt(combined)
|
finalized = coordinator_wallet.finalizepsbt(combined)
|
||||||
coordinator_wallet.sendrawtransaction(finalized["hex"])
|
coordinator_wallet.sendrawtransaction(finalized["hex"])
|
||||||
|
|
||||||
|
self.log.info("Check that we can strip bip32 derivation information from a single psbt using combinepsbt")
|
||||||
|
psbts2 = [psbts[0],]
|
||||||
|
combined_single_stripped = coordinator_wallet.combinepsbt(psbts2, stripderivs=True)
|
||||||
|
combined_single_stripped_decoded = coordinator_wallet.decodepsbt(combined_single_stripped)
|
||||||
|
assert "bip32_derivs" not in combined_single_stripped_decoded["inputs"][0]
|
||||||
|
assert "bip32_derivs" not in combined_single_stripped_decoded["outputs"][0]
|
||||||
|
|
||||||
self.log.info("Check that balances are correct after the transaction has been included in a block.")
|
self.log.info("Check that balances are correct after the transaction has been included in a block.")
|
||||||
self.generate(self.nodes[0], 1)
|
self.generate(self.nodes[0], 1)
|
||||||
assert_approx(participants["multisigs"][0].getbalance(), deposit_amount - value, vspan=0.001)
|
assert_approx(participants["multisigs"][0].getbalance(), deposit_amount - value, vspan=0.001)
|
||||||
|
|
Loading…
Add table
Reference in a new issue