Skip to content

Conversation

@pinheadmz
Copy link
Member

@pinheadmz pinheadmz commented Mar 6, 2023

This is a first step towards closing #3314 by implementing functions in Wallet and ScriptPubKeyMan that determine if a key is "active" meaning "derived from a currently active scriptpubkey manager."

A key is NOT "active" if:

  • It was imported using an import{privkey, address, pubkey} rpc
  • It was derived before the wallet was encrypted (which replaces the active SPKM(s))

Users should be informed that these keys may be compromised:

  • Some other wallet generated the private key and may still have a copy
  • The private key was stored on disk unencrypted

This PR adds a new field isactive to rpc getaddressinfo, follow-up work will be adding this to the GUI address book as well, something like this. The original issue also recommends sweeping all value from keys in this category, I think that feature can be discussed in the future as a follow-up.

This PR also patches PKDescriptor from #22051 where matching public keys were not returned.

Follow-up PR for the GUI: bitcoin-core/gui#723

@DrahtBot
Copy link
Contributor

DrahtBot commented Mar 6, 2023

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage

For detailed information about the code coverage, see the test coverage report.

Reviews

See the guideline for information on the review process.

Type Reviewers
Concept ACK jarolrod, pablomartin4btc
Stale ACK jonatack

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #28528 (test: Use test framework utils in functional tests by osagie98)
  • #28454 (wallet: allowing walletpassphrase timeout to be MAX_SLEEP_TIME if set to -1 by kevkevinpal)
  • #28403 (test: Bump walletpassphrase timeouts to avoid intermittent issues by MarcoFalke)
  • #28333 (wallet: Construct ScriptPubKeyMans with all data rather than loaded progressively by achow101)
  • #24142 (Deprecate SubtractFeeFromOutputs by achow101)
  • #20892 (tests: Run both descriptor and legacy tests within a single test invocation by achow101)

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.

@fanquake
Copy link
Member

fanquake commented Mar 7, 2023

https://github.com/bitcoin/bitcoin/pull/27216/checks?check_run_id=11804958852

Assertion failed: lock cs_wallet not held in ./wallet/wallet.h:444; locks held:
'cs_KeyStore' in wallet/scriptpubkeyman.cpp:1185 (in thread 'test')

@pinheadmz pinheadmz force-pushed the used-addr-ui branch 4 times, most recently from 87da692 to 1e4b0e8 Compare March 9, 2023 21:07
@pinheadmz pinheadmz force-pushed the used-addr-ui branch 2 times, most recently from c1bca08 to 4fec5fd Compare March 13, 2023 14:25
@pinheadmz pinheadmz changed the title Add wallet method to detect if a key is "fresh" Add wallet method to detect if a key is "active" Mar 13, 2023
@pinheadmz pinheadmz marked this pull request as ready for review March 13, 2023 18:27
@pinheadmz
Copy link
Member Author

rebased on master and addressed some nits from follow-up PR bitcoin-core/gui#723

Copy link
Member

@jonatack jonatack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK, tested and reviewed each commit. Some thoughts and questions follow.

This PR also patches PKDescriptor from #22051 where matching public keys were not returned.

I didn't see where/how; can you help or explain?

@pinheadmz
Copy link
Member Author

@jonatack thanks so much for the great review, addressed comments (1 or 2 questions along the way) rebase on master and also rebased the GUI follow up

@pinheadmz
Copy link
Member Author

rebase to: 58fba35e25c2b9dcdd5b3dd0aa8f412801a98146

  • added nodiscard
  • cleaned up help message in getaddressinfo
  • added release note file

Copy link
Member

@jonatack jonatack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK with some suggestions below.

The release note and added test coverage could optionally all be in separate commits, provided each commit is hygienic, i.e. builds and all tests pass. It's a bit easier for reviewers if updates needed for tests to pass with a change are in the same commit, while additional coverage is in separate commits.

pinheadmz added a commit to pinheadmz/bitcoin that referenced this pull request Apr 14, 2023
@pinheadmz
Copy link
Member Author

thanks again @jonatack
rebase to 8139075

  • refactor commits to separate tests in hygenic order!
  • address review nits

Copy link
Member

@jonatack jonatack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 8139075 modulo eyes from someone more familiar with the wallet to validate the approach.

Commit 2a5774f should probably be renamed to something like test: cover ScriptPubKeyMan::IsKeyActive and IsDestinationActive

Re-reviewing this shuffled version was a little trickier due to the unneeded rebase. Please rebase only when you have to. Aside from that, the reorganized commits seem easier to review now.

virtual std::vector<WalletDestination> MarkUnusedAddresses(const CScript& script) { return {}; }

/* Determines if address is derived from active key manager */
[[nodiscard]] virtual bool IsKeyActive(const CScript& script) const { return false; };
Copy link
Member

@jonatack jonatack Apr 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fb3d39a It may make sense to declare this base class member as a pure virtual function. The implementation here seems unused/unnecessary.

Suggested change
[[nodiscard]] virtual bool IsKeyActive(const CScript& script) const { return false; };
virtual bool IsKeyActive(const CScript& script) const = 0;

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah thanks for the ack, I'll update this and the commit message you mentioned if I need to revise again.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, both comments addressed with rebase

@jonatack
Copy link
Member

81f29f0 I'm not sure if or when legacy wallet support is expected to be phased out, but if isactive is only different from ismine in legacy wallets, is it worth adding a field (and all the new methods), versus just updating the ismine field documentation in the help for descriptor wallets

Following up on #27216 (comment) above, according to #20160 legacy wallet support may be dropped in the next couple releases.

Copy link
Contributor

@S3RK S3RK left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, we can't use "activeness" of a descriptor to determine if it was generated before encryption.

Descriptor wallets allow as many descriptors as you want, but only one descriptor per OutputType could be active at a time. The activeness only specifies which descriptor to use when generating new addresses for that type. Therefore, there are more scenarios, besides the two you've listed, in which a key will be NOT "active". The simplest one would be — I just imported two p2wpkh descriptors for two BIP-44 accounts that I have. And only one of these descriptors could be active at a time.

In general, I believe that the proper solution to "exposed" keys is to get a new wallet with new set of keys, rather than rotating keys in existing wallet. We don't have a good separation between UTXOs/addresses within a wallet. On the other hand we already support multiple wallets, which are completely isolated so there are no chances of leaking/mixing UTXOs and addresses.

@pinheadmz
Copy link
Member Author

I just imported two p2wpkh descriptors for two BIP-44 accounts that I have. And only one of these descriptors could be active at a time.

You would have to set "active":"true" when executing RPC importdescriptors and the default is false. But I see your point, considering this feature is for safety its not all that difficult for an advanced user to bypass that check.

Even if you did do this (import with active:true), I still think it is useful for getaddrinfo to notify you if an address is derived from an inactive descriptor. The issue I am targeting with this PR is more about protecting less advanced users (particularly with the GUI) from giving out "risky" receive addresses.

@S3RK
Copy link
Contributor

S3RK commented Sep 26, 2023

I have two concerns with the approach:

  • I don't like changing semantic of "active" field to mean it's somehow unsafe.
  • Even for less advances users, I don't think it fully addresses the describe use-case, because it doesn't allow easy sweeping of compromised keys.

1/ The activeness indicates from which descriptor to derive new addresses. Based on the semantics of that field, It is incorrect to imply that addresses from inactive descriptors are less safe that addresses from active descriptors. Moreover, activeness of the descriptor is a user configurable parameter and we can't just reuse it to store information about "safety" of the descriptor.

2/ IIUC here's the use cases this PR tries to address: a user believes existing private keys are compromised and rotates the keys in the wallet. Since the keys are compromised it's unsafe to receive new and keep old funds. So after rotating the keys the user would have to sweep all the compromised keys. If I'm not mistaken, we currently don't support sweeping coins from a descriptor. However, we support the whole wallet sweeping with sendall command.

Separate wallet solution is also less error prone because it doesn't rely on a user to pay attention to the addresses to maintain safety. This approach (with optional sweeping) also works for privacy leaks, because it also prevents mixing UTXO histories.

@DrahtBot
Copy link
Contributor

DrahtBot commented Oct 5, 2023

🐙 This pull request conflicts with the target branch and needs rebase.

Copy link
Member

@pablomartin4btc pablomartin4btc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Concept ACK
Interesting points of view from both @pinheadmz and @S3RK, regarding "activeness" concept for descriptor wallets, the use case scenario this PR tries to solve and safety related with rotating keys vs wallet sweeping.
I'll review this PR further.

@pinheadmz
Copy link
Member Author

I'm closing this to rethink the approach. Since 'active' descriptors can be imported it defeats the value of that field for this purpose. Really it seems the only property the wallet can really know for sure is if it generated keys into an encrypted wallet. I'll be back with a UI indicator for keys that we know have always been "safe".

@pinheadmz pinheadmz closed this Oct 12, 2023
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Mar 5, 2024
This new method returns true if the given CScript key is derived
from the SPKM. For Legacy that means checking the hd_seed_id in the
key's metadata.

Also patches PKDescriptor to return associated public keys in
MakeScripts()

Github-Pull: bitcoin#27216
Rebased-From: f39957f
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Mar 5, 2024
This connects SPKM.IsKeyActive() up to the wallet level.

Github-Pull: bitcoin#27216
Rebased-From: d33f3bd
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Mar 5, 2024
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Mar 5, 2024
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Mar 13, 2024
This new method returns true if the given CScript key is derived
from the SPKM. For Legacy that means checking the hd_seed_id in the
key's metadata.

Also patches PKDescriptor to return associated public keys in
MakeScripts()

Github-Pull: bitcoin#27216
Rebased-From: f39957f
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Mar 13, 2024
This connects SPKM.IsKeyActive() up to the wallet level.

Github-Pull: bitcoin#27216
Rebased-From: d33f3bd
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Mar 13, 2024
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Mar 13, 2024
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Jun 13, 2024
This new method returns true if the given CScript key is derived
from the SPKM. For Legacy that means checking the hd_seed_id in the
key's metadata.

Also patches PKDescriptor to return associated public keys in
MakeScripts()

Github-Pull: bitcoin#27216
Rebased-From: f39957f
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Jun 13, 2024
This connects SPKM.IsKeyActive() up to the wallet level.

Github-Pull: bitcoin#27216
Rebased-From: d33f3bd
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Jun 13, 2024
luke-jr pushed a commit to bitcoinknots/bitcoin that referenced this pull request Jun 13, 2024
@bitcoin bitcoin locked and limited conversation to collaborators Oct 11, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants