From 023d2f4e7a10cd6ee9c9a59d5e2b8fdebe828fa6 Mon Sep 17 00:00:00 2001 From: Andrea Franz Date: Tue, 11 Dec 2018 17:01:30 +0100 Subject: [PATCH] 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 * Update extkeys/hdkey_test.go Co-Authored-By: gravityblast * Update extkeys/hdkey_test.go Co-Authored-By: gravityblast * Update extkeys/hdkey_test.go Co-Authored-By: gravityblast * Update extkeys/hdkey_test.go Co-Authored-By: gravityblast * refactoring and comments * set EIP1581KeyTypeChat type to uint32 * set 0x00 instead of zero to avoid lint errors --- extkeys/hdkey.go | 76 +++++++++++++++++++++++++++++++++++++++---- extkeys/hdkey_test.go | 42 ++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 7 deletions(-) diff --git a/extkeys/hdkey.go b/extkeys/hdkey.go index bf66442aa..cf9c33b5c 100644 --- a/extkeys/hdkey.go +++ b/extkeys/hdkey.go @@ -41,6 +41,13 @@ import ( // TODO make sure we're doing this ^^^^ !!!!!! +type KeyPurpose int + +const ( + KeyPurposeWallet KeyPurpose = iota + 1 + KeyPurposeChat +) + const ( // HardenedKeyStart defines a starting point for hardened key. // Each extended key has 2^31 normal child keys and 2^31 hardened child keys. @@ -79,6 +86,7 @@ const ( // errors var ( ErrInvalidKey = errors.New("key is invalid") + ErrInvalidKeyPurpose = errors.New("key purpose is invalid") ErrInvalidSeed = errors.New("seed is invalid") 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") @@ -95,6 +103,25 @@ var ( // PublicKeyVersion is version for public key 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 @@ -236,9 +263,29 @@ func (k *ExtendedKey) Child(i uint32) (*ExtendedKey, error) { 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). // 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) { + 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 { return nil, ErrInvalidMasterKey } @@ -248,13 +295,28 @@ func (k *ExtendedKey) BIP44Child(coinType, i uint32) (*ExtendedKey, error) { } // m/44'/60'/0'/0/index - extKey, err := k.Derive([]uint32{ - HardenedKeyStart + 44, // purpose - HardenedKeyStart + coinType, // cointype - HardenedKeyStart + 0, // account - 0, // 0 - public, 1 - private - i, // index - }) + extKey, err := k.Derive(append(EthBIP44ParentPath, i)) + if err != nil { + return nil, err + } + + 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 { return nil, err } diff --git a/extkeys/hdkey_test.go b/extkeys/hdkey_test.go index 11aed96a6..d33d33e97 100644 --- a/extkeys/hdkey_test.go +++ b/extkeys/hdkey_test.go @@ -577,6 +577,48 @@ func TestBIP44ChildDerivation(t *testing.T) { 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) { password := "TREZOR" mnemonic := NewMnemonic()