-
Notifications
You must be signed in to change notification settings - Fork 37.7k
wallet: Pass in transactions and messages for signing instead of exporting the private keys #18115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. ConflictsReviewers, this pull request conflicts with the following ones:
If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first. |
1145c59
to
885fa89
Compare
@kallewoof may find this interesting too in light of BIP-322 (Generic signed message format) #16440 |
case SigningError::OK: | ||
error = tr("No error"); | ||
case SigningError::PRIVATE_KEY_NOT_AVAILABLE: | ||
error = tr("Private key for the entered address is not available."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing break;
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also these strings can be obtained fromSigningErrorString
in error.cpp
(the wording here seems better)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The strings from SigningErrorString
won't be translated (we don't translate things for RPC), so that logic had to be duplicated here. Also, the strings are different to maintain compatibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added the breaks in.
https://github.com/achow101/bitcoin/blob/e4714e0/src/rpc/misc.cpp#L340 still calls the old signing code |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept ACK. I roughly reviewed code for e4714e0 and left some comments.
0eb285f Refactor rawtransaction's SignTransaction into generic SignTransaction function
would be easier to review (--color-moved=dimmed-zebra -w
) if you renamed the variables in separate commit.
It may be better to combine 4595326 with be7991f so the signing code is moved, rather than just appear (though it's short enough to compare manually).
Lastly,
GetSigningProvider()
is renamed toGetPublicSigningProvider()
. It will now only provide pubkeys, key origins, and scripts.LegacySigningProvider
has it'sGetKey
andHaveKey
functions changed to only return false. Future implementations should returnHidingSigningProvider
s where private keys are hidden.
This bit is fairly confusing.
This PR is fairly large, and only deduplicates a subset of @vasild's #17577 (only what's needed to move forward with the wallet refactor). I didn't study this other PR in detail, but if it's possible to build on top of that, it would split the review burden and remove more duplication.
|
||
// amount must be specified for valid segwit signature | ||
if (amount == MAX_MONEY && !txin.scriptWitness.IsNull()) { | ||
input_errors[i] = "Missing amount"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 0eb285f this seems a bit brittle: input_errors[i] = "Missing amount";
(which is then matched by string to throw an exception later). But at least this error message is covered by rpc_rawtransaction.py
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is brittle, and I'm not sure why only that error specifically throws an exception. But keeping it for compatibility.
if (!VerifyScript(txin.scriptSig, spk, &txin.scriptWitness, STANDARD_SCRIPT_VERIFY_FLAGS, TransactionSignatureChecker(&tx_const, i, amount), &serror)) { | ||
if (serror == SCRIPT_ERR_INVALID_STACK_OPERATION) { | ||
// Unable to sign input and verification failed (possible attempt to partially sign). | ||
input_errors[i] = "Unable to sign input, invalid stack size (possibly missing key)"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a good time to add tests for the three error strings used here (SCRIPT_ERR_INVALID_STACK_OPERATION
, SCRIPT_ERR_SIG_NULLFAIL
, else
). Not sure how though.
src/wallet/wallet.cpp
Outdated
// Sign the tx with ScriptPubKeyMans | ||
bool result = false; | ||
for (const auto& spk_man_pair : m_spk_managers) { | ||
result |= spk_man_pair.second->SignTransaction(tx, coins, sighash, input_errors); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way input_errors
are set, if there are multiple matching ScriptPubKeyMans
* they would override each-others results. The last commit e4714e0 clears the error for any given input, but only if SPKman SignTransaction() returns true, which requires all input errors to be empty, so that seems useless. At minimum this is confusing.
*
= not the case with legacy, and in practice shouldn't happen with descriptor wallets, but that's not enforced
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The expectation is that all of the errors will be the same across all failed ScriptPubKeyMan
s because they don't have the private keys. The ScriptPubKeyMan
which fully signs an input will clear the error for that input. This clearing is not dependent on input_errors.empty()
.
case SigningError::OK: | ||
error = tr("No error"); | ||
case SigningError::PRIVATE_KEY_NOT_AVAILABLE: | ||
error = tr("Private key for the entered address is not available."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also these strings can be obtained fromSigningErrorString
in error.cpp
(the wording here seems better)
46a5deb
to
62a66ca
Compare
I've undone the renames.
Squashed them together.
How so? I'm just saying that
I'll take a look at that.
Done |
62a66ca
to
01839f3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looked at code a bit, but not extensively.
The SignMessage for each scriptpubman is a step backwards considering BIP322 will unify these all into a single place. It would be great if you reviewed that PR (if you already have, and still feel this is the right approach, I'd love to know why).
I'll review/test this PR as well and comment more generally but wanted to put this out there now.
I am trying to merge BIP322 #16440 on top of this, and am running into an issue demonstrated by the below patch: diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index bde8a6099..dde7819fa 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -559,6 +559,14 @@ static UniValue signmessage(const JSONRPCRequest& request)
}
const PKHash *pkhash = boost::get<PKHash>(&dest);
+
+ auto provider = pwallet->GetPublicSigningProvider(GetScriptForDestination(dest));
+ auto keyid = GetKeyForDestination(*provider, dest);
+ CKey secret;
+ if (!provider->GetKey(keyid, secret)) {
+ assert(0);
+ }
+
if (!pkhash) {
throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");
} The above code hits the assert. I.e. despite getting a signing provider for the destination, it still fails to get the key. This code (i.e. getting signing provider and then getting privkey from that) in current master. |
You can still have the message signing code unified to a single place, and in fact, this PR does that (it consolidates the This change means that a future
That's because you are still using the old code and way of signing messages. There is no need for Specifically the reason this hits the assert is that the |
01839f3
to
bb7af89
Compare
I've based this on top of #17577 |
Ahh. In that case |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Light code review / build / test run ACK 08b9b68
If Empact, instagibbs, or Sjors could post-rebase re-ACK I'll merge this
Will do after @achow101 fixes the Travis build failure :-) https://travis-ci.org/bitcoin/bitcoin/jobs/659844515#L2418 Marking |
…iptPubKeyMan::FillPSBT Instead of fetching a SigningProvider from ScriptPubKeyMan in order to fill and sign the keys and scripts for a PSBT, just pass that PSBT to a new FillPSBT function that does all that for us.
…allet and ScriptPubKeyMan Instead of getting a SigningProvider and then going to MessageSign, have ScriptPubKeyMan handle the message signing internally.
Not all ScriptPubKeyMans will be able to provide private keys, but pubkeys and scripts should be. So only provide public-only SigningProviders, i.e. ones that can help with Solving.
Make sure that there are no errors set for an input after it is signed. This is useful for when there are multiple ScriptPubKeyMans. Some may fail to sign, but one may be able to sign, and after it does, we don't want there to be any more errors there.
reACK d2774c0 |
I did that, but I don't think it will help. I'm not sure what would cause that travis failure; it's failing on something that the other targets are compiling too. I think travis may have gotten confused? |
re-utACK d2774c0 |
1 similar comment
re-utACK d2774c0 |
…gning instead of exporting the private keys d2774c0 Clear any input_errors for an input after it is signed (Andrew Chow) dc17488 Replace GetSigningProvider with GetSolvingProvider (Andrew Chow) 6a9c429 Move direct calls to MessageSign into new SignMessage functions in CWallet and ScriptPubKeyMan (Andrew Chow) 82a30fa Move key and script filling and signing from CWallet::FillPSBT to ScriptPubKeyMan::FillPSBT (Andrew Chow) 3d70dd9 Move FillPSBT to be a member of CWallet (Andrew Chow) a4af324 Use CWallet::SignTransaction in CreateTransaction and signrawtransactionwithwallet (Andrew Chow) f37de92 Implement CWallet::SignTransaction using ScriptPubKeyMan::SignTransaction (Andrew Chow) d999dd5 Add SignTransaction function to ScriptPubKeyMan and LegacyScriptPubKeyMan (Andrew Chow) 2c52b59 Refactor rawtransaction's SignTransaction into generic SignTransaction function (Andrew Chow) Pull request description: Following bitcoin#17261, the way to sign transactions, PSBTs, and messages was to use `GetSigningProvider()` and get a `SigningProvider` containing the private keys. However this may not be feasible for future `ScriptPubKeyMan`s, such as for hardware wallets. Instead of exporting a `SigningProvider` containing private keys, we need to pass these things into the `ScriptPubKeyMan` (via `CWallet`) so that they can do whatever is needed internally to sign them. This is largely a refactor as the logic of processing transactions, PSBTs, and messages for is moved into `LegacyScriptPubKeyMan` and `CWallet` instead of being handled by the caller (e.g. `signrawtransaction`). To help with this, I've refactored the 3(!) implementations of a `SignTransaction()` function into one generic one. This function will be called by `signrawtransactionwithkey` and `LegacyScriptPubKeyMan::SignTransaction()`. `CWallet::CreateTransaction()` is changed to call `CWallet::SignTransaction()` which in turn, calls `LegacyScriptPubKeyMan::SignTransaction()`. Other `ScriptPubKeyMan`s may implement `SignTransaction()` differently. `FillPSBT()` is moved to be a member function of `CWallet` and the `psbtwallet.cpp/h` files removed. It is further split so that `CWallet` handles filling the UTXOs while the `ScriptPubKeyMan` handles adding keys, derivation paths, scripts, and signatures. In the end `LegacyScriptPubKeyMan::FillPSBT` still calls `SignPSBTInput`, but the `SigningProvider` is internal to `LegacyScriptPubKeyMan`. Other `ScriptPubKeyMan`s may do something different. A new `SignMessage()` function is added to both `CWallet` and `ScriptPubKeyMan`. Instead of having the caller (i.e. `signmessage` or the sign message dialog) get the private key, hash the message, and sign, `ScriptPubKeyMan` will now handle that (`CWallet` passes through to the `ScriptPubKeyMan`s as it does for many functions). This signing code is thus consolidated into `LegacyScriptPubKeyMan::SignMessage()`, though other `ScriptPubKeyMan`s may implement it differently. Additionally, a `SigningError` enum is introduced for the different errors that we expect to see from `SignMessage()`. Lastly, `GetSigningProvider()` is renamed to `GetPublicSigningProvider()`. It will now only provide pubkeys, key origins, and scripts. `LegacySigningProvider` has it's `GetKey` and `HaveKey` functions changed to only return false. Future implementations should return `HidingSigningProvider`s where private keys are hidden. Other things like `dumpprivkey` and `dumpwallet` are not changed because they directly need and access the `LegacyScriptPubKeyMan` so are not relevant to future changes. ACKs for top commit: instagibbs: reACK bitcoin@d2774c0 Sjors: re-utACK d2774c0 meshcollider: re-utACK d2774c0 Tree-SHA512: 89c83e7e7e9315e283fae145a2264648a9d7f7ace8f3281cb3f44f0b013c988d67ba4fa9726e50c643c0ed921bdd269adaec984840d11acf4a681f3e8a582cc1
these were removed as part of bitcoin#18115 We should find a way to deal with it in the future
…n function Summary: Backport of Core [[bitcoin/bitcoin#18115 | PR18115]] part [1/9] : bitcoin/bitcoin@2c52b59 Test Plan: ninja all check-all Reviewers: #bitcoin_abc, PiRK Reviewed By: PiRK Differential Revision: https://reviews.bitcoinabc.org/D8094
…yMan Summary: Backport of Core [[bitcoin/bitcoin#18115 | PR18115]] part [2/9] : bitcoin/bitcoin@d999dd5 Depends on D8094 Test Plan: ninja all check-all Reviewers: #bitcoin_abc, PiRK Reviewed By: PiRK Differential Revision: https://reviews.bitcoinabc.org/D8095
…tion Summary: Backport of Core [[bitcoin/bitcoin#18115 | PR18115]] part [3/9] : bitcoin/bitcoin@f37de92 Depends on D8095 Test Plan: ninja all check-all Reviewers: #bitcoin_abc, PiRK Reviewed By: PiRK Differential Revision: https://reviews.bitcoinabc.org/D8096
…ionwithwallet Summary: Instead of duplicating signing code, just use the function we already have. Backport of Core [[bitcoin/bitcoin#18115 | PR18115]] part [4/9] : bitcoin/bitcoin@a4af324 Depends on D8098 Test Plan: ninja all check-all Reviewers: #bitcoin_abc, majcosta Reviewed By: #bitcoin_abc, majcosta Differential Revision: https://reviews.bitcoinabc.org/D8100
Summary: Make sure that there are no errors set for an input after it is signed. This is useful for when there are multiple ScriptPubKeyMans. Some may fail to sign, but one may be able to sign, and after it does, we don't want there to be any more errors there. Backport of Core [[bitcoin/bitcoin#18115 | PR18115]] part [9/9] : bitcoin/bitcoin@d2774c0 Test Plan: ninja all check-all Reviewers: #bitcoin_abc, majcosta Reviewed By: #bitcoin_abc, majcosta Differential Revision: https://reviews.bitcoinabc.org/D8103
Summary: Backport of Core [[bitcoin/bitcoin#18115 | PR18115]] part [5/9] : bitcoin/bitcoin@3d70dd9 Test Plan: ninja all check-all Reviewers: #bitcoin_abc, PiRK Reviewed By: PiRK Subscribers: PiRK Differential Revision: https://reviews.bitcoinabc.org/D8097
…iptPubKeyMan::FillPSBT Summary: Instead of fetching a SigningProvider from ScriptPubKeyMan in order to fill and sign the keys and scripts for a PSBT, just pass that PSBT to a new FillPSBT function that does all that for us. Backport of Core [[bitcoin/bitcoin#18115 | PR18115]] part [6/9] : bitcoin/bitcoin@82a30fa Depends on D8097 Test Plan: ninja all check-all Reviewers: #bitcoin_abc, majcosta Reviewed By: #bitcoin_abc, majcosta Differential Revision: https://reviews.bitcoinabc.org/D8101
…allet and ScriptPubKeyMan Summary: Instead of getting a SigningProvider and then going to MessageSign, have ScriptPubKeyMan handle the message signing internally. Backport of Core [[bitcoin/bitcoin#18115 | PR18115]] part [7/9] : bitcoin/bitcoin@6a9c429 Depends on D8101 and D8098 Test Plan: ninja all check-all Reviewers: #bitcoin_abc, majcosta Reviewed By: #bitcoin_abc, majcosta Differential Revision: https://reviews.bitcoinabc.org/D8102
Summary: Not all ScriptPubKeyMans will be able to provide private keys, but pubkeys and scripts should be. So only provide public-only SigningProviders, i.e. ones that can help with Solving. Backport of Core [[bitcoin/bitcoin#18115 | PR18115]] part [8/9] : bitcoin/bitcoin@dc17488 Depends on D8100 and D8102 Test Plan: ninja all check-all Reviewers: #bitcoin_abc, PiRK Reviewed By: PiRK Differential Revision: https://reviews.bitcoinabc.org/D8114
…gning instead of exporting the private keys d2774c0 Clear any input_errors for an input after it is signed (Andrew Chow) dc17488 Replace GetSigningProvider with GetSolvingProvider (Andrew Chow) 6a9c429 Move direct calls to MessageSign into new SignMessage functions in CWallet and ScriptPubKeyMan (Andrew Chow) 82a30fa Move key and script filling and signing from CWallet::FillPSBT to ScriptPubKeyMan::FillPSBT (Andrew Chow) 3d70dd9 Move FillPSBT to be a member of CWallet (Andrew Chow) a4af324 Use CWallet::SignTransaction in CreateTransaction and signrawtransactionwithwallet (Andrew Chow) f37de92 Implement CWallet::SignTransaction using ScriptPubKeyMan::SignTransaction (Andrew Chow) d999dd5 Add SignTransaction function to ScriptPubKeyMan and LegacyScriptPubKeyMan (Andrew Chow) 2c52b59 Refactor rawtransaction's SignTransaction into generic SignTransaction function (Andrew Chow) Pull request description: Following bitcoin#17261, the way to sign transactions, PSBTs, and messages was to use `GetSigningProvider()` and get a `SigningProvider` containing the private keys. However this may not be feasible for future `ScriptPubKeyMan`s, such as for hardware wallets. Instead of exporting a `SigningProvider` containing private keys, we need to pass these things into the `ScriptPubKeyMan` (via `CWallet`) so that they can do whatever is needed internally to sign them. This is largely a refactor as the logic of processing transactions, PSBTs, and messages for is moved into `LegacyScriptPubKeyMan` and `CWallet` instead of being handled by the caller (e.g. `signrawtransaction`). To help with this, I've refactored the 3(!) implementations of a `SignTransaction()` function into one generic one. This function will be called by `signrawtransactionwithkey` and `LegacyScriptPubKeyMan::SignTransaction()`. `CWallet::CreateTransaction()` is changed to call `CWallet::SignTransaction()` which in turn, calls `LegacyScriptPubKeyMan::SignTransaction()`. Other `ScriptPubKeyMan`s may implement `SignTransaction()` differently. `FillPSBT()` is moved to be a member function of `CWallet` and the `psbtwallet.cpp/h` files removed. It is further split so that `CWallet` handles filling the UTXOs while the `ScriptPubKeyMan` handles adding keys, derivation paths, scripts, and signatures. In the end `LegacyScriptPubKeyMan::FillPSBT` still calls `SignPSBTInput`, but the `SigningProvider` is internal to `LegacyScriptPubKeyMan`. Other `ScriptPubKeyMan`s may do something different. A new `SignMessage()` function is added to both `CWallet` and `ScriptPubKeyMan`. Instead of having the caller (i.e. `signmessage` or the sign message dialog) get the private key, hash the message, and sign, `ScriptPubKeyMan` will now handle that (`CWallet` passes through to the `ScriptPubKeyMan`s as it does for many functions). This signing code is thus consolidated into `LegacyScriptPubKeyMan::SignMessage()`, though other `ScriptPubKeyMan`s may implement it differently. Additionally, a `SigningError` enum is introduced for the different errors that we expect to see from `SignMessage()`. Lastly, `GetSigningProvider()` is renamed to `GetPublicSigningProvider()`. It will now only provide pubkeys, key origins, and scripts. `LegacySigningProvider` has it's `GetKey` and `HaveKey` functions changed to only return false. Future implementations should return `HidingSigningProvider`s where private keys are hidden. Other things like `dumpprivkey` and `dumpwallet` are not changed because they directly need and access the `LegacyScriptPubKeyMan` so are not relevant to future changes. ACKs for top commit: instagibbs: reACK bitcoin@d2774c0 Sjors: re-utACK d2774c0 meshcollider: re-utACK d2774c0 Tree-SHA512: 89c83e7e7e9315e283fae145a2264648a9d7f7ace8f3281cb3f44f0b013c988d67ba4fa9726e50c643c0ed921bdd269adaec984840d11acf4a681f3e8a582cc1
Following #17261, the way to sign transactions, PSBTs, and messages was to use
GetSigningProvider()
and get aSigningProvider
containing the private keys. However this may not be feasible for futureScriptPubKeyMan
s, such as for hardware wallets. Instead of exporting aSigningProvider
containing private keys, we need to pass these things into theScriptPubKeyMan
(viaCWallet
) so that they can do whatever is needed internally to sign them. This is largely a refactor as the logic of processing transactions, PSBTs, and messages for is moved intoLegacyScriptPubKeyMan
andCWallet
instead of being handled by the caller (e.g.signrawtransaction
).To help with this, I've refactored the 3(!) implementations of a
SignTransaction()
function into one generic one. This function will be called bysignrawtransactionwithkey
andLegacyScriptPubKeyMan::SignTransaction()
.CWallet::CreateTransaction()
is changed to callCWallet::SignTransaction()
which in turn, callsLegacyScriptPubKeyMan::SignTransaction()
. OtherScriptPubKeyMan
s may implementSignTransaction()
differently.FillPSBT()
is moved to be a member function ofCWallet
and thepsbtwallet.cpp/h
files removed. It is further split so thatCWallet
handles filling the UTXOs while theScriptPubKeyMan
handles adding keys, derivation paths, scripts, and signatures. In the endLegacyScriptPubKeyMan::FillPSBT
still callsSignPSBTInput
, but theSigningProvider
is internal toLegacyScriptPubKeyMan
. OtherScriptPubKeyMan
s may do something different.A new
SignMessage()
function is added to bothCWallet
andScriptPubKeyMan
. Instead of having the caller (i.e.signmessage
or the sign message dialog) get the private key, hash the message, and sign,ScriptPubKeyMan
will now handle that (CWallet
passes through to theScriptPubKeyMan
s as it does for many functions). This signing code is thus consolidated intoLegacyScriptPubKeyMan::SignMessage()
, though otherScriptPubKeyMan
s may implement it differently. Additionally, aSigningError
enum is introduced for the different errors that we expect to see fromSignMessage()
.Lastly,
GetSigningProvider()
is renamed toGetPublicSigningProvider()
. It will now only provide pubkeys, key origins, and scripts.LegacySigningProvider
has it'sGetKey
andHaveKey
functions changed to only return false. Future implementations should returnHidingSigningProvider
s where private keys are hidden.Other things like
dumpprivkey
anddumpwallet
are not changed because they directly need and access theLegacyScriptPubKeyMan
so are not relevant to future changes.