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:
parent
e2682486fd
commit
023d2f4e7a
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue