17:00:24 <ryanofsky> #startmeeting 17:00:24 <corebot> ryanofsky: Meeting started at 2025-08-06T17:00+0000 17:00:25 <corebot> ryanofsky: Current chairs: ryanofsky 17:00:26 <corebot> ryanofsky: Useful commands: #action #info #idea #link #topic #motion #vote #close #endmeeting 17:00:27 <corebot> ryanofsky: See also: https://hcoop-meetbot.readthedocs.io/en/stable/ 17:00:28 <corebot> ryanofsky: Participants should now identify themselves with '#here' or with an alias like '#here FirstLast' 17:00:35 <ryanofsky> hi 17:00:37 <ryanofsky> Welcome to the bitcoin review club! Today we'll discuss https://bitcoincore.reviews/32489 #32489 17:00:39 <corebot> https://github.com/bitcoin/bitcoin/issues/32489 | wallet: Add `exportwatchonlywallet` RPC to export a watchonly version of a wallet by achow101 · Pull Request #32489 · bitcoin/bitcoin · GitHub 17:00:40 <glozow> hi 17:00:42 <stringintech> Hi 17:00:42 <stickies-v> hi 17:01:06 <pablomartin4btc> hello 17:01:11 <colinc> hi 17:01:17 <kevkevin> hi 17:01:46 <ryanofsky> First question to get started 1. Did you test, concept ACK, approach ACK, or NACK the PR? What aspects did you focus on during review? 17:01:58 <emzy> hi 17:02:26 <monlovesmango> heyy 17:02:41 <stringintech> I was not familiar with this part of the codebase so mainly spent time on getting a basic understanding of the components touched by this PR and then did a light review on the changes. Concept ACK. 17:02:58 <monlovesmango> concept ACK but mostly here just to lurk and learn 17:02:59 <pablomartin4btc> light code review ACK 17:03:28 <effexzi> Hi every1 17:03:29 <ryanofsky> Yeah I feel like the harder part of this PR if you aren't familiar with wallet is probably the concepts 17:03:42 <monlovesmango> yes definitely haha 17:04:04 <ryanofsky> The code is more straightfoward than other prs, because it's new and not interacting with legacy stuff like a lot of wallet prs 17:04:51 <ryanofsky> Feel free to ask if a particular concept is unclear 17:05:08 <ryanofsky> Next up is 2. Why can’t the existing IsRange()/IsSingleType() information tell us whether a descriptor can be expanded on the watch-only side? Explain the logic behind CanSelfExpand() for a) a hardened wpkh(xpub/0h/*) path and b) a pkh(pubkey) descriptor. 17:06:49 <monlovesmango> is it possible that a range can be hardened but also could not be hardened? 17:06:52 <stringintech> About the first part of the question, CanSelfExpand() tells whether we need private keys or cache to get new addresses for a descriptor. A single type range descriptor may contain hardened paths or may not. The latter can self expand but not the former. So only those two methods are not enough. 17:07:34 <stringintech> About the second part 17:07:52 <stickies-v> I think range descriptors with hardened paths can still self expand, but only if they contain the xpriv? 17:08:20 <stringintech> pkh(pubkey) cannot expand because it is not ranged but the other one (which shows the limitation of IsRange()/IsSingleType() checks) cannot expand because it contains hardened paths. 17:08:59 <ryanofsky> stringintech, yeah it's just in the exported wallet there are no private keys, so anything hardened needs to be exported in the cache 17:09:08 <ryanofsky> oops that was in reply to stickies-v 17:09:49 <stringintech> :D 17:09:54 <ryanofsky> stringintech, your answer sounds right 17:10:41 <stickies-v> oh yeah, watchonly exports wouldn't contain any private keys, i was making a more general comment 17:11:17 <ryanofsky> Basically in order to do the export the wallet needs to know if the descriptor uses any hardened paths, so CanSelfExport searched the descriptor recursively for anything hardened and returns true if it finds that 17:11:44 <ryanofsky> If there are any hardened paths caches need to be exported 17:12:23 <monlovesmango> I did have a code question, where is CanSelfExpand defined for OriginPubkeyProvider? since it is just returning m_provider->CanSelfExpand()? 17:13:50 <ryanofsky> I belive OriginPubkeyProvider just wraps another pubkey provider and adds origin information 17:13:59 <stickies-v> I don't really understand what the relevant of the `IsSingleType()` part to the question is, when is that relevant? 17:14:00 <monlovesmango> wait doesn't it return false if it finds hardened paths? 17:14:11 <ryanofsky> So since it doesn't need to derive anything it can call the wrapped pubkey provider 17:15:03 <ryanofsky> IsSingleType() is really not relevant. The question was just asking why existing methods like IsSingleType were not sufficient and a new CanSelfExpand method needed to be added 17:15:25 <stickies-v> okay, thx 17:15:55 <monlovesmango> so does pkh(pubkey) not use ConstPubkeyProvider? bc I had assumed it did but stringintech said pkh(pubkey) would not be able to expand 17:16:39 <ryanofsky> pkh(pubkey) should self-expand, right? 17:17:03 <ryanofsky> Anything that doesn't include a hardened path should self-expand 17:17:42 <monlovesmango> ryanofsky: that is what I thought 17:18:15 <ryanofsky> Well next question is about application of CanSelfExpand, so related 17:18:19 <ryanofsky> 3. ExportWatchOnlyWallet only copies the descriptor cache if !desc->CanSelfExpand(). What exactly is stored in that cache? How could an incomplete cache affect address derivation on the watch-only wallet? 17:19:13 <monlovesmango> ryanofsky: about OriginPubkeyProvider question, m_provider is cast as std::unique_ptr<PubkeyProvider> which only defines a virtual CanSelfExpand function. how can I see that it is wrapped? 17:20:18 <ryanofsky> Oh I might be using the word "wrapped" in a different way maybe? 17:20:35 <stringintech> monlovesmongo you are right i confused CanSelfExpand() with CanGetAddresses() earlier 17:20:57 <monlovesmango> I might also be asking the question in an unintuitive way haha 17:21:38 <ryanofsky> I just meant to say that OriginPubkeyProvider contains another PubkeyProvider. And since can just call CanSelfExpand on that provider since origin information in a descriptor doesn't affect what the descriptor expands to 17:22:04 <ryanofsky> Origin information is just additional metadata, doesn't affect how addresses are generated or what scriptPubKeys are matched 17:22:44 <monlovesmango> ok that makes sense! thank you! 17:23:00 <stickies-v> The cache stores all the parent xpubs (aren't those in the descriptor already?), a well a a number of derived xpubs (`m_derived_xpubs`), I think according to the size of the keypool? 17:23:11 <ryanofsky> Ok, I'm not 100% sure of this stuff myself so don't take me as authority, but that's my understanding 17:23:14 <stringintech> On question 3: This case we need to copy the cache because it contains the pregenerated addresses from a hardened path where copying them allows us to use those addresses while not having private keys to resolve the hardened paths. 17:23:48 <stickies-v> so an incomplete cache would be similar to someone sending a transaction to a derived address that exceeds the gap limit, I think? 17:24:02 <ryanofsky> +1 stickies-v and stringintech yes that all sounds right 17:24:21 <pablomartin4btc> stringintech +1 (derivation info like last index used, etc) 17:24:22 <ryanofsky> The cache stores pre-derived pubkeys for hardened paths. If it isn’t copied, the watch-only wallet will not be able to see transactions sent to resulting addresses 17:25:45 <ryanofsky> Feel free to ask more about that if not clear, next question is on another topic 17:25:53 <ryanofsky> 4. The exporter sets create_flags = GetWalletFlags() | WALLET_FLAG_DISABLE_PRIVATE_KEYS. Why is it important to preserve the original flags (e.g. AVOID_REUSE) instead of clearing everything and starting fresh? 17:26:23 <stringintech> How can the number of pre-derived pubkeys be configured? (before exporting the watchonly wallet) Is it configurable? Cause eventually we will run out of them I guess. 17:27:02 <monlovesmango> stringintech: great question! 17:27:15 <pablomartin4btc> 4. i think keeping user intent and expected behavior across exported/restored environments 17:27:51 <ryanofsky> yes good question i wonder if the keypool size options affect it 17:28:21 <stickies-v> continuing on the cache: does that mean user should consider calling `keypoolrefill` rpc before exporting the watchonly wallet? 17:28:25 <ryanofsky> or maybe you just topup the keypool before exporting the wallet 17:28:52 <stickies-v> oh we were all thinking the same hah 17:29:12 <ryanofsky> Yes could look into it, good question 17:29:16 <stringintech> Cool :))) 17:29:43 <ryanofsky> Yes for question 4 I had: I think AVOID_REUSE flag is actually not relevant. IIUC point of that flag is to avoid spending from coins previously spent from to improve privacy, and since watch-only wallets can't spend it's not relevant. But not sure about this. 17:29:57 <ryanofsky> In general seems good to preserve flags though 17:30:29 <monlovesmango> yes, any you never know if future flags might have differences in behavior 17:30:43 <ryanofsky> Yep 17:30:44 <glozow> I think AvailableCoins's result depends on AVOID_REUSE, so there is a difference? 17:30:57 <glozow> even though you can't spend those coins 17:31:23 <monlovesmango> true 17:31:55 <ryanofsky> Oh so balance would show up differently, that makes sense if I'm understanding right 17:31:58 <glozow> the user would be confused why the balance shown isn't the same 17:32:01 <glozow> yeah I think so 17:32:01 <monlovesmango> like if you just need to provide an address AVOID_REUSE would affect which address could be returned? 17:32:14 <monlovesmango> oh ok 17:32:54 <monlovesmango> wait AVOID_REUSE would make the wallet not identify balances just bc address is reused? 17:33:21 <glozow> https://github.com/bitcoin/bitcoin/blob/cf15d45192e03ff2b0267842353f5f89541cb3f1/src/wallet/spend.cpp#L420 17:33:35 <ryanofsky> monlovesmango, I'm guessing it would still be in overall balance but not available balance 17:34:33 <ryanofsky> 5. Why does the exporter read the locator from the source wallet and write it verbatim into the new wallet instead of letting the new wallet start from block 0? 17:34:50 <monlovesmango> glozow: thanks :) ryanofsky: gotcha 17:36:21 <monlovesmango> I assume to not have to rescan the wallet? 17:36:26 <pablomartin4btc> monlovesmango: check avoid_reuse description in getbalance RPC 17:36:32 <stringintech> I think it is because we want to know where to resume scanning the blocks for txs we are interested in (and since we have already copied the old txs data, the wallet doesn't need to rescan from genesis). Though I am not confident yet on how locators work in general. 17:37:05 <monlovesmango> pablomartin4btc: will do! 17:37:52 <ryanofsky> feel free to ask about locators! But yeah that all sounds right, it's just to avoid rescanning. It would almost defeat purpose of the RPC if needed to rescan after exporting 17:38:13 <ryanofsky> 6. Consider a multisig descriptor wsh(multi(2,xpub1,xpub2)). If one cosigner exports a watch-only wallet and shares it with a third party, what new information does that third party learn compared to just giving them the descriptor strings? 17:38:17 <pablomartin4btc> monlovesmango +1 plus avoid missing transactions that happened after wallet creation but before export 17:39:31 <stringintech> About locators, what other parts of the codebase use them other than wallet? 17:40:20 <monlovesmango> pablomartin4btc: watch only wallet wouldn't find these? interesting 17:41:03 <ryanofsky> I think just the wallets and indexes use locators 17:41:13 <monlovesmango> is it all scenarios or are there only certain scenarios where watch only wallet wouldn't find these? 17:41:40 <stringintech> ryanofsky Thanks! 17:43:31 <glozow> are these the same locators we use for syncing headers with our peers? 17:45:04 <ryanofsky> Could be? Maybe they are. I'm very ignorant of p2p 17:45:21 <glozow> Seems they could be! `CBlockLocator` 17:45:39 <furszy> yes, they are. 17:45:45 <stringintech> Nice! 17:46:38 <ryanofsky> For answer 6 I had: If the third-party was given the watch-only wallet they would transaction history and wallet metadata along with the descriptor. If they only had the descriptor they might not be able to get full transaction history if uses hardened paths. 17:47:46 <stringintech> And also the addresses copied through the cache I guess (related to hardened paths)? 17:48:16 <monlovesmango> ryanofsky: oh that makes sense 17:48:54 <ryanofsky> stringintech, yes I think that's right 17:50:04 <ryanofsky> IIUC DescriptorCache just contains CExtPubKey objects so not addresses directly, but the thing you would use to derive them and also to match transactions 17:50:25 <monlovesmango> so just to test my understanding, would watch only descriptor of hardened path kinda be useless? or what information could you discern from the descriptor alone? 17:51:19 <stringintech> Oh right, thanks ryanofsky 17:52:46 <ryanofsky> monlovesmango, yes I can't think of any uses if the wallet only had a hardened descriptor and not private key and no cache 17:53:03 <monlovesmango> ryanofsky: cool thanks 17:54:04 <ryanofsky> Since not much time left will just post the last two questions, feel free to respond to either 17:54:08 <ryanofsky> 7. In wallet_exported_watchonly.py, why does the test call wallet.keypoolrefill(100) before checking spendability across the online/offline pair? 17:54:12 <ryanofsky> 8. Prior to this PR, users could achieve a watch-only setup via dumpwallet → importwallet or importdescriptors. Compare those approaches with exportwatchonlywallet in terms of UX, completeness, and long-term maintainability. 17:55:01 <stringintech> On the last one, dumpwallet and importwallet currently do not exist in the codebase, right? 17:55:43 <ryanofsky> Ha yeah, that's a pretty good reason 17:55:47 <monlovesmango> hahaah 17:56:07 <stringintech> :D :D :D 17:56:31 <monlovesmango> for the last one, there is no way to avoid rescan right? 17:57:08 <ryanofsky> I wrote: dumpwallet saves private keys which we don't want to export. importdescriptors would drop metadata, require rescanning, not work with hardened descriptors 17:57:09 <pablomartin4btc> stringintech yeah so at the moment you cant achieve the watch-only setup... unles you run it from the latest release 17:57:23 <pablomartin4btc> but you can do the dump from the wallettool/ bitcoin-wallet bin 17:57:58 <monlovesmango> and if descriptor of hardened path without priv key isn't useful importdescriptors wouldn't really do much? 17:58:07 <ryanofsky> pablomartin4btc, I think the PR doesn't provide a way to do this from wallettool, but it could in the future 17:58:10 <stringintech> ryanofsky: Is a manual copy of the wallet database file be equivalent of dumpwallet? 17:58:36 <stringintech> pablomartin4btc: ah thanks!! 17:58:38 <pablomartin4btc> ryanofsky, to run the dump, not the export 17:58:39 <monlovesmango> ryanofsky: ok you beat me haa 17:59:33 <stringintech> On question 7 18:00:01 <stringintech> In that test the watch-only wallet generates a new address and prepares an incomplete tx and for the offline wallet to be able to sign that tx it has to call keypoolrefill so it can pre-compute addresses which includes that specific address which allows the offline wallet to sign the transaction (since the offline wallet has the corresponding 18:00:02 <stringintech> private key) 18:00:33 <ryanofsky> i think a manual copy of the file had some more information, dumpwallet was like a text representation, but similar 18:00:56 <stringintech> ryanofsky: great thank you 18:01:03 <ryanofsky> stringintech, I think that is correct for question 7 18:01:30 <ryanofsky> Thanks everybody for reviewing and participating! 18:01:48 <stringintech> Thank you and thanks everyone. 18:01:49 <glozow> thanks ryanofsky! 18:01:50 <ryanofsky> Will end the meeting in a minute if no remaining comments 18:02:08 <pablomartin4btc> thank you ryanofsky! thanks for the useful notes! 18:02:09 <monlovesmango> thanks for hosting ryanofsky !! went fast and learned a lot 18:02:26 <ryanofsky> I learned a lot too, this was fun! 18:02:56 <ryanofsky> #endmeeting