Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

backport: merge bitcoin#23106, #22067, #23303, #23403, #22513, #17034, #23718, partial bitcoin#21330, #21365, #22514 (psbt backports) #6503

Merged
merged 12 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions doc/descriptors.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,47 @@ Key order does not matter for `sortedmulti()`. `sortedmulti()` behaves in the sa
as `multi()` does but the keys are reordered in the resulting script such that they
are lexicographically ordered as described in BIP67.

#### Basic multisig example

For a good example of a basic M-of-N multisig between multiple participants using descriptor
wallets and PSBTs, as well as a signing flow, see [this functional test](/test/functional/wallet_multisig_descriptor_psbt.py).

Disclaimers: It is important to note that this example serves as a quick-start and is kept basic for readability. A downside of the approach
outlined here is that each participant must maintain (and backup) two separate wallets: a signer and the corresponding multisig.
It should also be noted that privacy best-practices are not "by default" here - participants should take care to only use the signer to sign
transactions related to the multisig. Lastly, it is not recommended to use anything other than a Bitcoin Core descriptor wallet to serve as your
signer(s). Other wallets, whether hardware or software, likely impose additional checks and safeguards to prevent users from signing transactions that
could lead to loss of funds, or are deemed security hazards. Conforming to various 3rd-party checks and verifications is not in the scope of this example.

The basic steps are:

1. Every participant generates an xpub. The most straightforward way is to create a new descriptor wallet which we will refer to as
the participant's signer wallet. Avoid reusing this wallet for any purpose other than signing transactions from the
corresponding multisig we are about to create. Hint: extract the wallet's xpubs using `listdescriptors` and pick the one from the
`pkh` descriptor since it's least likely to be accidentally reused (legacy addresses)
2. Create a watch-only descriptor wallet (blank, private keys disabled). Now the multisig is created by importing the two descriptors:
`wsh(sortedmulti(<M>,XPUB1/0/*,XPUB2/0/*,…,XPUBN/0/*))` and `wsh(sortedmulti(<M>,XPUB1/1/*,XPUB2/1/*,…,XPUBN/1/*))`
(one descriptor w/ `0` for receiving addresses and another w/ `1` for change). Every participant does this
3. A receiving address is generated for the multisig. As a check to ensure step 2 was done correctly, every participant
should verify they get the same addresses
4. Funds are sent to the resulting address
5. A sending transaction from the multisig is created using `walletcreatefundedpsbt` (anyone can initiate this). It is simple to do
this in the GUI by going to the `Send` tab in the multisig wallet and creating an unsigned transaction (PSBT)
6. At least `M` participants check the PSBT with their multisig using `decodepsbt` to verify the transaction is OK before signing it.
7. (If OK) the participant signs the PSBT with their signer wallet using `walletprocesspsbt`. It is simple to do this in the GUI by
loading the PSBT from file and signing it
8. The signed PSBTs are collected with `combinepsbt`, finalized w/ `finalizepsbt`, and then the resulting transaction is broadcasted
to the network. Note that any wallet (eg one of the signers or multisig) is capable of doing this.
9. Checks that balances are correct after the transaction has been included in a block

You may prefer a daisy chained signing flow where each participant signs the PSBT one after another until
the PSBT has been signed `M` times and is "complete." For the most part, the steps above remain the same, except (6, 7)
change slightly from signing the original PSBT in parallel to signing it in series. `combinepsbt` is not necessary with
this signing flow and the last (`m`th) signer can just broadcast the PSBT after signing. Note that a parallel signing flow may be
preferable in cases where there are more signers. This signing flow is also included in the test / Python example.
[The test](/test/functional/wallet_multisig_descriptor_psbt.py) is meant to be documentation as much as it is a functional test, so
it is kept as simple and readable as possible.

### BIP32 derived keys and chains

Most modern wallet software and hardware uses keys that are derived using
Expand Down
3 changes: 3 additions & 0 deletions doc/psbt.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ hardware implementations will typically implement multiple roles simultaneously.

#### Multisig with multiple Bitcoin Core instances

For a quick start see [Basic M-of-N multisig example using descriptor wallets and PSBTs](./descriptors.md#basic-multisig-example).
If you are using legacy wallets feel free to continue with the example provided here.

Alice, Bob, and Carol want to create a 2-of-3 multisig address. They're all using
Bitcoin Core. We assume their wallets only contain the multisig funds. In case
they also have a personal wallet, this can be accomplished through the
Expand Down
2 changes: 1 addition & 1 deletion src/coinjoin/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ bool CCoinJoinServer::IsInputScriptSigValid(const CTxIn& txin) const
txNew.vin[nTxInIndex].scriptSig = txin.scriptSig;
LogPrint(BCLog::COINJOIN, "CCoinJoinServer::IsInputScriptSigValid -- verifying scriptSig %s\n", ScriptToAsmStr(txin.scriptSig).substr(0, 24));
// TODO we're using amount=0 here but we should use the correct amount. This works because Dash ignores the amount while signing/verifying (only used in Bitcoin/Segwit)
if (!VerifyScript(txNew.vin[nTxInIndex].scriptSig, sigPubKey, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC, MutableTransactionSignatureChecker(&txNew, nTxInIndex, 0))) {
if (!VerifyScript(txNew.vin[nTxInIndex].scriptSig, sigPubKey, SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC, MutableTransactionSignatureChecker(&txNew, nTxInIndex, 0, MissingDataBehavior::ASSERT_FAIL))) {
LogPrint(BCLog::COINJOIN, "CCoinJoinServer::IsInputScriptSigValid -- VerifyScript() failed on input %d\n", nTxInIndex);
return false;
}
Expand Down
6 changes: 4 additions & 2 deletions src/node/psbt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)

result.inputs.resize(psbtx.tx->vin.size());

const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);

for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
PSBTInput& input = psbtx.inputs[i];
PSBTInputAnalysis& input_analysis = result.inputs[i];
Expand Down Expand Up @@ -60,7 +62,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)

// Figure out what is missing
SignatureData outdata;
bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, &outdata);
bool complete = SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, 1, &outdata);

// Things are missing
if (!complete) {
Expand Down Expand Up @@ -119,7 +121,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx)
PSBTInput& input = psbtx.inputs[i];
Coin newcoin;

if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, 1, nullptr, true) || !psbtx.GetInputUTXO(newcoin.out, i)) {
if (!SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, nullptr, 1) || !psbtx.GetInputUTXO(newcoin.out, i)) {
success = false;
break;
} else {
Expand Down
54 changes: 49 additions & 5 deletions src/psbt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ bool PartiallySignedTransaction::Merge(const PartiallySignedTransaction& psbt)
for (unsigned int i = 0; i < outputs.size(); ++i) {
outputs[i].Merge(psbt.outputs[i]);
}
for (auto& xpub_pair : psbt.m_xpubs) {
if (m_xpubs.count(xpub_pair.first) == 0) {
m_xpubs[xpub_pair.first] = xpub_pair.second;
} else {
m_xpubs[xpub_pair.first].insert(xpub_pair.second.begin(), xpub_pair.second.end());
}
}
unknown.insert(psbt.unknown.begin(), psbt.unknown.end());

return true;
Expand All @@ -63,12 +70,15 @@ bool PartiallySignedTransaction::AddOutput(const CTxOut& txout, const PSBTOutput

bool PartiallySignedTransaction::GetInputUTXO(CTxOut& utxo, int input_index) const
{
PSBTInput input = inputs[input_index];
const PSBTInput& input = inputs[input_index];
uint32_t prevout_index = tx->vin[input_index].prevout.n;
if (input.non_witness_utxo) {
if (prevout_index >= input.non_witness_utxo->vout.size()) {
return false;
}
if (input.non_witness_utxo->GetHash() != tx->vin[input_index].prevout.hash) {
return false;
}
utxo = input.non_witness_utxo->vout[prevout_index];
} else {
return false;
Expand Down Expand Up @@ -127,6 +137,10 @@ void PSBTInput::Merge(const PSBTInput& input)
if (!non_witness_utxo && input.non_witness_utxo) non_witness_utxo = input.non_witness_utxo;

partial_sigs.insert(input.partial_sigs.begin(), input.partial_sigs.end());
ripemd160_preimages.insert(input.ripemd160_preimages.begin(), input.ripemd160_preimages.end());
sha256_preimages.insert(input.sha256_preimages.begin(), input.sha256_preimages.end());
hash160_preimages.insert(input.hash160_preimages.begin(), input.hash160_preimages.end());
hash256_preimages.insert(input.hash256_preimages.begin(), input.hash256_preimages.end());
hd_keypaths.insert(input.hd_keypaths.begin(), input.hd_keypaths.end());
unknown.insert(input.unknown.begin(), input.unknown.end());

Expand Down Expand Up @@ -202,7 +216,24 @@ bool PSBTInputSigned(const PSBTInput& input)
return !input.final_script_sig.empty();
}

bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash, SignatureData* out_sigdata, bool use_dummy)
PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction& psbt)
{
const CMutableTransaction& tx = *psbt.tx;
bool have_all_spent_outputs = true;
std::vector<CTxOut> utxos(tx.vin.size());
for (size_t idx = 0; idx < tx.vin.size(); ++idx) {
if (!psbt.GetInputUTXO(utxos[idx], idx)) have_all_spent_outputs = false;
}
PrecomputedTransactionData txdata;
if (have_all_spent_outputs) {
txdata.Init(tx, std::move(utxos), true);
} else {
txdata.Init(tx, {}, true);
}
return txdata;
}

bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash, SignatureData* out_sigdata, bool finalize)
{
PSBTInput& input = psbt.inputs.at(index);
const CMutableTransaction& tx = *psbt.tx;
Expand Down Expand Up @@ -233,12 +264,16 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
}

bool sig_complete;
if (use_dummy) {
if (txdata == nullptr) {
sig_complete = ProduceSignature(provider, DUMMY_SIGNATURE_CREATOR, utxo.scriptPubKey, sigdata);
} else {
MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, sighash);
MutableTransactionSignatureCreator creator(&tx, index, utxo.nValue, txdata, sighash);
sig_complete = ProduceSignature(provider, creator, utxo.scriptPubKey, sigdata);
}

// If we are not finalizing, set sigdata.complete to false to not set the scriptSig
if (!finalize && sigdata.complete) sigdata.complete = false;

input.FromSignatureData(sigdata);

// Fill in the missing info
Expand All @@ -258,8 +293,9 @@ bool FinalizePSBT(PartiallySignedTransaction& psbtx)
// PartiallySignedTransaction did not understand them), this will combine them into a final
// script.
bool complete = true;
const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, SIGHASH_ALL);
complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, SIGHASH_ALL, nullptr, true);
}

return complete;
Expand Down Expand Up @@ -331,3 +367,11 @@ bool DecodeRawPSBT(PartiallySignedTransaction& psbt, const std::string& tx_data,
}
return true;
}

uint32_t PartiallySignedTransaction::GetVersion() const
{
if (m_version != std::nullopt) {
return *m_version;
}
return 0;
}
Loading
Loading