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
cmd/bnscli: use mnemonic to generate private key #923
Conversation
Instead of generating a random private key use algorithm described in https://github.com/iov-one/iov-core/blob/master/docs/address-derivation-v1.md to create a mnemonic and later use that mnemonic to create a private key. resolve #855 Tests are missing.
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.
🐎
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.
Good start but from an UX point I would prefer the key generation to create and add the mnemonic and derived key to the storage instead than to pipe another command.
Looks like you want to cut a corner but not sure if the mnemonic command is of any use without key generation. We can have an import and export command instead that deal with the other processes.
I did this on purpose. I think it makes sense as each operation produce a result that is important on its own. Those commands are not always expected to be piped. I think the use cases are for example:
I don't think it is a valid usage if you do the following: $ bnscli mnemonic | bnscli keygen You have generated a private key, but you lost the mnemonic which I understand is much more important than the private key. Using separate commands enforce the user to make an important decision of where to store the mnemonic. It must be in a safe place that I do not want to guess. Otherwise, I would have to choose to store it in a file and decide on the location. In the current implementation, you can pipe it to an encryption program before writing to a file. You can use the same program of your choice (ie Vim 😂) to decode it before sending to Does this make sense? 😃 |
I think all is in place. Please review again. |
Codecov Report
@@ Coverage Diff @@
## master #923 +/- ##
========================================
- Coverage 65.1% 64.8% -0.3%
========================================
Files 176 176
Lines 10174 10242 +68
========================================
+ Hits 6627 6646 +19
- Misses 2801 2847 +46
- Partials 746 749 +3
Continue to review full report at Codecov.
|
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.
Good job
cmd/bnscli/cmd_key.go
Outdated
// path. | ||
func keygen(mnemonic, derivationPath string) (ed25519.PrivateKey, error) { | ||
if !bip39.IsMnemonicValid(string(mnemonic)) { | ||
return nil, errors.New("invalid mnemonic") |
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 we test this validation for
- valid mnemonic 12 words
- valid mnemonic 15 words
- valid mnemonic 18 words
- valid mnemonic 21 words
- valid mnemonic 24 words
- invalid: mnemonic with wrong checksum (change last word to some other workd from the workdlist)
- invalid: additional whitespace around valid mnemonnic
- invalid: mnenomic that is valid in a language other than English
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 code processing the mnemonic is not ours and usually I would argue that we should not test it. But here it is 4b19844
invalid: mnemonic with wrong checksum (change last word to some other workd from the workdlist)
I do not understand this case, so I did not include it in the tests.
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 code processing the mnemonic is not ours and usually I would argue that we should not test it.
We don't need to test every single code branch of that external library but we should at least cover our integration of it as well as the bahaviour we expect from the API.
I do not understand this case, so I did not include it in the tests.
I'm glad we talk about it: A BIP39 mnemonic is not just a list of 12, 15, 18, 21 or 24 words from a 2048 element wordlist. It is encoded data plus a checksum. In a 12 word mnemonic for example, 11.64 words are arbitrary data and 0.36 words are a checksum.
e.g.
valid:
crack turtle seminar height entire subway motion rail pass seat violin scene
invalid (first word changed):
turtle turtle seminar height entire subway motion rail pass seat violin scene
invalid (last word changed):
crack turtle seminar height entire subway motion rail pass seat violin violin
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.
It seems as the implementation is not complete.
https://github.com/tyler-smith/go-bip39/blob/master/bip39.go#L275-L294
- It ignores whitespace by using
string.Fields
to extract words. There is an issue for this Mnemonic whitespace separator deviates from reference implementation tyler-smith/go-bip39#29 - It ensures that the number of words is in the right amount
- It ensures that all words belong to the dictionary
- It does not check the checksum IsMnemonicValid does not validate checksum tyler-smith/go-bip39#32
I think the best is to create a pull request with a fix.
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 think the best is to create a pull request with a fix.
I agree. Good that we checked that ;)
Regarding 1.: the behaviour is fair indeed. We should just be explicit about it since not every implementations wirks that way. In this case we should have a test that verifies that mnemonic and menonic+whitespace leads to the same keypair.
}, | ||
"valid mnemonic 24 words": { | ||
mnemonic: "usage mountain noodle inspire distance lyrics caution wait mansion never announce biology squirrel guess key gain belt same matrix chase mom beyond model toy", | ||
wantErr: 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.
The last mnemonic is valid but the cut ones (15–21 words) are not due to invalid checksum. How can those tests pass?
never announce biology | ||
`, | ||
wantErr: 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.
Why is the additional whitespace test passing? When you add additional whitespace to a mnemonic, you get a different kypair/address
@webmaster128 please check again. I did not manage to fix the This bip39 is .... could be improved. It is also not updated/fixed anymore and broadly used which is very surprising. If I had 2 days of time I would for sure write it from scratch myself to have a proper implementation. |
@husio nice, looks good. Feel free to create a ticket for writing such a module. In this area a single bit can make the different between being rich and not having access to tokens at all. The only thing I am missing right now is a test that shows two mnemonics return the same keypair even if they differ in whitespace. |
Done in d370993 |
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.
nice
Mnemonic must be normalized before being used, so I have extended the validation to not allow any whitespace separator. To avoid issues, only a single space word separator is allowed. |
# Use a custom key path just in case the host is using original one. | ||
tempdir=`mktemp -d` | ||
keypath=$tempdir/key.priv | ||
export BNSCLI_PRIV_KEY=$keypath | ||
|
||
bnscli keygen | ||
bnscli keygen < $mnemonic | ||
rm $mnemonic |
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.
req: please update the comments below as key derivation is deterministic.
@@ -25,6 +32,7 @@ created. This command fails if the private key file already exists. | |||
var ( | |||
keyPathFl = fl.String("key", env("BNSCLI_PRIV_KEY", os.Getenv("HOME")+"/.bnsd.priv.key"), | |||
"Path to the private key file that transaction should be signed with. You can use BNSCLI_PRIV_KEY environment variable to set it.") | |||
pathFl = fl.String("path", "m/44'/234'/0'", "Derivation path as described in BIP-44.") |
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.
pp: please add default path to the description so that it can be copied and modified knowing our exact format and numbers if required.
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.
Default is always printed as part of the help message.
-path string
Derivation path as described in BIP-44. (default "m/44'/234'/0'")
// words as this is important during seed creation. | ||
func validateMnemonic(mnemonic string) error { | ||
// A lazy way to check that words are exactly single space separated. | ||
expected := strings.Join(strings.Fields(mnemonic), " ") |
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.
Works for whitespace. Is upper/lower case still considered a problem here?
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 think validation is case-sensitive as it has a fixed set of words that it is using to map each word to a numeric value. Using non lowercase letter would not return a match in the index.
@@ -66,6 +126,7 @@ Print out a hex-address associated with your private key. | |||
var ( | |||
keyPathFl = fl.String("key", env("BNSCLI_PRIV_KEY", os.Getenv("HOME")+"/.bnsd.priv.key"), | |||
"Path to the private key file that transaction should be signed with. You can use BNSCLI_PRIV_KEY environment variable to set it.") | |||
bechPrefixFl = fl.String("bp", "iov", "Bech32 prefix.") |
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.
req: please add the 2 formats we have for tiov
testnet and iov
as default to the description.
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.
What do you mean? You can have only one default value.
// https://github.com/iov-one/iov-core/blob/8846fed17443766a9ad9c908c3d7fc9d205e02ef/docs/address-derivation-v1.md#deriving-addresses-from-keypairs | ||
func toBech32(prefix string, pubkey []byte) ([]byte, error) { | ||
data := append([]byte("sigs/ed25519/"), pubkey...) | ||
hash := sha256.Sum256(data) |
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.
pp: hash[:20]
is the same as weave.NewCondition("sigs", "ed25519", pubkey).Address(()
. Please use existing logic instead for this sensible part.
return ioutil.ReadAll(input) | ||
} | ||
|
||
var errNoPipe = errors.New("no data piped") |
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.
req: Please move const and vars before the methods using them.
Instead of generating a random private key use algorithm described in
https://github.com/iov-one/iov-core/blob/master/docs/address-derivation-v1.md
to create a mnemonic and later use that mnemonic to create a private
key.
resolve #855
Tests are missing.
🗒️ Use https://iov-one.github.io/token-finder/ for testing