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.
This commit is contained in:
parent
78b9893d02
commit
102faad81e
6 changed files with 95 additions and 26 deletions
|
@ -29,6 +29,8 @@ const char* TransactionErrorString(const TransactionError err)
|
||||||
return "AcceptToMemoryPool failed";
|
return "AcceptToMemoryPool failed";
|
||||||
case TransactionError::INVALID_PSBT:
|
case TransactionError::INVALID_PSBT:
|
||||||
return "PSBT is not sane";
|
return "PSBT is not sane";
|
||||||
|
case TransactionError::PSBT_MISMATCH:
|
||||||
|
return "PSBTs not compatible (different transactions)";
|
||||||
case TransactionError::SIGHASH_MISMATCH:
|
case TransactionError::SIGHASH_MISMATCH:
|
||||||
return "Specified sighash value does not match existing value";
|
return "Specified sighash value does not match existing value";
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ enum class TransactionError {
|
||||||
MEMPOOL_REJECTED,
|
MEMPOOL_REJECTED,
|
||||||
MEMPOOL_ERROR,
|
MEMPOOL_ERROR,
|
||||||
INVALID_PSBT,
|
INVALID_PSBT,
|
||||||
|
PSBT_MISMATCH,
|
||||||
SIGHASH_MISMATCH,
|
SIGHASH_MISMATCH,
|
||||||
|
|
||||||
ERROR_COUNT
|
ERROR_COUNT
|
||||||
|
|
49
src/psbt.cpp
49
src/psbt.cpp
|
@ -232,3 +232,52 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
|
||||||
|
|
||||||
return sig_complete;
|
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
src/psbt.h
27
src/psbt.h
|
@ -544,4 +544,31 @@ bool PSBTInputSigned(PSBTInput& input);
|
||||||
/** Signs a PSBTInput, verifying that all provided data matches what is being signed. */
|
/** 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);
|
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
|
#endif // BITCOIN_PSBT_H
|
||||||
|
|
|
@ -1477,16 +1477,10 @@ UniValue combinepsbt(const JSONRPCRequest& request)
|
||||||
psbtxs.push_back(psbtx);
|
psbtxs.push_back(psbtx);
|
||||||
}
|
}
|
||||||
|
|
||||||
PartiallySignedTransaction merged_psbt(psbtxs[0]); // Copy the first one
|
PartiallySignedTransaction merged_psbt;
|
||||||
|
TransactionError error;
|
||||||
// Merge
|
if (!CombinePSBTs(merged_psbt, error, psbtxs)) {
|
||||||
for (auto it = std::next(psbtxs.begin()); it != psbtxs.end(); ++it) {
|
throw JSONRPCTransactionError(error);
|
||||||
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");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UniValue result(UniValue::VOBJ);
|
UniValue result(UniValue::VOBJ);
|
||||||
|
@ -1531,29 +1525,23 @@ UniValue finalizepsbt(const JSONRPCRequest& request)
|
||||||
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error));
|
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
|
bool extract = request.params[1].isNull() || (!request.params[1].isNull() && request.params[1].get_bool());
|
||||||
// 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
|
CMutableTransaction mtx;
|
||||||
// script.
|
bool complete = FinalizeAndExtractPSBT(psbtx, mtx);
|
||||||
bool complete = true;
|
|
||||||
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
|
||||||
complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, SIGHASH_ALL);
|
|
||||||
}
|
|
||||||
|
|
||||||
UniValue result(UniValue::VOBJ);
|
UniValue result(UniValue::VOBJ);
|
||||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
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) {
|
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;
|
ssTx << mtx;
|
||||||
result.pushKV("hex", HexStr(ssTx.str()));
|
result_str = HexStr(ssTx.str());
|
||||||
|
result.pushKV("hex", result_str);
|
||||||
} else {
|
} else {
|
||||||
ssTx << psbtx;
|
ssTx << psbtx;
|
||||||
result.pushKV("psbt", EncodeBase64(ssTx.str()));
|
result_str = EncodeBase64(ssTx.str());
|
||||||
|
result.pushKV("psbt", result_str);
|
||||||
}
|
}
|
||||||
result.pushKV("complete", complete);
|
result.pushKV("complete", complete);
|
||||||
|
|
||||||
|
|
|
@ -151,6 +151,8 @@ RPCErrorCode RPCErrorFromTransactionError(TransactionError terr)
|
||||||
case TransactionError::P2P_DISABLED:
|
case TransactionError::P2P_DISABLED:
|
||||||
return RPC_CLIENT_P2P_DISABLED;
|
return RPC_CLIENT_P2P_DISABLED;
|
||||||
case TransactionError::INVALID_PSBT:
|
case TransactionError::INVALID_PSBT:
|
||||||
|
case TransactionError::PSBT_MISMATCH:
|
||||||
|
return RPC_INVALID_PARAMETER;
|
||||||
case TransactionError::SIGHASH_MISMATCH:
|
case TransactionError::SIGHASH_MISMATCH:
|
||||||
return RPC_DESERIALIZATION_ERROR;
|
return RPC_DESERIALIZATION_ERROR;
|
||||||
default: break;
|
default: break;
|
||||||
|
|
Loading…
Reference in a new issue