keys package: fundraiser compatibility and HD keys (BIP 39 & BIP 32 / BIP 44)#118
keys package: fundraiser compatibility and HD keys (BIP 39 & BIP 32 / BIP 44)#118liamsi merged 28 commits intotendermint:developfrom
Conversation
0802437 to
ca85172
Compare
…approach # Conflicts: # signature_test.go
…approach # Conflicts: # signature_test.go
adbcf91 to
8ef5d95
Compare
8ef5d95 to
54b9bd5
Compare
0e710fb to
e9506fc
Compare
xla
left a comment
There was a problem hiding this comment.
👍
🍡
Some minor nitpicks. Given my limited understanding from the design doc this looks good to me.
armor.go
Outdated
| w, err := armor.Encode(buf, blockType, headers) | ||
| if err != nil { | ||
| PanicSanity("Error encoding ascii armor: " + err.Error()) | ||
| cmn.PanicSanity("Error encoding ascii armor: " + err.Error()) |
There was a problem hiding this comment.
AFAIK we want to transition away from panic methods from common and use naked panic statements.
keys/hd/fundraiser_test.go
Outdated
| seed := bip39.NewSeed(d.Mnemonic, "") | ||
|
|
||
| //fmt.Println("================================") | ||
| //fmt.Println("ROUND:", i, "MNEMONIC:", d.Mnemonic) |
There was a problem hiding this comment.
Why not always print those and use t.Log to have it only presented on verbosre test runs.
keys/hd/hdpath.go
Outdated
| } | ||
| // m / purpose' / coin_type' / account' / change / address_index | ||
| return fmt.Sprintf("%d'/%d'/%d'/%s/%d", | ||
| p.purpose, p.coinType, p.account, changeStr, p.addressIdx) |
There was a problem hiding this comment.
If we break method calls into multiple lines, can we consistently have the following format:
namespace.Method(
arg1,
arg2,
arg3,
)| // ComputeMastersFromSeed returns the master public key, master secret, and chain code in hex. | ||
| func ComputeMastersFromSeed(seed []byte) (secret [32]byte, chainCode [32]byte) { | ||
| masterSecret := []byte("Bitcoin seed") | ||
| secret, chainCode = i64(masterSecret, seed) |
There was a problem hiding this comment.
Is there significance to the named returns, or can we just return the result from i64 directly?
keys/hd/hdpath.go
Outdated
|
|
||
| // DerivePrivateKeyForPath derives the private key by following the BIP 32/44 path from privKeyBytes, | ||
| // using the given chainCode. | ||
| func DerivePrivateKeyForPath(privKeyBytes [32]byte, chainCode [32]byte, path string) (derivedKey [32]byte, err error) { |
There was a problem hiding this comment.
Named returns for function bodies of this complexity seem hard to decypher what the state at what point is. It could be clearer with early and explicit returns.
| fmt.Println() | ||
|
|
||
| seed = bip39.MnemonicToSeed( | ||
| "advice process birth april short trust crater change bacon monkey medal garment " + |
There was a problem hiding this comment.
Why not use an unescaped raw string?
keys/keybase.go
Outdated
| const ( | ||
| // English is the default language to create a mnemonic. | ||
| // It is the only supported language by this package. | ||
| English Language = iota |
There was a problem hiding this comment.
Would be nice to have the first value at iota + 1 to prevent accidental zero value passing.
There was a problem hiding this comment.
Should we consider prefixing with Language.
There was a problem hiding this comment.
iota+1 it is 👍
Should we consider prefixing with Language.
You mean s/English/LanguageEnglish/ ? Why? Isn't that a bit redundant?
There was a problem hiding this comment.
There is a case to make for either on the caller side it's a bit more clear when reading, yet the type system should inform that already. Could be either way, in general it's good to prefix if the namespace of the package gets a bit more crowded.
keys/keybase.go
Outdated
| priv, err := generate(algo, secret) | ||
| func (kb dbKeybase) CreateMnemonic(name string, language Language, passwd string, algo SigningAlgo) (info Info, mnemonic string, err error) { | ||
| if language != English { | ||
| return nil, "", fmt.Errorf("unsupported language: currently only english is supported") |
There was a problem hiding this comment.
Would be nice to have solid error handling, either with sentinal values or types, so that callers get control flow.
There was a problem hiding this comment.
There is not much the callers can control here: In these cases (currently) only one possible language is supported (all other will err). This means, if the caller calls this with a different language then engl. he (currently) isn't using the lib correctly (and he can't handle this error case differently then by picking a supported language). Maybe, the sentinel types are still useful as they additionally document the behaviour (additionally to the documentation).
| } | ||
| // TODO(ismail): we have to be careful with the separator in non-ltr languages. Ideally, our package should provide | ||
| // a helper function for that | ||
| mnemonic = strings.Join(mnemonicS, " ") |
There was a problem hiding this comment.
The least we can do with the separators is to have them in constants, and ideally later configurable. This is the second occurrence of the literal string and a TODO, looks like a good candidate to fix before merge.
| } | ||
| } | ||
| // Secp256k1 uses the Bitcoin secp256k1 ECDSA parameters. | ||
| Secp256k1 = SigningAlgo("secp256k1") |
… for word list and supported curve/params for signing
cwgoes
left a comment
There was a problem hiding this comment.
A few minor nits, needs a rebase on develop for the new PubKey() API
keys/bip39/wordcodec.go
Outdated
| return | ||
| } | ||
|
|
||
| // MnemonicToSeedWithErrChecking is completely equivalent to MnemonicToSeed. |
There was a problem hiding this comment.
This comment and the one two lines below seem to contradict each other - by "equivalent" do you mean that it returns the same seed?
keys/hd/hdpath.go
Outdated
| // m / 44' / 118' / account' / 0 / address_index | ||
| // The fixed parameters (purpose', coin_type', and change) are determined by what was used in the fundraiser. | ||
| func NewFundraiserParams(account uint32, addressIdx uint32) *BIP44Params { | ||
| return &BIP44Params{ |
There was a problem hiding this comment.
Might be a bit more idiomatic to call our own NewParams here
keys/hd/hdpath.go
Outdated
| // using the given chainCode. | ||
| func DerivePrivateKeyForPath(privKeyBytes [32]byte, chainCode [32]byte, path string) (derivedKey [32]byte, err error) { | ||
| data := privKeyBytes | ||
| // TODO(ismail): refactor this out and provide a constructor for BIP44 |
There was a problem hiding this comment.
Can we turn this into an issue if it will not be included in this PR?
keys/hd/hdpath.go
Outdated
| data = append([]byte{byte(0)}, privKeyBytes[:]...) | ||
| } else { | ||
| // this can't return an error: | ||
| pubkey, err := crypto.PrivKeySecp256k1(privKeyBytes).PubKey() |
There was a problem hiding this comment.
Can be rebased on develop, which no longer errors on .PubKey()
| } | ||
|
|
||
| // derivePrivateKey derives the private key with index and chainCode. | ||
| // If harden is true, the derivation is 'hardened'. |
There was a problem hiding this comment.
What is the purposing of "hardening" - can we link to the spec?
There was a problem hiding this comment.
Added link to the spec which contains further info.
|
|
||
| func ExampleStringifyPathParams() { | ||
| path := NewParams(44, 0, 0, false, 0) | ||
| fmt.Println(path.String()) |
There was a problem hiding this comment.
Why does this print instead of checking the result?
There was a problem hiding this comment.
| // Output: 44'/0'/0'/0/0 | ||
| } | ||
|
|
||
| func ExampleSomeBIP32TestVecs() { |
There was a problem hiding this comment.
Why do these print instead of checking whether or not the outputs are correct in the testcase?
There was a problem hiding this comment.
This prints do exactly that: https://blog.golang.org/examples
keys/keybase.go
Outdated
| if passwd != "" { | ||
| info = kb.writeLocalKey(crypto.PrivKeySecp256k1(derivedPriv), name, passwd) | ||
| } else { | ||
| pubk, err := crypto.PrivKeySecp256k1(derivedPriv).PubKey() |
There was a problem hiding this comment.
Error check can be removed with a rebase on develop
random.go
Outdated
| copy(hashBytes32[:], hashBytes) | ||
| ri.seedBytes = xorBytes32(ri.seedBytes, hashBytes32) | ||
| // Create new cipher.Block | ||
| // CreateMnemonic new cipher.Block |
- fix comments - call NewParams - remove accidental change
- remove accidental changes
…ip39-2nd-approach
resolves #90
resolves #89
resolves #57
partly resolves #124
closes cosmos/cosmos-sdk#872