-
Notifications
You must be signed in to change notification settings - Fork 37.7k
Silent Payments: Receiving #32966
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
base: master
Are you sure you want to change the base?
Silent Payments: Receiving #32966
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code Coverage & BenchmarksFor details see: https://corecheck.dev/bitcoin/bitcoin/pulls/32966. ReviewsSee the guideline for information on the review process. 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. |
🚧 At least one of the CI tasks failed. HintsTry to run the tests locally, according to the documentation. However, a CI failure may still
Leave a comment here, if you need help tracking down a confusing failure. |
@@ -358,6 +358,7 @@ static RPCHelpMan createwallet() | |||
{"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "If set, must be \"true\""}, | |||
{"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."}, | |||
{"external_signer", RPCArg::Type::BOOL, RPCArg::Default{false}, "Use an external signer such as a hardware wallet. Requires -signer to be configured. Wallet creation will fail if keys cannot be fetched. Requires disable_private_keys and descriptors set to true."}, | |||
{"silent_payments", RPCArg::Type::BOOL, RPCArg::Default{false}, "Enable creating and receiving with a silent payments address. Will disable fast rescans with block filters"}, |
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 89831da wallet/rpc: add create silent-payments wallet option: while testing I found that it doesn't disable block filters.
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.
Thanks @Sjors. I'll fix it
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.
We can disable the fast rescan for now just to keep this PR focused, but in theory we should be able to use the fast rescan regardless of whether or not the wallet is a silent payments wallet. Perhaps we can leave a comment to enable fast rescans in a follow up?
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.
IIUC The silent payments wallet needs to scan entire blocks since it doesn't know ahead of time which scriptPubKeys
to use in a GCS filter. Since we can't skip blocks, fast rescans shouldn't provide any advantages here.
EDIT
It might also be possible to use fast rescan when importing descriptors if none of the descriptors are silent payment descriptors. Is this what you were referring to @josibake?
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 silent payments wallet needs to scan entire blocks since it doesn't know ahead of time which scriptPubKeys to use in a GCS filter.
This makes sense. Is it documented anywhere so we don't forget by the next review round? :-)
It might also be possible to use fast rescan when importing descriptors if none of the descriptors are silent payment descriptors.
It wasn't referring to that and I wouldn't worry about this. Somewhere in the RPC documentation (createwallet and/or importdescriptors) we should warn that silent payments disables fast rescan. But -blockfilterindex
isn't on by default and so it's probably only advanced users that will run into this.
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 makes sense. Is it documented anywhere so we don't forget by the next review round? :-)
I added a separate commit that disables fast rescan and I added an explanation to the commit message. Will push soon.
EDIT
see 82db7cc
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 silent payments wallet needs to scan entire blocks since it doesn't know ahead of time which scriptPubKeys to use in a GCS filter
This is correct. I was imagining a world where we can pre-compute the scriptPubKeys, but realised we have to iterate the blocks anyways to get the tweaks so its likely faster to just check directly. In the future, if the wallet has some sort of silent payments index, then I think fast rescans would be possible (and beneficial).
Another thing I noticed while testing using the
It seems safer, for backups, to send change to the same silent payment address. Wallet created by: bitcoin rpc createwallet Silent blank=true silent_payments=true
bitcoin rpc importdescriptors '[{"desc": "sp(xprv...,xprv...)", "timestamp": "now", "active": true] Also looking forward to have labels back :-) |
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.
Did a big overview with @Sjors , leaving the notes from our discussion as a review. In general, I think we should investigate using a custom type for the scan key since a lot of the changes seem to be hacking around the fact we represent the scan key as a private key, but then often need to use it as not a private key.
Still in the process of reviewing, but leaving the initial notes for now
src/script/descriptor.cpp
Outdated
@@ -206,6 +206,8 @@ struct PubkeyProvider | |||
/** Get the descriptor string form including private data (if available in arg). */ | |||
virtual bool ToPrivateString(const SigningProvider& arg, std::string& out) const = 0; | |||
|
|||
virtual std::optional<std::string> ToPrivateString(const CKey& key) const = 0; |
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.
While reviewing the PR, it seems a lot of things could be simplified by if we just treated the scan key as something that isn't a CKey
, i.e., we introduce a completely new object for holding the scan key. I think that would allow us to drop this commit and simplify other places where we are trying to work around the fact that a scan key isnt really a private key.
I haven't thought this all the way through yet, but in my initial pass on the PR it seems like it should be possible.
src/script/descriptor.cpp
Outdated
@@ -776,7 +776,7 @@ class DescriptorImpl : public Descriptor | |||
} | |||
|
|||
// NOLINTNEXTLINE(misc-no-recursion) | |||
void ExpandPrivate(int pos, const SigningProvider& provider, FlatSigningProvider& out) const final | |||
void ExpandPrivate(int pos, const SigningProvider& provider, FlatSigningProvider& out) const override |
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.
Potentially another area we could simplify if we end up not treating a scan key as a CKey
.
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.
Confirmed, considering we don't need ExpandPrivate anymore, this can remain const final
.
src/script/descriptor.cpp
Outdated
out.sp_keys = std::make_pair(m_scan_key, *spend_pubkey); | ||
|
||
auto it{tmp.keys.find(spend_pubkey->GetID())}; | ||
if (it != tmp.keys.end()) out.keys.emplace(spend_pubkey->GetID(), it->second); |
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.
AFAICT, we only use the sp_keys
member of the FlatSigningProvider as a way of passing around the scan key and the spend pubkey, and then also for the spend private key (for signing). It makes sense to me that we would later add the spend private key + tweaks to the FlatSigningProvider, but ideally thats the only thing we use it for and can get rid of sp_keys
.
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.
Without sp_keys
, the wallet can't identify which keys are scan and spend keys from the signing provider. Do you have an alternative approach in mind?
src/wallet/scriptpubkeyman.cpp
Outdated
return CTxDestination{op_dest}; | ||
} | ||
|
||
bool SilentPaymentDescriptorScriptPubKeyMan::TopUp(const uint256& tweak) |
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 name TopUp here makes sense in that we are trying to match how the rest of the wallet does it, but @Sjors pointed out this can be confusing for people who think of TopUp in terms of the gap limit / key pool refresh context, which we aren't really doing here. We could just have one function called AddTweak
and then use a boolean for writing to the database, e.g., AddTweak(uint256& tweak, store_in_db=false)
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.
Updated
src/wallet/scriptpubkeyman.cpp
Outdated
return res; | ||
} | ||
|
||
bool SilentPaymentDescriptorScriptPubKeyMan::TopUpWithDB(WalletBatch& batch, const uint256& tweak) |
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.
Could be AddTweakWithDB
(based on my previous comment).
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.
Worth mentioning, the only reason we wouldn't be able to do this is if this function (and TopUp
) are called generically on SPKMans, e.g., in a loop. I don't think this is the case, but wanted to mention it.
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.
Updated
src/wallet/scriptpubkeyman.cpp
Outdated
FlatSigningProvider SilentPaymentDescriptorScriptPubKeyMan::GetSPProvider() const | ||
{ | ||
AssertLockHeld(cs_desc_man); | ||
FlatSigningProvider provider; | ||
provider.keys = GetKeys(); | ||
FlatSigningProvider tmp; | ||
m_wallet_descriptor.descriptor->ExpandPrivate(0, provider, tmp); | ||
return tmp; | ||
} |
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.
Can likely be removed if we have custom methods for storing scan/spend pubkey data, or custom methods for getting them.
src/wallet/wallet.cpp
Outdated
// Scan block | ||
bool wallet_updated = false; | ||
for (size_t index = 0; index < block.data->vtx.size(); index++) { | ||
CTransactionRef tx{block.data->vtx.at(index)}; |
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.
Do we need the CTransactionRef
here, as opposed to just operating on block.data->vtx
directly?
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.
We only use the reference once, so it's not needed. I'll take this out as I retouch.
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.
I ended up leaving the reference and used it in 2 other locations. The code looks slightly cleaner this way.
4b49704
to
f86b9d2
Compare
@Eunovo Nice work on addressing the issues identified in previous tests. All of those are working as expected now. I am including a patch that adds 2 more failing tests when adding silent-payments address in the transaction output: |
I think these might be more appropriate for the sending PR #28201 cc @josibake |
While the scankey looks like a private key, and is represented as a `CKey` in the code, it is actually much closer to a public key and it needs to be treated as a public key, so it can be added to watch-only wallets.
sp() expects a scan private key and a spend key which could be a private key or a public key. The scan key is really a "scan entropy" and will not be treated as an actual private key.
Create an SPDescriptor interface that will be used by the `SilentPaymentDescriptorScriptPubKeyMan`. The interface only needs to provide a way to get the scan key, the spend key can be obtained through the normal Descriptor methods. While the scan key is represented as a `CKey`, it will not be used in the signing process. This creates an argument against putting this in a signing provider.
This method makes it easier to tweak silent payment spend keys in later commits
The spent_coins data will be used to scan for silent payments in later commits Co-authored-by: Ava Chow <github@achow101.com>
This undo data will be used to scan for silent payments in later commits Co-authored-by: Ava Chow <github@achow101.com>
The scan key will be derived while the descriptor is parsed, so we must pass the master xpriv into `GenerateWalletDescriptor`. The spend key is expanded later so it is specified as an xpub and path instead.
Co-authored-by: Ava Chow <github@achow101.com>
Co-authored-by: Ava Chow <github@achow101.com>
Co-authored-by: Ava Chow <github@achow101.com>
silent_payments wallet needs to scan entire blocks since it doesn't know ahead of time which scriptPubKeys to use in it's fast rescan filter.
Co-authored-by: Ava Chow <github@achow101.com>
traditionally, the receiving scripts are known to the core wallet because they are added to the addressbook at the time they are requested. in silent payments, the outputs are not known until found. its important, however, to have the found scripts in the addressbook so all of the change accounting can be down properly. this commits adds found outputs to the address book if they are not change. the way change is determined is a bit hacky in that we just check if the found output was found with a label (since this is the only label currently implemented). in the future, we should specifically check for a change specific label.
The wallet tries to match change OutputTpe to other outputs to preserve privacy. Use silent payment change address when sending coins to: - any silent payment address - any taproot address
`CreateTransaction` fails if change_type is set to OutputType::SILENT_PAYMENTS and there are no eligible inputs for the transaction. This commit modifies `CreateTransaction` logic to make a second attempt to create a tx without OutputType::SILENT_PAYMENTS change_type
This allows wallets with only sp descs to use sp change destination for transactions.
Co-authored-by: macgyver13 <4712150+macgyver13@users.noreply.github.com>
f86b9d2
to
7150948
Compare
Some Major Updates:
|
This PR is part of integrating silent payments into Bitcoin Core. Status and tracking for the project is managed in #28536
This PR is based on #28201 and will remain in draft until #28201 is merged.
This PR:
SilentPaymentsDescriptorScriptPubKeyMan
Impl that is a subclass ofDescriptorScriptPubKeyMan
silent-payments
destination for change when sending tosilent-payments
destinationFollow-ups