add ExtendedKey.ChildForPurpose method to derive wallet or chat keys (#1312)

* add ExtendedKey.ChildForPurpose method to derive wallet or chat keys

* Update extkeys/hdkey.go

Co-Authored-By: gravityblast <andrea@gravityblast.com>

* Update extkeys/hdkey_test.go

Co-Authored-By: gravityblast <andrea@gravityblast.com>

* Update extkeys/hdkey_test.go

Co-Authored-By: gravityblast <andrea@gravityblast.com>

* Update extkeys/hdkey_test.go

Co-Authored-By: gravityblast <andrea@gravityblast.com>

* Update extkeys/hdkey_test.go

Co-Authored-By: gravityblast <andrea@gravityblast.com>

* refactoring and comments

* set EIP1581KeyTypeChat type to uint32

* set 0x00 instead of zero to avoid lint errors
This commit is contained in:
Andrea Franz 2018-12-11 17:01:30 +01:00 committed by GitHub
parent e2682486fd
commit 023d2f4e7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 111 additions and 7 deletions

View File

@ -41,6 +41,13 @@ import (
// TODO make sure we're doing this ^^^^ !!!!!! // TODO make sure we're doing this ^^^^ !!!!!!
type KeyPurpose int
const (
KeyPurposeWallet KeyPurpose = iota + 1
KeyPurposeChat
)
const ( const (
// HardenedKeyStart defines a starting point for hardened key. // HardenedKeyStart defines a starting point for hardened key.
// Each extended key has 2^31 normal child keys and 2^31 hardened child keys. // Each extended key has 2^31 normal child keys and 2^31 hardened child keys.
@ -79,6 +86,7 @@ const (
// errors // errors
var ( var (
ErrInvalidKey = errors.New("key is invalid") ErrInvalidKey = errors.New("key is invalid")
ErrInvalidKeyPurpose = errors.New("key purpose is invalid")
ErrInvalidSeed = errors.New("seed is invalid") ErrInvalidSeed = errors.New("seed is invalid")
ErrInvalidSeedLen = fmt.Errorf("the recommended size of seed is %d-%d bits", MinSeedBytes, MaxSeedBytes) ErrInvalidSeedLen = fmt.Errorf("the recommended size of seed is %d-%d bits", MinSeedBytes, MaxSeedBytes)
ErrDerivingHardenedFromPublic = errors.New("cannot derive a hardened key from public key") ErrDerivingHardenedFromPublic = errors.New("cannot derive a hardened key from public key")
@ -95,6 +103,25 @@ var (
// PublicKeyVersion is version for public key // PublicKeyVersion is version for public key
PublicKeyVersion, _ = hex.DecodeString("0488B21E") PublicKeyVersion, _ = hex.DecodeString("0488B21E")
// EthBIP44ParentPath is BIP44 keys parent's derivation path
EthBIP44ParentPath = []uint32{
HardenedKeyStart + 44, // purpose
HardenedKeyStart + CoinTypeETH, // cointype set to ETH
HardenedKeyStart + 0, // account
0, // 0 - public, 1 - private
}
// EIP1581KeyTypeChat is used as chat key_type in the derivation of EIP1581 keys
EIP1581KeyTypeChat uint32 = 0x00
// EthEIP1581ChatParentPath is EIP-1581 chat keys parent's derivation path
EthEIP1581ChatParentPath = []uint32{
HardenedKeyStart + 43, // purpose
HardenedKeyStart + CoinTypeETH, // cointype set to ETH
HardenedKeyStart + 1581, // EIP-1581 subpurpose
HardenedKeyStart + EIP1581KeyTypeChat, // key_type (chat)
}
) )
// ExtendedKey represents BIP44-compliant HD key // ExtendedKey represents BIP44-compliant HD key
@ -236,9 +263,29 @@ func (k *ExtendedKey) Child(i uint32) (*ExtendedKey, error) {
return child, nil return child, nil
} }
// ChildForPurpose derives the child key at index i using a derivation path based on the purpose.
func (k *ExtendedKey) ChildForPurpose(p KeyPurpose, i uint32) (*ExtendedKey, error) {
switch p {
case KeyPurposeWallet:
return k.EthBIP44Child(i)
case KeyPurposeChat:
return k.EthEIP1581ChatChild(i)
default:
return nil, ErrInvalidKeyPurpose
}
}
// BIP44Child returns Status CKD#i (where i is child index). // BIP44Child returns Status CKD#i (where i is child index).
// BIP44 format is used: m / purpose' / coin_type' / account' / change / address_index // BIP44 format is used: m / purpose' / coin_type' / account' / change / address_index
// BIP44Child is depracated in favour of EthBIP44Child
// Param coinType is deprecated; we override it to always use CoinTypeETH.
func (k *ExtendedKey) BIP44Child(coinType, i uint32) (*ExtendedKey, error) { func (k *ExtendedKey) BIP44Child(coinType, i uint32) (*ExtendedKey, error) {
return k.EthBIP44Child(i)
}
// BIP44Child returns Status CKD#i (where i is child index).
// BIP44 format is used: m / purpose' / coin_type' / account' / change / address_index
func (k *ExtendedKey) EthBIP44Child(i uint32) (*ExtendedKey, error) {
if !k.IsPrivate { if !k.IsPrivate {
return nil, ErrInvalidMasterKey return nil, ErrInvalidMasterKey
} }
@ -248,13 +295,28 @@ func (k *ExtendedKey) BIP44Child(coinType, i uint32) (*ExtendedKey, error) {
} }
// m/44'/60'/0'/0/index // m/44'/60'/0'/0/index
extKey, err := k.Derive([]uint32{ extKey, err := k.Derive(append(EthBIP44ParentPath, i))
HardenedKeyStart + 44, // purpose if err != nil {
HardenedKeyStart + coinType, // cointype return nil, err
HardenedKeyStart + 0, // account }
0, // 0 - public, 1 - private
i, // index return extKey, nil
}) }
// EthEIP1581ChatChild returns the whisper key #i (where i is child index).
// EthEIP1581ChatChild format is used is the one defined in the EIP-1581:
// m / 43' / coin_type' / 1581' / key_type / index
func (k *ExtendedKey) EthEIP1581ChatChild(i uint32) (*ExtendedKey, error) {
if !k.IsPrivate {
return nil, ErrInvalidMasterKey
}
if k.Depth != 0 {
return nil, ErrInvalidMasterKey
}
// m/43'/60'/1581'/0/index
extKey, err := k.Derive(append(EthEIP1581ChatParentPath, i))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -577,6 +577,48 @@ func TestBIP44ChildDerivation(t *testing.T) {
t.Logf("Account 1 key: %s", accounKey2.String()) t.Logf("Account 1 key: %s", accounKey2.String())
} }
func TestChildForPurpose(t *testing.T) {
masterKey, err := NewKeyFromString(masterPrivKey1)
if err != nil {
t.Error("NewKeyFromString: cannot create master extended key")
}
bip44Child, err := masterKey.EthBIP44Child(0)
if err != nil {
t.Error("Error deriving BIP44-compliant key")
}
eip1581Child, err := masterKey.EthEIP1581ChatChild(0)
if err != nil {
t.Error("Error deriving EIP1581-compliant key")
}
walletChild, err := masterKey.ChildForPurpose(KeyPurposeWallet, 0)
if err != nil {
t.Error("Error deriving BIP44-compliant key")
}
chatChild, err := masterKey.ChildForPurpose(KeyPurposeChat, 0)
if err != nil {
t.Error("Error deriving EIP1581-compliant key")
}
// Check that ChildForPurpose with KeyPurposeWallet generates a BIP44 key
if walletChild.String() != bip44Child.String() {
t.Errorf("wrong wallet key. expected to be equal to bip44Child")
}
// Check that ChildForPurpose with KeyPurposeChat generates a EIP1581 key
if chatChild.String() != eip1581Child.String() {
t.Errorf("wrong chat key. expected to be equal to eip1581Child")
}
// Check that the key generated by ChildForPurpose with KeyPurposeChat is different from the BIP44
if walletChild.String() == chatChild.String() {
t.Errorf("wrong chat key. expected to be diferrent from the wallet key")
}
}
func TestHDWalletCompatibility(t *testing.T) { func TestHDWalletCompatibility(t *testing.T) {
password := "TREZOR" password := "TREZOR"
mnemonic := NewMnemonic() mnemonic := NewMnemonic()