status-im/status-go

View on GitHub
extkeys/hdkey.go

Summary

Maintainability
A
0 mins
Test Coverage
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
}