aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/node/transaction.cpp2
-rw-r--r--src/node/transaction.h1
-rw-r--r--src/psbt.cpp49
-rw-r--r--src/psbt.h27
-rw-r--r--src/rpc/rawtransaction.cpp40
-rw-r--r--src/rpc/util.cpp2
6 files changed, 95 insertions, 26 deletions
diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp
index 6c4efb3d26..c9cdd0d1cd 100644
--- a/src/node/transaction.cpp
+++ b/src/node/transaction.cpp
@@ -29,6 +29,8 @@ const char* TransactionErrorString(const TransactionError err)
return "AcceptToMemoryPool failed";
case TransactionError::INVALID_PSBT:
return "PSBT is not sane";
+ case TransactionError::PSBT_MISMATCH:
+ return "PSBTs not compatible (different transactions)";
case TransactionError::SIGHASH_MISMATCH:
return "Specified sighash value does not match existing value";
diff --git a/src/node/transaction.h b/src/node/transaction.h
index 83354d9400..3b0cbba98b 100644
--- a/src/node/transaction.h
+++ b/src/node/transaction.h
@@ -18,6 +18,7 @@ enum class TransactionError {
MEMPOOL_REJECTED,
MEMPOOL_ERROR,
INVALID_PSBT,
+ PSBT_MISMATCH,
SIGHASH_MISMATCH,
ERROR_COUNT
diff --git a/src/psbt.cpp b/src/psbt.cpp
index 06032d6953..81633c0cc7 100644
--- a/src/psbt.cpp
+++ b/src/psbt.cpp
@@ -232,3 +232,52 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
return sig_complete;
}
+
+bool FinalizePSBT(PartiallySignedTransaction& psbtx)
+{
+ // Finalize input signatures -- in case we have partial signatures that add up to a complete
+ // signature, but have not combined them yet (e.g. because the combiner that created this
+ // PartiallySignedTransaction did not understand them), this will combine them into a final
+ // script.
+ bool complete = true;
+ for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
+ complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, SIGHASH_ALL);
+ }
+
+ return complete;
+}
+
+bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransaction& result)
+{
+ // It's not safe to extract a PSBT that isn't finalized, and there's no easy way to check
+ // whether a PSBT is finalized without finalizing it, so we just do this.
+ if (!FinalizePSBT(psbtx)) {
+ return false;
+ }
+
+ result = *psbtx.tx;
+ for (unsigned int i = 0; i < result.vin.size(); ++i) {
+ result.vin[i].scriptSig = psbtx.inputs[i].final_script_sig;
+ result.vin[i].scriptWitness = psbtx.inputs[i].final_script_witness;
+ }
+ return true;
+}
+
+bool CombinePSBTs(PartiallySignedTransaction& out, TransactionError& error, const std::vector<PartiallySignedTransaction>& psbtxs)
+{
+ out = psbtxs[0]; // Copy the first one
+
+ // Merge
+ for (auto it = std::next(psbtxs.begin()); it != psbtxs.end(); ++it) {
+ if (!out.Merge(*it)) {
+ error = TransactionError::PSBT_MISMATCH;
+ return false;
+ }
+ }
+ if (!out.IsSane()) {
+ error = TransactionError::INVALID_PSBT;
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/psbt.h b/src/psbt.h
index 4b7ea4383a..e18790322b 100644
--- a/src/psbt.h
+++ b/src/psbt.h
@@ -544,4 +544,31 @@ bool PSBTInputSigned(PSBTInput& input);
/** Signs a PSBTInput, verifying that all provided data matches what is being signed. */
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, int sighash = SIGHASH_ALL);
+/**
+ * Finalizes a PSBT if possible, combining partial signatures.
+ *
+ * @param[in,out] &psbtx reference to PartiallySignedTransaction to finalize
+ * return True if the PSBT is now complete, false otherwise
+ */
+bool FinalizePSBT(PartiallySignedTransaction& psbtx);
+
+/**
+ * Finalizes a PSBT if possible, and extracts it to a CMutableTransaction if it could be finalized.
+ *
+ * @param[in] &psbtx reference to PartiallySignedTransaction
+ * @param[out] result CMutableTransaction representing the complete transaction, if successful
+ * @return True if we successfully extracted the transaction, false otherwise
+ */
+bool FinalizeAndExtractPSBT(PartiallySignedTransaction& psbtx, CMutableTransaction& result);
+
+/**
+ * Combines PSBTs with the same underlying transaction, resulting in a single PSBT with all partial signatures from each input.
+ *
+ * @param[out] &out the combined PSBT, if successful
+ * @param[out] &error reference to TransactionError to fill with error info on failure
+ * @param[in] psbtxs the PSBTs to combine
+ * @return True if we successfully combined the transactions, false if they were not compatible
+ */
+bool CombinePSBTs(PartiallySignedTransaction& out, TransactionError& error, const std::vector<PartiallySignedTransaction>& psbtxs);
+
#endif // BITCOIN_PSBT_H
diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp
index 4205a3d10f..f511cbbdab 100644
--- a/src/rpc/rawtransaction.cpp
+++ b/src/rpc/rawtransaction.cpp
@@ -1477,16 +1477,10 @@ UniValue combinepsbt(const JSONRPCRequest& request)
psbtxs.push_back(psbtx);
}
- PartiallySignedTransaction merged_psbt(psbtxs[0]); // Copy the first one
-
- // Merge
- for (auto it = std::next(psbtxs.begin()); it != psbtxs.end(); ++it) {
- if (!merged_psbt.Merge(*it)) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "PSBTs do not refer to the same transactions.");
- }
- }
- if (!merged_psbt.IsSane()) {
- throw JSONRPCError(RPC_INVALID_PARAMETER, "Merged PSBT is inconsistent");
+ PartiallySignedTransaction merged_psbt;
+ TransactionError error;
+ if (!CombinePSBTs(merged_psbt, error, psbtxs)) {
+ throw JSONRPCTransactionError(error);
}
UniValue result(UniValue::VOBJ);
@@ -1531,29 +1525,23 @@ UniValue finalizepsbt(const JSONRPCRequest& request)
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
}
- // Finalize input signatures -- in case we have partial signatures that add up to a complete
- // signature, but have not combined them yet (e.g. because the combiner that created this
- // PartiallySignedTransaction did not understand them), this will combine them into a final
- // script.
- bool complete = true;
- for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
- complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, SIGHASH_ALL);
- }
+ bool extract = request.params[1].isNull() || (!request.params[1].isNull() && request.params[1].get_bool());
+
+ CMutableTransaction mtx;
+ bool complete = FinalizeAndExtractPSBT(psbtx, mtx);
UniValue result(UniValue::VOBJ);
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
- bool extract = request.params[1].isNull() || (!request.params[1].isNull() && request.params[1].get_bool());
+ std::string result_str;
+
if (complete && extract) {
- CMutableTransaction mtx(*psbtx.tx);
- for (unsigned int i = 0; i < mtx.vin.size(); ++i) {
- mtx.vin[i].scriptSig = psbtx.inputs[i].final_script_sig;
- mtx.vin[i].scriptWitness = psbtx.inputs[i].final_script_witness;
- }
ssTx << mtx;
- result.pushKV("hex", HexStr(ssTx.str()));
+ result_str = HexStr(ssTx.str());
+ result.pushKV("hex", result_str);
} else {
ssTx << psbtx;
- result.pushKV("psbt", EncodeBase64(ssTx.str()));
+ result_str = EncodeBase64(ssTx.str());
+ result.pushKV("psbt", result_str);
}
result.pushKV("complete", complete);
diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp
index 9e825ac12a..653568044d 100644
--- a/src/rpc/util.cpp
+++ b/src/rpc/util.cpp
@@ -151,6 +151,8 @@ RPCErrorCode RPCErrorFromTransactionError(TransactionError terr)
case TransactionError::P2P_DISABLED:
return RPC_CLIENT_P2P_DISABLED;
case TransactionError::INVALID_PSBT:
+ case TransactionError::PSBT_MISMATCH:
+ return RPC_INVALID_PARAMETER;
case TransactionError::SIGHASH_MISMATCH:
return RPC_DESERIALIZATION_ERROR;
default: break;