Skip to content

Commit

Permalink
Factor out combine / finalize / extract PSBT helpers
Browse files Browse the repository at this point in the history
Refactor the new CombinePSBT, FinalizePSBT, and FinalizeAndExtractPSBT
general-purpose functions out of the combinepsbt and finalizepsbt RPCs,
for use in the GUI code.
  • Loading branch information
gwillen committed Feb 11, 2019
1 parent 78b9893 commit 102faad
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 26 deletions.
2 changes: 2 additions & 0 deletions src/node/transaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down
1 change: 1 addition & 0 deletions src/node/transaction.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ enum class TransactionError {
MEMPOOL_REJECTED,
MEMPOOL_ERROR,
INVALID_PSBT,
PSBT_MISMATCH,
SIGHASH_MISMATCH,

ERROR_COUNT
Expand Down
49 changes: 49 additions & 0 deletions src/psbt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
27 changes: 27 additions & 0 deletions src/psbt.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
40 changes: 14 additions & 26 deletions src/rpc/rawtransaction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);

Expand Down
2 changes: 2 additions & 0 deletions src/rpc/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 102faad

Please sign in to comment.