Skip to content

Commit 102faad

Browse files
committed
Factor out combine / finalize / extract PSBT helpers
Refactor the new CombinePSBT, FinalizePSBT, and FinalizeAndExtractPSBT general-purpose functions out of the combinepsbt and finalizepsbt RPCs, for use in the GUI code.
1 parent 78b9893 commit 102faad

File tree

6 files changed

+95
-26
lines changed

6 files changed

+95
-26
lines changed

src/node/transaction.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ const char* TransactionErrorString(const TransactionError err)
2929
return "AcceptToMemoryPool failed";
3030
case TransactionError::INVALID_PSBT:
3131
return "PSBT is not sane";
32+
case TransactionError::PSBT_MISMATCH:
33+
return "PSBTs not compatible (different transactions)";
3234
case TransactionError::SIGHASH_MISMATCH:
3335
return "Specified sighash value does not match existing value";
3436

src/node/transaction.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ enum class TransactionError {
1818
MEMPOOL_REJECTED,
1919
MEMPOOL_ERROR,
2020
INVALID_PSBT,
21+
PSBT_MISMATCH,
2122
SIGHASH_MISMATCH,
2223

2324
ERROR_COUNT

src/psbt.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,3 +232,52 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
232232

233233
return sig_complete;
234234
}
235+
236+
bool FinalizePSBT(PartiallySignedTransaction& psbtx)
237+
{
238+
// Finalize input signatures -- in case we have partial signatures that add up to a complete
239+
// signature, but have not combined them yet (e.g. because the combiner that created this
240+
// PartiallySignedTransaction did not understand them), this will combine them into a final
241+
// script.
242+
bool complete = true;
243+
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
244+
complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, SIGHASH_ALL);
245+
}
246+
247+
return complete;
248+
}
249+
250+
bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransaction& result)
251+
{
252+
// It's not safe to extract a PSBT that isn't finalized, and there's no easy way to check
253+
// whether a PSBT is finalized without finalizing it, so we just do this.
254+
if (!FinalizePSBT(psbtx)) {
255+
return false;
256+
}
257+
258+
result = *psbtx.tx;
259+
for (unsigned int i = 0; i < result.vin.size(); ++i) {
260+
result.vin[i].scriptSig = psbtx.inputs[i].final_script_sig;
261+
result.vin[i].scriptWitness = psbtx.inputs[i].final_script_witness;
262+
}
263+
return true;
264+
}
265+
266+
bool CombinePSBTs(PartiallySignedTransaction& out, TransactionError& error, const std::vector<PartiallySignedTransaction>& psbtxs)
267+
{
268+
out = psbtxs[0]; // Copy the first one
269+
270+
// Merge
271+
for (auto it = std::next(psbtxs.begin()); it != psbtxs.end(); ++it) {
272+
if (!out.Merge(*it)) {
273+
error = TransactionError::PSBT_MISMATCH;
274+
return false;
275+
}
276+
}
277+
if (!out.IsSane()) {
278+
error = TransactionError::INVALID_PSBT;
279+
return false;
280+
}
281+
282+
return true;
283+
}

src/psbt.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,4 +544,31 @@ bool PSBTInputSigned(PSBTInput& input);
544544
/** Signs a PSBTInput, verifying that all provided data matches what is being signed. */
545545
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash = SIGHASH_ALL);
546546

547+
/**
548+
* Finalizes a PSBT if possible, combining partial signatures.
549+
*
550+
* @param[in,out] &psbtx reference to PartiallySignedTransaction to finalize
551+
* return True if the PSBT is now complete, false otherwise
552+
*/
553+
bool FinalizePSBT(PartiallySignedTransaction& psbtx);
554+
555+
/**
556+
* Finalizes a PSBT if possible, and extracts it to a CMutableTransaction if it could be finalized.
557+
*
558+
* @param[in] &psbtx reference to PartiallySignedTransaction
559+
* @param[out] result CMutableTransaction representing the complete transaction, if successful
560+
* @return True if we successfully extracted the transaction, false otherwise
561+
*/
562+
bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransaction& result);
563+
564+
/**
565+
* Combines PSBTs with the same underlying transaction, resulting in a single PSBT with all partial signatures from each input.
566+
*
567+
* @param[out] &out the combined PSBT, if successful
568+
* @param[out] &error reference to TransactionError to fill with error info on failure
569+
* @param[in] psbtxs the PSBTs to combine
570+
* @return True if we successfully combined the transactions, false if they were not compatible
571+
*/
572+
bool CombinePSBTs(PartiallySignedTransaction& out, TransactionError& error, const std::vector<PartiallySignedTransaction>& psbtxs);
573+
547574
#endif // BITCOIN_PSBT_H

src/rpc/rawtransaction.cpp

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1477,16 +1477,10 @@ UniValue combinepsbt(const JSONRPCRequest& request)
14771477
psbtxs.push_back(psbtx);
14781478
}
14791479

1480-
PartiallySignedTransaction merged_psbt(psbtxs[0]); // Copy the first one
1481-
1482-
// Merge
1483-
for (auto it = std::next(psbtxs.begin()); it != psbtxs.end(); ++it) {
1484-
if (!merged_psbt.Merge(*it)) {
1485-
throw JSONRPCError(RPC_INVALID_PARAMETER, "PSBTs do not refer to the same transactions.");
1486-
}
1487-
}
1488-
if (!merged_psbt.IsSane()) {
1489-
throw JSONRPCError(RPC_INVALID_PARAMETER, "Merged PSBT is inconsistent");
1480+
PartiallySignedTransaction merged_psbt;
1481+
TransactionError error;
1482+
if (!CombinePSBTs(merged_psbt, error, psbtxs)) {
1483+
throw JSONRPCTransactionError(error);
14901484
}
14911485

14921486
UniValue result(UniValue::VOBJ);
@@ -1531,29 +1525,23 @@ UniValue finalizepsbt(const JSONRPCRequest& request)
15311525
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
15321526
}
15331527

1534-
// Finalize input signatures -- in case we have partial signatures that add up to a complete
1535-
// signature, but have not combined them yet (e.g. because the combiner that created this
1536-
// PartiallySignedTransaction did not understand them), this will combine them into a final
1537-
// script.
1538-
bool complete = true;
1539-
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
1540-
complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, SIGHASH_ALL);
1541-
}
1528+
bool extract = request.params[1].isNull() || (!request.params[1].isNull() && request.params[1].get_bool());
1529+
1530+
CMutableTransaction mtx;
1531+
bool complete = FinalizeAndExtractPSBT(psbtx, mtx);
15421532

15431533
UniValue result(UniValue::VOBJ);
15441534
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
1545-
bool extract = request.params[1].isNull() || (!request.params[1].isNull() && request.params[1].get_bool());
1535+
std::string result_str;
1536+
15461537
if (complete && extract) {
1547-
CMutableTransaction mtx(*psbtx.tx);
1548-
for (unsigned int i = 0; i < mtx.vin.size(); ++i) {
1549-
mtx.vin[i].scriptSig = psbtx.inputs[i].final_script_sig;
1550-
mtx.vin[i].scriptWitness = psbtx.inputs[i].final_script_witness;
1551-
}
15521538
ssTx << mtx;
1553-
result.pushKV("hex", HexStr(ssTx.str()));
1539+
result_str = HexStr(ssTx.str());
1540+
result.pushKV("hex", result_str);
15541541
} else {
15551542
ssTx << psbtx;
1556-
result.pushKV("psbt", EncodeBase64(ssTx.str()));
1543+
result_str = EncodeBase64(ssTx.str());
1544+
result.pushKV("psbt", result_str);
15571545
}
15581546
result.pushKV("complete", complete);
15591547

src/rpc/util.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ RPCErrorCode RPCErrorFromTransactionError(TransactionError terr)
151151
case TransactionError::P2P_DISABLED:
152152
return RPC_CLIENT_P2P_DISABLED;
153153
case TransactionError::INVALID_PSBT:
154+
case TransactionError::PSBT_MISMATCH:
155+
return RPC_INVALID_PARAMETER;
154156
case TransactionError::SIGHASH_MISMATCH:
155157
return RPC_DESERIALIZATION_ERROR;
156158
default: break;

0 commit comments

Comments
 (0)