status-go/extkeys/hdkey.go
Andrea Franz dcb0fa5262
add multi-account support (#1500)
* add multiaccount support

add multi account ImportPrivateKey and StoreAccount

test derivation from normal keys

* add multiaccount to mobile pkg

* use multiaccount params structs from the mobile pkg

* move multiaccount tests together with the other lib tests

* fix codeclimate warning and temporarily increase methods threshold

* split library_test_utils.go to avoid linter warnings
2019-07-24 20:59:15 +02:00

492 lines
17 KiB
Go

package extkeys
import (
"bytes"
"crypto/ecdsa"
"encoding/binary"
"encoding/hex"
"errors"
"fmt"
"math/big"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/base58"
)
// Implementation of the following BIPs:
// - BIP32 (https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
// - BIP39 (https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki)
// - BIP44 (https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki)
//
// Referencing
// https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
// https://bitcoin.org/en/developer-guide#hardened-keys
// Reference Implementations
// https://github.com/btcsuite/btcutil/tree/master/hdkeychain
// https://github.com/WeMeetAgain/go-hdwallet
// https://github.com/ConsenSys/eth-lightwallet/blob/master/lib/keystore.js
// https://github.com/bitpay/bitcore-lib/tree/master/lib
// MUST CREATE HARDENED CHILDREN OF THE MASTER PRIVATE KEY (M) TO PREVENT
// A COMPROMISED CHILD KEY FROM COMPROMISING THE MASTER KEY.
// AS THERE ARE NO NORMAL CHILDREN FOR THE MASTER KEYS,
// THE MASTER PUBLIC KEY IS NOT USED IN HD WALLETS.
// ALL OTHER KEYS CAN HAVE NORMAL CHILDREN,
// SO THE CORRESPONDING EXTENDED PUBLIC KEYS MAY BE USED INSTEAD.
// 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.
// Thus the range for normal child keys is [0, 2^31 - 1] and the range for hardened child keys is [2^31, 2^32 - 1].
HardenedKeyStart = 0x80000000 // 2^31
// MinSeedBytes is the minimum number of bytes allowed for a seed to a master node.
MinSeedBytes = 16 // 128 bits
// MaxSeedBytes is the maximum number of bytes allowed for a seed to a master node.
MaxSeedBytes = 64 // 512 bits
// serializedKeyLen is the length of a serialized public or private
// extended key. It consists of 4 bytes version, 1 byte depth, 4 bytes
// fingerprint, 4 bytes child number, 32 bytes chain code, and 33 bytes
// public/private key data.
serializedKeyLen = 4 + 1 + 4 + 4 + 32 + 33 // 78 bytes
// CoinTypeBTC is BTC coin type
CoinTypeBTC = 0 // 0x80000000
// CoinTypeTestNet is test net coin type
CoinTypeTestNet = 1 // 0x80000001
// CoinTypeETH is ETH coin type
CoinTypeETH = 60 // 0x8000003c
// EmptyExtendedKeyString marker string for zero extended key
EmptyExtendedKeyString = "Zeroed extended key"
// MaxDepth is the maximum depth of an extended key.
// Extended keys with depth MaxDepth cannot derive child keys.
MaxDepth = 255
)
// 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")
ErrBadChecksum = errors.New("bad extended key checksum")
ErrInvalidKeyLen = errors.New("serialized extended key length is invalid")
ErrDerivingChild = errors.New("error deriving child key")
ErrInvalidMasterKey = errors.New("invalid master key supplied")
ErrMaxDepthExceeded = errors.New("max depth exceeded")
)
var (
// PrivateKeyVersion is version for private key
PrivateKeyVersion, _ = hex.DecodeString("0488ADE4")
// 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
type ExtendedKey struct {
Version []byte // 4 bytes, mainnet: 0x0488B21E public, 0x0488ADE4 private; testnet: 0x043587CF public, 0x04358394 private
Depth uint8 // 1 byte, depth: 0x00 for master nodes, 0x01 for level-1 derived keys, ....
FingerPrint []byte // 4 bytes, fingerprint of the parent's key (0x00000000 if master key)
ChildNumber uint32 // 4 bytes, This is ser32(i) for i in xi = xpar/i, with xi the key being serialized. (0x00000000 if master key)
KeyData []byte // 33 bytes, the public key or private key data (serP(K) for public keys, 0x00 || ser256(k) for private keys)
ChainCode []byte // 32 bytes, the chain code
IsPrivate bool // (non-serialized) if false, this chain will only contain a public key and can only create a public key chain.
CachedPubKeyData []byte // (non-serialized) used for memoization of public key (calculated from a private key)
}
// nolint: gas
const masterSecret = "Bitcoin seed"
// NewMaster creates new master node, root of HD chain/tree.
// Both master and child nodes are of ExtendedKey type, and all the children derive from the root node.
func NewMaster(seed []byte) (*ExtendedKey, error) {
// Ensure seed is within expected limits
lseed := len(seed)
if lseed < MinSeedBytes || lseed > MaxSeedBytes {
return nil, ErrInvalidSeedLen
}
secretKey, chainCode, err := splitHMAC(seed, []byte(masterSecret))
if err != nil {
return nil, err
}
master := &ExtendedKey{
Version: PrivateKeyVersion,
Depth: 0,
FingerPrint: []byte{0x00, 0x00, 0x00, 0x00},
ChildNumber: 0,
KeyData: secretKey,
ChainCode: chainCode,
IsPrivate: true,
}
return master, nil
}
// Child derives extended key at a given index i.
// If parent is private, then derived key is also private. If parent is public, then derived is public.
//
// If i >= HardenedKeyStart, then hardened key is generated.
// You can only generate hardened keys from private parent keys.
// If you try generating hardened key form public parent key, ErrDerivingHardenedFromPublic is returned.
//
// There are four CKD (child key derivation) scenarios:
// 1) Private extended key -> Hardened child private extended key
// 2) Private extended key -> Non-hardened child private extended key
// 3) Public extended key -> Non-hardened child public extended key
// 4) Public extended key -> Hardened child public extended key (INVALID!)
func (k *ExtendedKey) Child(i uint32) (*ExtendedKey, error) {
if k.Depth == MaxDepth {
return nil, ErrMaxDepthExceeded
}
// A hardened child may not be created from a public extended key (Case #4).
isChildHardened := i >= HardenedKeyStart
if !k.IsPrivate && isChildHardened {
return nil, ErrDerivingHardenedFromPublic
}
keyLen := 33
seed := make([]byte, keyLen+4)
if isChildHardened {
// Case #1: 0x00 || ser256(parentKey) || ser32(i)
copy(seed[1:], k.KeyData) // 0x00 || ser256(parentKey)
} else {
// Case #2 and #3: serP(parentPubKey) || ser32(i)
copy(seed, k.pubKeyBytes())
}
binary.BigEndian.PutUint32(seed[keyLen:], i)
secretKey, chainCode, err := splitHMAC(seed, k.ChainCode)
if err != nil {
return nil, err
}
child := &ExtendedKey{
ChainCode: chainCode,
Depth: k.Depth + 1,
ChildNumber: i,
IsPrivate: k.IsPrivate,
// The fingerprint for the derived child is the first 4 bytes of parent's
FingerPrint: btcutil.Hash160(k.pubKeyBytes())[:4],
}
if k.IsPrivate {
// Case #1 or #2: childKey = parse256(IL) + parentKey
parentKeyBigInt := new(big.Int).SetBytes(k.KeyData)
keyBigInt := new(big.Int).SetBytes(secretKey)
keyBigInt.Add(keyBigInt, parentKeyBigInt)
keyBigInt.Mod(keyBigInt, btcec.S256().N)
// Make sure that child.KeyData is 32 bytes of data even if the value is represented with less bytes.
// When we derive a child of this key, we call splitHMAC that does a sha512 of a seed that is:
// - 1 byte with 0x00
// - 32 bytes for the key data
// - 4 bytes for the child key index
// If we don't padd the KeyData, it will be shifted to left in that 32 bytes space
// generating a different seed and different child key.
// This part fixes a bug we had previously and described at:
// https://medium.com/@alexberegszaszi/why-do-my-bip32-wallets-disagree-6f3254cc5846#.86inuifuq
keyData := keyBigInt.Bytes()
if len(keyData) < 32 {
extra := make([]byte, 32-len(keyData))
keyData = append(extra, keyData...)
}
child.KeyData = keyData
child.Version = PrivateKeyVersion
} else {
// Case #3: childKey = serP(point(parse256(IL)) + parentKey)
// Calculate the corresponding intermediate public key for intermediate private key.
keyx, keyy := btcec.S256().ScalarBaseMult(secretKey)
if keyx.Sign() == 0 || keyy.Sign() == 0 {
return nil, ErrInvalidKey
}
// Convert the serialized compressed parent public key into X and Y coordinates
// so it can be added to the intermediate public key.
pubKey, err := btcec.ParsePubKey(k.KeyData, btcec.S256())
if err != nil {
return nil, err
}
// childKey = serP(point(parse256(IL)) + parentKey)
childX, childY := btcec.S256().Add(keyx, keyy, pubKey.X, pubKey.Y)
pk := btcec.PublicKey{Curve: btcec.S256(), X: childX, Y: childY}
child.KeyData = pk.SerializeCompressed()
child.Version = PublicKeyVersion
}
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
}
if k.Depth != 0 {
return nil, ErrInvalidMasterKey
}
// m/44'/60'/0'/0/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
}
return extKey, nil
}
// Derive returns a derived child key at a given path
func (k *ExtendedKey) Derive(path []uint32) (*ExtendedKey, error) {
var err error
extKey := k
for _, i := range path {
extKey, err = extKey.Child(i)
if err != nil {
return nil, ErrDerivingChild
}
}
return extKey, nil
}
// Neuter returns a new extended public key from a give extended private key.
// If the input extended key is already public, it will be returned unaltered.
func (k *ExtendedKey) Neuter() (*ExtendedKey, error) {
// Already an extended public key.
if !k.IsPrivate {
return k, nil
}
// Get the associated public extended key version bytes.
version, err := chaincfg.HDPrivateKeyToPublicKeyID(k.Version)
if err != nil {
return nil, err
}
// Convert it to an extended public key. The key for the new extended
// key will simply be the pubkey of the current extended private key.
return &ExtendedKey{
Version: version,
KeyData: k.pubKeyBytes(),
ChainCode: k.ChainCode,
FingerPrint: k.FingerPrint,
Depth: k.Depth,
ChildNumber: k.ChildNumber,
IsPrivate: false,
}, nil
}
// IsZeroed returns true if key is nil or empty
func (k *ExtendedKey) IsZeroed() bool {
return k == nil || len(k.KeyData) == 0
}
// String returns the extended key as a human-readable base58-encoded string.
func (k *ExtendedKey) String() string {
if k.IsZeroed() {
return EmptyExtendedKeyString
}
var childNumBytes [4]byte
binary.BigEndian.PutUint32(childNumBytes[:], k.ChildNumber)
// The serialized format is:
// version (4) || depth (1) || parent fingerprint (4)) ||
// child num (4) || chain code (32) || key data (33) || checksum (4)
serializedBytes := make([]byte, 0, serializedKeyLen+4)
serializedBytes = append(serializedBytes, k.Version...)
serializedBytes = append(serializedBytes, k.Depth)
serializedBytes = append(serializedBytes, k.FingerPrint...)
serializedBytes = append(serializedBytes, childNumBytes[:]...)
serializedBytes = append(serializedBytes, k.ChainCode...)
if k.IsPrivate {
serializedBytes = append(serializedBytes, 0x00)
serializedBytes = paddedAppend(32, serializedBytes, k.KeyData)
} else {
serializedBytes = append(serializedBytes, k.pubKeyBytes()...)
}
checkSum := chainhash.DoubleHashB(serializedBytes)[:4]
serializedBytes = append(serializedBytes, checkSum...)
return base58.Encode(serializedBytes)
}
// pubKeyBytes returns bytes for the serialized compressed public key associated
// with this extended key in an efficient manner including memoization as
// necessary.
//
// When the extended key is already a public key, the key is simply returned as
// is since it's already in the correct form. However, when the extended key is
// a private key, the public key will be calculated and memoized so future
// accesses can simply return the cached result.
func (k *ExtendedKey) pubKeyBytes() []byte {
// Just return the key if it's already an extended public key.
if !k.IsPrivate {
return k.KeyData
}
pkx, pky := btcec.S256().ScalarBaseMult(k.KeyData)
pubKey := btcec.PublicKey{Curve: btcec.S256(), X: pkx, Y: pky}
return pubKey.SerializeCompressed()
}
// ToECDSA returns the key data as ecdsa.PrivateKey
func (k *ExtendedKey) ToECDSA() *ecdsa.PrivateKey {
privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), k.KeyData)
return privKey.ToECDSA()
}
// NewKeyFromString returns a new extended key instance from a base58-encoded
// extended key.
func NewKeyFromString(key string) (*ExtendedKey, error) {
if key == EmptyExtendedKeyString || len(key) == 0 {
return &ExtendedKey{}, nil
}
// The base58-decoded extended key must consist of a serialized payload
// plus an additional 4 bytes for the checksum.
decoded := base58.Decode(key)
if len(decoded) != serializedKeyLen+4 {
return nil, ErrInvalidKeyLen
}
// The serialized format is:
// version (4) || depth (1) || parent fingerprint (4)) ||
// child num (4) || chain code (32) || key data (33) || checksum (4)
// Split the payload and checksum up and ensure the checksum matches.
payload := decoded[:len(decoded)-4]
checkSum := decoded[len(decoded)-4:]
expectedCheckSum := chainhash.DoubleHashB(payload)[:4]
if !bytes.Equal(checkSum, expectedCheckSum) {
return nil, ErrBadChecksum
}
// Deserialize each of the payload fields.
version := payload[:4]
depth := payload[4:5][0]
fingerPrint := payload[5:9]
childNumber := binary.BigEndian.Uint32(payload[9:13])
chainCode := payload[13:45]
keyData := payload[45:78]
// The key data is a private key if it starts with 0x00. Serialized
// compressed pubkeys either start with 0x02 or 0x03.
isPrivate := keyData[0] == 0x00
if isPrivate {
// Ensure the private key is valid. It must be within the range
// of the order of the secp256k1 curve and not be 0.
keyData = keyData[1:]
keyNum := new(big.Int).SetBytes(keyData)
if keyNum.Cmp(btcec.S256().N) >= 0 || keyNum.Sign() == 0 {
return nil, ErrInvalidSeed
}
} else {
// Ensure the public key parses correctly and is actually on the
// secp256k1 curve.
_, err := btcec.ParsePubKey(keyData, btcec.S256())
if err != nil {
return nil, err
}
}
return &ExtendedKey{
Version: version,
KeyData: keyData,
ChainCode: chainCode,
FingerPrint: fingerPrint,
Depth: depth,
ChildNumber: childNumber,
IsPrivate: isPrivate,
}, nil
}