Skip to content
This repository has been archived by the owner on Jul 15, 2018. It is now read-only.

keys package: fundraiser compatibility and HD keys (BIP 39 & BIP 32 / BIP 44) #118

Merged
merged 28 commits into from
Jun 20, 2018

Conversation

liamsi
Copy link
Contributor

@liamsi liamsi commented Jun 7, 2018

resolves #90
resolves #89
resolves #57

partly resolves #124
closes cosmos/cosmos-sdk#872

@liamsi liamsi changed the title WIP: BIP 39 & BIP 32 / BIP 44 things BIP 39 & BIP 32 / BIP 44 things Jun 12, 2018
@liamsi liamsi requested review from cwgoes and xla June 12, 2018 05:35
Copy link
Contributor

@xla xla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 :octocat: :shipit: 🍡

Some minor nitpicks. Given my limited understanding from the design doc this looks good to me.

armor.go Outdated
"golang.org/x/crypto/openpgp/armor"
)

func EncodeArmor(blockType string, headers map[string]string, data []byte) string {
buf := new(bytes.Buffer)
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())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK we want to transition away from panic methods from common and use naked panic statements.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thx! done

seed := bip39.NewSeed(d.Mnemonic, "")

//fmt.Println("================================")
//fmt.Println("ROUND:", i, "MNEMONIC:", d.Mnemonic)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not always print those and use t.Log to have it only presented on verbosre test runs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, why not 👍done

}
// 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we break method calls into multiple lines, can we consistently have the following format:

	namespace.Method(
		arg1,
		arg2,
		arg3,
	)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there significance to the named returns, or can we just return the result from i64 directly?


// 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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

fmt.Println()

seed = bip39.MnemonicToSeed(
"advice process birth april short trust crater change bacon monkey medal garment " +
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to have the first value at iota + 1 to prevent accidental zero value passing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we consider prefixing with Language.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iota+1 it is 👍

Should we consider prefixing with Language.

You mean s/English/LanguageEnglish/ ? Why? Isn't that a bit redundant?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to have solid error handling, either with sentinal values or types, so that callers get control flow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

keys/keybase.go Outdated
}
// 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, " ")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again would prefix.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

@liamsi liamsi changed the title BIP 39 & BIP 32 / BIP 44 things keys package: fundraiser compatibility and HD keys (BIP 39 & BIP 32 / BIP 44) Jun 12, 2018
Copy link
Contributor

@cwgoes cwgoes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor nits, needs a rebase on develop for the new PubKey() API

return
}

// MnemonicToSeedWithErrChecking is completely equivalent to MnemonicToSeed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment and the one two lines below seem to contradict each other - by "equivalent" do you mean that it returns the same seed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// 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{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be a bit more idiomatic to call our own NewParams here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we turn this into an issue if it will not be included in this PR?

data = append([]byte{byte(0)}, privKeyBytes[:]...)
} else {
// this can't return an error:
pubkey, err := crypto.PrivKeySecp256k1(privKeyBytes).PubKey()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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'.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purposing of "hardening" - can we link to the spec?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added link to the spec which contains further info.


func ExampleStringifyPathParams() {
path := NewParams(44, 0, 0, false, 0)
fmt.Println(path.String())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this print instead of checking the result?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// Output: 44'/0'/0'/0/0
}

func ExampleSomeBIP32TestVecs() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do these print instead of checking whether or not the outputs are correct in the testcase?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error check can be removed with a rebase on develop

random.go Outdated
@@ -82,15 +82,15 @@ func (ri *randInfo) MixEntropy(seedBytes []byte) {
hashBytes32 := [32]byte{}
copy(hashBytes32[:], hashBytes)
ri.seedBytes = xorBytes32(ri.seedBytes, hashBytes32)
// Create new cipher.Block
// CreateMnemonic new cipher.Block
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accidental find replace?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

@liamsi liamsi mentioned this pull request Jun 20, 2018
2 tasks
- fix comments
- call NewParams
- remove accidental change
- remove accidental changes
@liamsi liamsi merged commit 4634063 into tendermint:develop Jun 20, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants