-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
Automated BIP39 Recovery (scan derivation paths, automatic, restore) #6219
Conversation
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.
Looks fine
Thanks for reviewing @SomberNight, I have two questions re GUI integration. 1. Whats the best way to hook into the derivation path view? I want to add a button in this view that says "Detect Existing Accounts" which when clicked, opens a popup with a loading icon, calls the account discovery function, displays a list of results to the user, allows them to select one, and populate the path/script type inputs with the result. For example the previous view to enter the seed has it's own file in electrum/electrum/base_wizard.py Lines 407 to 439 in d0ab003
I can see the QT implementation of electrum/electrum/gui/qt/installwizard.py Lines 605 to 633 in d0ab003
I could just edit that method and wrap my code with. What's the best way to hook into this? 2. How should I provide network access to my method? Currently in the script I'm creating a network instance with: from electrum.simple_config import SimpleConfig
from electrum.network import Network
config = SimpleConfig()
network = Network(config)
network.start() When I add this to the GUI I'll obviously want to use the existing network instance (if it exists during account creation?). I see What's the best way to go about this? Inside the functoin use the global instance if it exists or create one if it doesn't? Or maybe pass a Not quite sure the best way to go about this. |
Yes, that sounds good.
Yes, typically it exists, unless the user used the
|
@SomberNight thanks, makes sense! Hadn't realised And yes, I was only planning on implementing it in the QT GUI. CLI users can use the script, and I don't think the Kivy GUI allows setting the BIP39 option on a seed so not really applicable there. |
da21c58
to
e124f61
Compare
@SomberNight when calling an async function from a QT accounts = asyncio.run_coroutine_threadsafe(account_discovery(), network.asyncio_loop).result() Which works but it blocks the QT thread so the UI hangs and there's no way for the user click the cancel button while they're waiting. I'd like to:
Are you able to point me in the right direction for how to achieve this? Disclaimer: Apologies if this question seems obvious, I'm not really a Python developer, in fact the only other time I've ever written Python was for my SSL fingerprint PR. I'm not familiar with Python's async API works. |
No worries, it's not obvious at all. Qt does not really support python asyncio. Take a look at electrum/electrum/gui/qt/main_window.py Line 1658 in b0230f6
It does not behave exactly as you described but similar. |
The resulting file name could use another label (as the [standard] one), so the user knows which one he selected after the scan. |
I'd highly encourage using a gap limit that far exceeds the standard. Consider the fact that there are a variety of applications such as payment processing that can result in huge gaps. If I recall correctly, btcpayserver set their gap limit to either 1,000 or 10,000. In my opinion the additional computation required is well worth the pain it will save for folks who have funds stranded beyond the standard gap limit. |
For reference, yes, their gap limit appears to be 10,000. |
Do you mean for the discovery, or after the wallet is created for normal operations? If you mean for the discovery, then ok, perhaps. It seems a bit unlucky to have a wallet where none of the first few addresses are used but later ones are, but I guess it can happen if a merchant was griefed as soon as they started using a wallet. But to be clear, regardless of what insane gap limits some services are using, we are not able to use insane high gap limits simply due to architecture limitations. IMO what these services do when they use a gap limit of 10000 by default is anti-social behaviour: they are pulling the whole ecosystem in the same direction and slowly forcing everyone to use larger and larger gap limits (what if poor user used insane service no.17, we should use a higher gap limit then...). By using those high gap limits they introduce the risk for the user that they might not be able to restore easily using other services. That's just how it goes, we are not participating in this game. |
Some back-of-the-envelope calculations re gap limit: It currently takes around 2 seconds per account for discovery to complete with a gap limit of 20. That means in the best case scenario where there are 0 used accounts, we will only scan 13 paths so it'll take ~26 seconds. Setting the gap limit to 10,000 will mean the best case recovery process increases from ~26 seconds to ~3.6 hours. (assuming each account scan is exactly 500x slower since it's scanning 500x more addresses) |
Default ElectrumX DOS limits will get hit a lot sooner than that, slowing you down to oblivion. |
One solution could be to read the gap limit from the Electrum config if it exists instead of hardcoding to 20. Or maybe add a new Then if you connect to your own local Electrum instance, disable DoS limits, and manually set the config param to 10,000 it would work. But if you're capable of all this, you're probably also capable of just manually restoring your wallet. |
The audience for this "wizard" is newbies who don't know their derivation
path. The standard gap limit on import is enough. This is not a tool for
merchants or advanced users.
Let's keep it as simple and efficient as possible.
|
@rdymac that would indeed be nice but unfortunately that isn't really possible without making quite large changes to the way the Electrum import wizard works. Currently this PR is just adding a modal to one window and then auto-filling some fields in that window on completion. Keeping this change simple and not too intrusive into the way Electrum works was a specific goal for this PR: #6155 (comment) |
Yes, let's not change |
that's my opinion too. a high limit might work if you are scanning blocks yourself, but we should not engage in such behaviour with public servers. |
I've addresses the feedback and tested/fixed when setting up with a HWW wallet. Unless storing seed data on Let me know if you require any other changes or you think there's any other scenarios I should test. |
To expand on https://github.com/spesmilo/electrum/pull/6219/files#r444897453 , and then, in the GUI, instead of guarding with |
Sorry, I didn't get your point. It's not about the first funded address only. Maybe it's just not suitable for those wallets with huge gap-limit to be imported into Electrum. Allowing the user to manually specify child key index also enables mis-uses. |
I also wonder whether the "BIP39-Electrum2.0 duality" issue (#1300) is properly handled here? Electrum 2.0 seed phrase is not compatible with BIP39, but it still shares exactly the same wordlist with BIP39. Therefore, an Electrum 2.0 seed can be both a valid Electrum 2.0 seed and a valid BIP39 mnemonic, with a probability of one in sixteen. (12-word BIP39 mnemonic phrase has 4-bit checksum. 2^4=16) It's then possible (although not very possible, because the BIP39 wallet would show empty transaction history and zero balance, so that the user is highly unlikely to continue) for a user to import an Electrum 2.0 seed into another BIP39-compliant wallet. I think the seed generating step of the wallet wizard should also show some reminds/warnings about this issue. |
I dreamed that the new derivation path step of the wizard may look like this: Specify your derivation pathStandard derivation paths
Non-standard derivation paths
Not sure?
(empty, if no detection is ever executed) / No fund detected / Recoverable funds detected, congratulations! You may click "next" now. Don't touch things below if you have no idea what they mean. BIP32 derivation path: _________ Address type: Gap limit: ____ (auto-filled, depends on wallet type) |
related: #6001 |
@SomberNight @ecdsa are you able to re-review when you have time? I've resolved the issue of attaching the mnemonic/passphrase to the instance and instead now pass an I haven't implemented this function for when a HWW is being restored but I have tested the wizard during HWW restore and it correctly leaves out the recovery option. I don't currently have an Android development environment setup to test the Kivy GUI, however I had a quick look at the Kivy view and it just accesses all the keyword args with |
@SomberNight @ecdsa sorry to keep pinging you guys but not heard anything for ~1 month now so just wanted to check you haven't forgotten about this. AFAIK I've implemented all required changes and there aren't any conflicts, is there anything else you want from me for this or can we get it merged in? |
- use logger - allow qt dialog to be GC-ed - (trivial) add typing; minor formatting
cc7a6e8
to
7b122d2
Compare
Starting with 41827cf (copy here: https://github.com/SomberNight/electrum/tree/pr/6219_bak20200820), |
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.
Looks good. Reviewed code and tested.
I have some minor nits (in addition to what I pushed) but that's fine.
Noticed that the Qt dialog has a sizing bug on dark theme (too small by default) - might be platform dependent, and probably related to QDarkStyle, so ignoring for now (my naive attempts to fix it failed).
"description": description, | ||
"derivation_path": bip32_ints_to_str(account_path), | ||
"script_type": wallet_format["script_type"], | ||
} |
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.
Would be better to return a NamedTuple or similar; e.g. so that fields can be typed and can be enumerated statically (by IDE).
Thank you for your work. |
follow-up #6219 for multisig, it's just confusing and useless as-is
I would like to do exactly this with a multisig wallet using Zpub keys (for a read-only wallet). In case it's not obvious, the Zpubs were derived from BIP39 words and extended passphrase. Is this possible?
|
@AlistairTB No, this is only implemented for singlesig wallets. Also, it is not clear exactly how you would want to use this. |
Thank you for the reality check. I was using it the way it was intended, however, I had the wallet type wrong (3/3 vs 2/3) and was looking to solve the wrong problem. |
Today I was helping a friend recover funds from a ledger and I'd say this feature would be very well appreciated also when creating a watch-only wallet from a hardware device. Shall I open a dedicated issue? |
Sure, go ahead. |
Resolves #6155
Implements automated scanning of all known BIP32 chains that can be imported into Electrum.
I'm opening this PR for review, it's not ready to be merged yet. Account discovery is functional but still needs to be integrated in to the GUI.
Currently it's implemented as a script for easy testing, you can quickly test it out by running the
electrum/scripts/bip39_recovery.py
script with amnemonic
parameter and also an optional secondpassphrase
parameter. It will return a list of any previously used accounts it can find. You can use this mnemonic that I've created a few test accounts in:e.g:
Diversions from BIP44
Due to the fact that at discovery we only need to check which accounts has been used, not the final balance of each account, we take a few shortcuts:
If one of the discovered accounts is selected by the user for recovery, then Electrum will proceed to properly scan both internal external chains and exhaust the gap limit when it creates the wallet.
Limitations
Electrum takes an account level import path like
m/84'/0'/0'
and derives the wallet with the non hardenedis_change/address_index
sub trees. Due to this we cannot import Bitcoin Core accounts as they use the formatm/0'/0'/address_index'
, notice all sub trees use hardened derivation.All other wallets listed on walletsrecovery.org can be successfully recovered.
Please let me know if this code looks ok so far. Next steps will be to migrate the account discovery code from the script to
electrum.bip39_recovery
and then integrate it in to the recovery wizard GUI.CC @ecdsa @SomberNight @aantonop