mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-10 11:57:28 -03:00
Merge bitcoin/bitcoin#22513: rpc: Allow walletprocesspsbt to sign without finalizing
a99ed89865
psbt: sign without finalizing (Andrew Chow) Pull request description: It can be useful to sign an input with `walletprocesspsbt` but not finalize that input if it is complete. This PR adds another option to `walletprocesspsbt` to be able to do that. We will still finalize by default. This does not materially change the PSBT workflow since `finalizepsbt` needs to be called in order to extract the tx for broadcast. ACKs for top commit: meshcollider: utACKa99ed89865
Sjors: utACKa99ed89
Tree-SHA512: c88e5d3222109c5f4e763b1b9d97ce4655f68f2985a4509caab2d4e7f5bac5047328fd69696e82a330f5c5a333e0312568ae293515689b77a4747ca2f17caca6
This commit is contained in:
commit
383d350bd5
11 changed files with 32 additions and 20 deletions
|
@ -247,7 +247,7 @@ PrecomputedTransactionData PrecomputePSBTData(const PartiallySignedTransaction&
|
||||||
return txdata;
|
return txdata;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash, SignatureData* out_sigdata)
|
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);
|
PSBTInput& input = psbt.inputs.at(index);
|
||||||
const CMutableTransaction& tx = *psbt.tx;
|
const CMutableTransaction& tx = *psbt.tx;
|
||||||
|
@ -295,6 +295,10 @@ bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction&
|
||||||
}
|
}
|
||||||
// Verify that a witness signature was produced in case one was required.
|
// Verify that a witness signature was produced in case one was required.
|
||||||
if (require_witness_sig && !sigdata.witness) return false;
|
if (require_witness_sig && !sigdata.witness) return false;
|
||||||
|
|
||||||
|
// If we are not finalizing, set sigdata.complete to false to not set the scriptWitness
|
||||||
|
if (!finalize && sigdata.complete) sigdata.complete = false;
|
||||||
|
|
||||||
input.FromSignatureData(sigdata);
|
input.FromSignatureData(sigdata);
|
||||||
|
|
||||||
// If we have a witness signature, put a witness UTXO.
|
// If we have a witness signature, put a witness UTXO.
|
||||||
|
@ -324,7 +328,7 @@ bool FinalizePSBT(PartiallySignedTransaction& psbtx)
|
||||||
bool complete = true;
|
bool complete = true;
|
||||||
const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
|
const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
|
||||||
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
|
||||||
complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, SIGHASH_ALL);
|
complete &= SignPSBTInput(DUMMY_SIGNING_PROVIDER, psbtx, i, &txdata, SIGHASH_ALL, nullptr, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return complete;
|
return complete;
|
||||||
|
|
|
@ -578,7 +578,7 @@ bool PSBTInputSigned(const PSBTInput& input);
|
||||||
* txdata should be the output of PrecomputePSBTData (which can be shared across
|
* txdata should be the output of PrecomputePSBTData (which can be shared across
|
||||||
* multiple SignPSBTInput calls). If it is nullptr, a dummy signature will be created.
|
* multiple SignPSBTInput calls). If it is nullptr, a dummy signature will be created.
|
||||||
**/
|
**/
|
||||||
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr);
|
bool SignPSBTInput(const SigningProvider& provider, PartiallySignedTransaction& psbt, int index, const PrecomputedTransactionData* txdata, int sighash = SIGHASH_ALL, SignatureData* out_sigdata = nullptr, bool finalize = true);
|
||||||
|
|
||||||
/** Counts the unsigned inputs of a PSBT. */
|
/** Counts the unsigned inputs of a PSBT. */
|
||||||
size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt);
|
size_t CountPSBTUnsignedInputs(const PartiallySignedTransaction& psbt);
|
||||||
|
|
|
@ -115,6 +115,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||||
{ "walletcreatefundedpsbt", 4, "bip32derivs" },
|
{ "walletcreatefundedpsbt", 4, "bip32derivs" },
|
||||||
{ "walletprocesspsbt", 1, "sign" },
|
{ "walletprocesspsbt", 1, "sign" },
|
||||||
{ "walletprocesspsbt", 3, "bip32derivs" },
|
{ "walletprocesspsbt", 3, "bip32derivs" },
|
||||||
|
{ "walletprocesspsbt", 4, "finalize" },
|
||||||
{ "createpsbt", 0, "inputs" },
|
{ "createpsbt", 0, "inputs" },
|
||||||
{ "createpsbt", 1, "outputs" },
|
{ "createpsbt", 1, "outputs" },
|
||||||
{ "createpsbt", 2, "locktime" },
|
{ "createpsbt", 2, "locktime" },
|
||||||
|
|
|
@ -60,10 +60,10 @@ bool ExternalSignerScriptPubKeyMan::DisplayAddress(const CScript scriptPubKey, c
|
||||||
}
|
}
|
||||||
|
|
||||||
// If sign is true, transaction must previously have been filled
|
// If sign is true, transaction must previously have been filled
|
||||||
TransactionError ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const
|
TransactionError ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const
|
||||||
{
|
{
|
||||||
if (!sign) {
|
if (!sign) {
|
||||||
return DescriptorScriptPubKeyMan::FillPSBT(psbt, txdata, sighash_type, false, bip32derivs, n_signed);
|
return DescriptorScriptPubKeyMan::FillPSBT(psbt, txdata, sighash_type, false, bip32derivs, n_signed, finalize);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Already complete if every input is now signed
|
// Already complete if every input is now signed
|
||||||
|
@ -79,6 +79,6 @@ TransactionError ExternalSignerScriptPubKeyMan::FillPSBT(PartiallySignedTransact
|
||||||
tfm::format(std::cerr, "Failed to sign: %s\n", strFailReason);
|
tfm::format(std::cerr, "Failed to sign: %s\n", strFailReason);
|
||||||
return TransactionError::EXTERNAL_SIGNER_FAILED;
|
return TransactionError::EXTERNAL_SIGNER_FAILED;
|
||||||
}
|
}
|
||||||
FinalizePSBT(psbt); // This won't work in a multisig setup
|
if (finalize) FinalizePSBT(psbt); // This won't work in a multisig setup
|
||||||
return TransactionError::OK;
|
return TransactionError::OK;
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,6 @@ class ExternalSignerScriptPubKeyMan : public DescriptorScriptPubKeyMan
|
||||||
|
|
||||||
bool DisplayAddress(const CScript scriptPubKey, const ExternalSigner &signer) const;
|
bool DisplayAddress(const CScript scriptPubKey, const ExternalSigner &signer) const;
|
||||||
|
|
||||||
TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override;
|
TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override;
|
||||||
};
|
};
|
||||||
#endif // BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H
|
#endif // BITCOIN_WALLET_EXTERNAL_SIGNER_SCRIPTPUBKEYMAN_H
|
||||||
|
|
|
@ -4551,6 +4551,7 @@ static RPCHelpMan walletprocesspsbt()
|
||||||
" \"NONE|ANYONECANPAY\"\n"
|
" \"NONE|ANYONECANPAY\"\n"
|
||||||
" \"SINGLE|ANYONECANPAY\""},
|
" \"SINGLE|ANYONECANPAY\""},
|
||||||
{"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"},
|
{"bip32derivs", RPCArg::Type::BOOL, RPCArg::Default{true}, "Include BIP 32 derivation paths for public keys if we know them"},
|
||||||
|
{"finalize", RPCArg::Type::BOOL, RPCArg::Default{true}, "Also finalize inputs if possible"},
|
||||||
},
|
},
|
||||||
RPCResult{
|
RPCResult{
|
||||||
RPCResult::Type::OBJ, "", "",
|
RPCResult::Type::OBJ, "", "",
|
||||||
|
@ -4572,7 +4573,7 @@ static RPCHelpMan walletprocesspsbt()
|
||||||
// the user could have gotten from another RPC command prior to now
|
// the user could have gotten from another RPC command prior to now
|
||||||
wallet.BlockUntilSyncedToCurrentChain();
|
wallet.BlockUntilSyncedToCurrentChain();
|
||||||
|
|
||||||
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VSTR});
|
RPCTypeCheck(request.params, {UniValue::VSTR});
|
||||||
|
|
||||||
// Unserialize the transaction
|
// Unserialize the transaction
|
||||||
PartiallySignedTransaction psbtx;
|
PartiallySignedTransaction psbtx;
|
||||||
|
@ -4587,11 +4588,12 @@ static RPCHelpMan walletprocesspsbt()
|
||||||
// Fill transaction with our data and also sign
|
// Fill transaction with our data and also sign
|
||||||
bool sign = request.params[1].isNull() ? true : request.params[1].get_bool();
|
bool sign = request.params[1].isNull() ? true : request.params[1].get_bool();
|
||||||
bool bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool();
|
bool bip32derivs = request.params[3].isNull() ? true : request.params[3].get_bool();
|
||||||
|
bool finalize = request.params[4].isNull() ? true : request.params[4].get_bool();
|
||||||
bool complete = true;
|
bool complete = true;
|
||||||
|
|
||||||
if (sign) EnsureWalletIsUnlocked(*pwallet);
|
if (sign) EnsureWalletIsUnlocked(*pwallet);
|
||||||
|
|
||||||
const TransactionError err{wallet.FillPSBT(psbtx, complete, nHashType, sign, bip32derivs)};
|
const TransactionError err{wallet.FillPSBT(psbtx, complete, nHashType, sign, bip32derivs, nullptr, finalize)};
|
||||||
if (err != TransactionError::OK) {
|
if (err != TransactionError::OK) {
|
||||||
throw JSONRPCTransactionError(err);
|
throw JSONRPCTransactionError(err);
|
||||||
}
|
}
|
||||||
|
|
|
@ -610,7 +610,7 @@ SigningResult LegacyScriptPubKeyMan::SignMessage(const std::string& message, con
|
||||||
return SigningResult::SIGNING_FAILED;
|
return SigningResult::SIGNING_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const
|
TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const
|
||||||
{
|
{
|
||||||
if (n_signed) {
|
if (n_signed) {
|
||||||
*n_signed = 0;
|
*n_signed = 0;
|
||||||
|
@ -639,7 +639,7 @@ TransactionError LegacyScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psb
|
||||||
}
|
}
|
||||||
SignatureData sigdata;
|
SignatureData sigdata;
|
||||||
input.FillSignatureData(sigdata);
|
input.FillSignatureData(sigdata);
|
||||||
SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, &txdata, sighash_type);
|
SignPSBTInput(HidingSigningProvider(this, !sign, !bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize);
|
||||||
|
|
||||||
bool signed_one = PSBTInputSigned(input);
|
bool signed_one = PSBTInputSigned(input);
|
||||||
if (n_signed && (signed_one || !sign)) {
|
if (n_signed && (signed_one || !sign)) {
|
||||||
|
@ -2074,7 +2074,7 @@ SigningResult DescriptorScriptPubKeyMan::SignMessage(const std::string& message,
|
||||||
return SigningResult::OK;
|
return SigningResult::OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed) const
|
TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction& psbtx, const PrecomputedTransactionData& txdata, int sighash_type, bool sign, bool bip32derivs, int* n_signed, bool finalize) const
|
||||||
{
|
{
|
||||||
if (n_signed) {
|
if (n_signed) {
|
||||||
*n_signed = 0;
|
*n_signed = 0;
|
||||||
|
@ -2124,7 +2124,7 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, &txdata, sighash_type);
|
SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize);
|
||||||
|
|
||||||
bool signed_one = PSBTInputSigned(input);
|
bool signed_one = PSBTInputSigned(input);
|
||||||
if (n_signed && (signed_one || !sign)) {
|
if (n_signed && (signed_one || !sign)) {
|
||||||
|
|
|
@ -224,7 +224,7 @@ public:
|
||||||
/** Sign a message with the given script */
|
/** Sign a message with the given script */
|
||||||
virtual SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const { return SigningResult::SIGNING_FAILED; };
|
virtual SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const { return SigningResult::SIGNING_FAILED; };
|
||||||
/** Adds script and derivation path information to a PSBT, and optionally signs it. */
|
/** Adds script and derivation path information to a PSBT, and optionally signs it. */
|
||||||
virtual TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const { return TransactionError::INVALID_PSBT; }
|
virtual TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const { return TransactionError::INVALID_PSBT; }
|
||||||
|
|
||||||
virtual uint256 GetID() const { return uint256(); }
|
virtual uint256 GetID() const { return uint256(); }
|
||||||
|
|
||||||
|
@ -388,7 +388,7 @@ public:
|
||||||
|
|
||||||
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const override;
|
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const override;
|
||||||
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override;
|
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override;
|
||||||
TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override;
|
TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override;
|
||||||
|
|
||||||
uint256 GetID() const override;
|
uint256 GetID() const override;
|
||||||
|
|
||||||
|
@ -593,7 +593,7 @@ public:
|
||||||
|
|
||||||
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const override;
|
bool SignTransaction(CMutableTransaction& tx, const std::map<COutPoint, Coin>& coins, int sighash, std::map<int, bilingual_str>& input_errors) const override;
|
||||||
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override;
|
SigningResult SignMessage(const std::string& message, const PKHash& pkhash, std::string& str_sig) const override;
|
||||||
TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr) const override;
|
TransactionError FillPSBT(PartiallySignedTransaction& psbt, const PrecomputedTransactionData& txdata, int sighash_type = 1 /* SIGHASH_ALL */, bool sign = true, bool bip32derivs = false, int* n_signed = nullptr, bool finalize = true) const override;
|
||||||
|
|
||||||
uint256 GetID() const override;
|
uint256 GetID() const override;
|
||||||
|
|
||||||
|
|
|
@ -1849,7 +1849,7 @@ bool CWallet::SignTransaction(CMutableTransaction& tx, const std::map<COutPoint,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& complete, int sighash_type, bool sign, bool bip32derivs, size_t * n_signed) const
|
TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& complete, int sighash_type, bool sign, bool bip32derivs, size_t * n_signed, bool finalize) const
|
||||||
{
|
{
|
||||||
if (n_signed) {
|
if (n_signed) {
|
||||||
*n_signed = 0;
|
*n_signed = 0;
|
||||||
|
@ -1881,7 +1881,7 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp
|
||||||
// Fill in information from ScriptPubKeyMans
|
// Fill in information from ScriptPubKeyMans
|
||||||
for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) {
|
for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) {
|
||||||
int n_signed_this_spkm = 0;
|
int n_signed_this_spkm = 0;
|
||||||
TransactionError res = spk_man->FillPSBT(psbtx, txdata, sighash_type, sign, bip32derivs, &n_signed_this_spkm);
|
TransactionError res = spk_man->FillPSBT(psbtx, txdata, sighash_type, sign, bip32derivs, &n_signed_this_spkm, finalize);
|
||||||
if (res != TransactionError::OK) {
|
if (res != TransactionError::OK) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
|
@ -555,6 +555,8 @@ public:
|
||||||
* @param[in] sighash_type the sighash type to use when signing (if PSBT does not specify)
|
* @param[in] sighash_type the sighash type to use when signing (if PSBT does not specify)
|
||||||
* @param[in] sign whether to sign or not
|
* @param[in] sign whether to sign or not
|
||||||
* @param[in] bip32derivs whether to fill in bip32 derivation information if available
|
* @param[in] bip32derivs whether to fill in bip32 derivation information if available
|
||||||
|
* @param[out] n_signed the number of inputs signed by this wallet
|
||||||
|
* @param[in] finalize whether to create the final scriptSig or scriptWitness if possible
|
||||||
* return error
|
* return error
|
||||||
*/
|
*/
|
||||||
TransactionError FillPSBT(PartiallySignedTransaction& psbtx,
|
TransactionError FillPSBT(PartiallySignedTransaction& psbtx,
|
||||||
|
@ -562,7 +564,8 @@ public:
|
||||||
int sighash_type = 1 /* SIGHASH_ALL */,
|
int sighash_type = 1 /* SIGHASH_ALL */,
|
||||||
bool sign = true,
|
bool sign = true,
|
||||||
bool bip32derivs = true,
|
bool bip32derivs = true,
|
||||||
size_t* n_signed = nullptr) const;
|
size_t* n_signed = nullptr,
|
||||||
|
bool finalize = true) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submit the transaction to the node's mempool and then relay to peers.
|
* Submit the transaction to the node's mempool and then relay to peers.
|
||||||
|
|
|
@ -120,7 +120,9 @@ class PSBTTest(BitcoinTestFramework):
|
||||||
self.nodes[0].walletpassphrase(passphrase="password", timeout=1000000)
|
self.nodes[0].walletpassphrase(passphrase="password", timeout=1000000)
|
||||||
|
|
||||||
# Sign the transaction and send
|
# Sign the transaction and send
|
||||||
signed_tx = self.nodes[0].walletprocesspsbt(psbtx)['psbt']
|
signed_tx = self.nodes[0].walletprocesspsbt(psbt=psbtx, finalize=False)['psbt']
|
||||||
|
finalized_tx = self.nodes[0].walletprocesspsbt(psbt=psbtx, finalize=True)['psbt']
|
||||||
|
assert signed_tx != finalized_tx
|
||||||
final_tx = self.nodes[0].finalizepsbt(signed_tx)['hex']
|
final_tx = self.nodes[0].finalizepsbt(signed_tx)['hex']
|
||||||
self.nodes[0].sendrawtransaction(final_tx)
|
self.nodes[0].sendrawtransaction(final_tx)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue