feat #100 - Recovery phrase confirmation and account recovery UX#400
feat #100 - Recovery phrase confirmation and account recovery UX#400pmespresso wants to merge 68 commits intomasterfrom
Conversation
|
What characters may a Recovery Phrase contain? The latest Parity Signer and Parity Fether generates recovery words that just contain lowercase letters. But based on feedback here it seems we need to allow for capital letters in recovery phrases, or perhaps any character at all. |
|
@ltfschoen Thank you for bringing this up I'm not 100% certain about this but from testing out recovery phrases from existing accounts, I found the recovery phrases are definitely case sensitive and considering the raw wordlists themselves are strictly lowercase as well, I would say the ideal behavior is to strictly allow only lowercase words. If a user types any characters with capitals in it, we would automatically convert it to lowercase, as you said in this issue. Considering the wordlists have never included uppercase letters (as far as I've seen), I would conjecture that they never have and never will though someone who's been on top of this for longer @amaurymartiny @jacogr @Tbaut would surely have a more definite answer. |
|
We are using the bip39 NPM package to generate the recovery words. If you want to use it then just install Node.js, clone Fether and install its dependencies with Note: If you install the BIP39 package globally (i.e. Then you can check if a given recovery phrase (mnemonic) is valid, and whether making the first letter a capital letter makes a difference (see Example): If you run We're generating the recovery phrase in here in Fether by running We also need to support novasamatech/parity-signer#154 (so not just recovery phrases generated by Parity Signer or Parity Fether, which may include non-English word lists), support Substrate/Polkadot novasamatech/parity-signer#113 Note that in Substrate/Polkadot the mnemonic is an editable field |
|
@ltfschoen Is that a greenlight regarding automatically converting words typed into recovery phrase to lowercase? I have added the BIP English wordlist next to the Parity Wordlist as how I interpreted the requested feature is to alert the user if the last word they typed is invalid, as they type it.
Regarding |
No it's not a green light. We should ask someone with more experience to help us answer those questions |
|
Hey, so there are two ways of generating an address (in Fether's case, I mean for "recovering" an address) :
Accounts generated inside Fether use BIP39. Right now, when the user enters a recovery phrase, we check if it's a BIP39 phrase, and if not we check if it's a Parity wordlist phrase.
There is overlap in the two wordlists, so I think it'd be better to pop up a warning if the words aren't all contained in the BIP39 English wordlist, nor are they all contained in the Parity wordlist. |
…they want to recover with a self genereated phrase
…into yj-recovery-ux
There was a problem hiding this comment.
Thank you so much for the PR!
-
- I'd expect to see the "Skip" button one step earlier in the process, right after the name. We could simply replace the "back" button with it (the "Back" isn't really needed cf #380 (comment)).
It's look like this:

Note that in the previous screen I propose to remove the "If you lose your secret phrase, your wallet cannot be recovered." that is not true anymore now that we have JSON backup and replace it by "You can view and save the recovery phrase later on."
- I'd expect to see the "Skip" button one step earlier in the process, right after the name. We could simply replace the "back" button with it (the "Back" isn't really needed cf #380 (comment)).
-
- For the backup heads-up, what about having the "warning" merged with the menu button. I find it a little messy to have those 2 buttons on top of each other. Here is a proposition inspired by what Status does. It allows to give more context to the user, so that they understand right away what the warning is here for.
Notification (the "1" being hardcoded is totally fine for now).

-
- Could we use the same screens as the one we use in account creation (the ones from my first point) so that the user understands what we is asked e.g 1 screen with "Please write your recovery phrase on a piece of paper.." and 1 screen with the recovery phrase check.
-
- Security-wise, we must ask the account's password before showing the recovery phrase, because this could prevent the theft from the funds from a physical access of the computer that runs Fether
|
I've just boarded a flight on route back to Berlin :) I've cloned the branch to review offline and will provide feedback tomorrow. |
Tbaut
left a comment
There was a problem hiding this comment.
Did the following tests and everything looks good :)
- import account from recovery phrase
- import account from JSON
- new account with skipping
- new account without skipping
- sending Token between those
|
i have a presentation on Thurs that i need to urgently start preparing for so i won't be able to look at this until this Fri at the earliest |
amaury1093
left a comment
There was a problem hiding this comment.
Some comments, but they are all small and should be easy to fix. Overall it works great 💯.
I do have one concern: we are storing the user's phrase inside localStorage, symmetrically encrypted. I think this should be fine security-wise. But still, storing a private key/phrase on a computer always has risks. Could we actually skip the localStorage altogether?
The process I'm thinking is:
- user skips the backup phrase step
- in localStorage, we put
__flagged_${publicAddress} - in UI his account is shown as not backed up
- user goes to the "Backup Recovery Phrase" page
- "Unlock your account to view your recovery phrase" is not needed*.
- User types in his phrase
- Derive the public address from the phrase the user inputted
- Check that the derived public address == user's public address, if yes, set as
__safe_${publicAddress}
* Note: we can still add a dummy page "Type your password to continue", though the password won't do anything behind the scenes, just go to the next page.
| <button className='button -utility'> | ||
| <a | ||
| className='contact' | ||
| href='https://riot.im/app/#/room/#fether:matrix.parity.io' |
There was a problem hiding this comment.
I think this room is private. Probably the support channel?
| history.push(`/accounts/new/${+currentStep + 1}`); | ||
| // If user wants to skip, move directly past the rewrite step. | ||
| if (event.currentTarget.dataset.skip) { | ||
| await flagAccount(); |
| !BIP39_WORDLIST.has(word.toLowerCase()) && | ||
| !PARITY_WORDLIST.has(word.toLowerCase()) | ||
| ) { | ||
| invalidWords.add(word); |
There was a problem hiding this comment.
Although this works here, it's bad practice: state should be immutable!
Consider cloning invalidWords before adding new words
|
|
||
| if (isWordEnded) { | ||
| for (let i = 0; i < words.length; i++) { | ||
| let word = words[i]; |
There was a problem hiding this comment.
| let word = words[i]; | |
| const word = words[i]; |
| ) { | ||
| invalidWords.add(word); | ||
|
|
||
| this.setState({ |
There was a problem hiding this comment.
setState inside a loop always makes me scared: are we doing n renders? React should be optimized to make batch state changes, but we're never too cautious.
Consider doing setState once the loop is finished.
| 2. The user has confirmed backup of their recovery phrase, but just wants to view it once more. | ||
| -> In this case, we do not show warning, but we alow them to decrypt the phrase with their password. | ||
| */ | ||
| @inject('parityStore') |
| account: { address } | ||
| } = this.props; | ||
|
|
||
| const status = await getAccountFlagStatus(address); |
There was a problem hiding this comment.
tiny improvement: const [canViewRecoveryPhrase, flaggedPhrase] = await getAccountFlagStatus(address)
| this.setState({ | ||
| canViewRecoveryPhrase, | ||
| flaggedPhrase, | ||
| showWarning: flaggedPhrase && true |
There was a problem hiding this comment.
Prefer !!flaggedPhrase
|
|
||
| this.setState({ | ||
| canViewRecoveryPhrase, | ||
| flaggedPhrase, |
There was a problem hiding this comment.
This state is not used anywhere, we can remove it
| const { isMenuOpen } = this.state; | ||
|
|
||
| const rightMenu = ( | ||
| <div> |
At this point you expect the users to have copied their recovery-phrase in the first place whereas they have skipped the verification step most probably because they didn't want to copy it anywhere. Also I think it's important to be able to let users view their recovery phrase in case they lose their backup. JSON are more complex to handle. |
Ah, then I think we disagree on this. My initial thought was: you can skip the rewrite phrase step, because we're not your teachers here to make sure you've done your homework right. Just skip, but we'll show your account as not secured, because we can't prove that you've backed up your account. I.e. for me, on account creation, we expect you to copy your phrase.
I agree, but I would do that via RPC (related to #148). I'm just not convinced we should ever store the private keys inside localStorage. |
|
Thinking about it, I agree security-wise that it makes sense not to store the (albeit encrypted) private keys in the localstorage. I guess that means not being able to show the user their phrase ? Not sure how you're thinking of doing that with RPC, since the node doesn't know the phrase of the account, only the derived private key (or did you mean using the RPCs to encrypt the passphrase with the account's private key? seems a bit complex and might introduce vulnerabilities) |
|
Right, thanks guys. We need to find a secure way to do this or not do it at all. |
Hmm yes, I wasn't aware of that. So that's kind of problematic. |
|
Just to clarify, I think it's a bad idea to store the (albeit encrypted) seed phrase on the client side in general (whether it be localStorage, filesystem or something else). fether is a UI to the node, the latter securely managing the accounts, private keys, etc. I'd say let's keep this model of Fether not managing anything sensible and being (to an extent) pure UI. If we store the encrypted seed phrase in Fether then Fether isn't just a UI anymore, and we introduce a second point of vulnerability |
|
What about we do what Status does and we keep the RP in localstorage up until the user confirms they copied it, then we remove it entirely? This adds more meaning to having the Warning too. I agree it is not ideal to have the phrase in local storage at all but that's the risk the user opts into by not copying it somewhere safe on account creation. https://user-images.githubusercontent.com/6559881/41660909-b12273bc-749d-11e8-9b60-731f0a703a45.jpg |
|
I'm talking with Kirill to see what he thinks. I'm not sure it'd be a good idea because the user are not aware of the fact that their wallet is less secure. All they are aware of, is that they could lose their money if they don't backup the phrase, which a totally different story. Recovery phrases could very well be stored for a long period of time, be stolen, then no matter what the users do, such as backing it up, they are still at risk of stolen funds.. We need a RPC that gives a BIP39 phrase from an account and password, but this doesn't exist afaik. |
|
What should we do with this PR then? Apart from the localStorage, it's a great PR UI- and UX-wise. I'd propose again the flow I described in #400 (review). imo it's a better UX than the current one (=forcing user to copy phrase), and we get to keep all the React components YJ added. |
|
What you proposed is an interesting yet disturbing flow IMHO. The only difference with the current flow is that we bother the users later on to make sure they have originally written down the recovery phrase correctly. If we oblige them to correctly write it down in the first place anyway, no need to wait for the verification. I don't see the added value in delaying the timing of this verification because we can't provide the phrase anymore. Also what happens if they haven't correctly written down the phrase and only realize later on? They're left in an situation where they have to move their funds, that's frustrating. |
That's actually the step I want to avoid. I remember hating MetaMask with their "click on the words in the right order" step which you couldn't skip, thinking that I didn't need them to babysit me. If we don't add a "Skip" button and disable copy/pasting on the field, then I believe that'll be a super annoying step for most Fether users, which imo are already semi-experienced crypto-enthusiasts who know what they're doing. I'm okay with only the Skip button (+ warning popup), and removing the "Backup later" step, which I agree can be disturbing and often quite useless. |
Right, I'm also fine with that, given the warning screen. |
|
Closing as we won't implement it any time soon. |
|
😿 |





Issue #100
Steps to close:
- [x] : If an unknown word is typed in, an error message should let the user know that this is not a valid word for this wallet.- [x] : For the phrase approval, instead of letting the user type the whole words, we should have suggestion/auto-completionEdit:
Note -
Warn the user when more than x Eth have been moved on an account for which the recovery phrase hasn't been verifiedwill be a separate PR