btcsuite / btcutil

Provides bitcoin-specific convenience functions and types
479 stars 409 forks source link

Private key doesn't match public address #109

Closed winteraz closed 6 years ago

winteraz commented 6 years ago

I'm trying to generate pairs of bitcoin private keys and public addresses. To test it I sent a small amount of BTC to the child BTC address from an external wallet. The BTC was sent but the issue is that the corresponding private key from path/chain is not recognised by any wallet software(i.e. they error out saying that the private key doesn't match the address) so I can't use the coins sent anymore. Any idea if this is a bug inhdkeychain/btcutil package or am I doing something wrong ? The code below prints:

master wif 5JKsPX7gkeARgrWjp8N1w5A4sS36GaUZ8j84T25xwGz5TgnkuRW
 btc child private 5J1HZKEP9YBd4apoBKyJLgZ6uUsxDuJ9ap4ncWSeFd3DpFuPkf1
 btc child Public 3HAkEb9NTC4jJCUBevuB2VrFoucc5vdzz4

I used btc child public (3HAkEb9NTC4jJCUBevuB2VrFoucc5vdzz4) as receiving address but it doesn't seem to match the generated private key 5J1HZKEP9YBd4apoBKyJLgZ6uUsxDuJ9ap4ncWSeFd3DpFuPkf1


package main

import (
    "github.com/bartekn/go-bip39"
    "github.com/btcsuite/btcd/chaincfg"
    "github.com/btcsuite/btcd/txscript"
    "github.com/btcsuite/btcutil"
    "github.com/btcsuite/btcutil/hdkeychain"

    "crypto/ecdsa"
    "fmt"
    log "github.com/golang/glog"
)

func main() {
    pass := "somepass"
    mnemonic := "glide west cross blue animal salmon mind sell guess vote crazy fashion language report wrap lounge fence soft strategy voice scout merit gate buyer"
    priv, pub, err := NewFromMnemonic(mnemonic, pass)
    // master key/wallet
    masterWIF, err := priv.MasterWIF()
    if err != nil {
        panic(err)
    }
    account := uint32(0)
    index := uint32(1)
    cointTyp := BTC

    btcPublic, err := pub.PublicAddr(cointTyp, account, index)
    if err != nil {
        panic(err)
    }
    btcPrivate, err := priv.WIF(cointTyp, account, index)
    if err != nil {
        panic(err)
    }

    fmt.Printf("mn %s \n, master wif %s\n, btc child private %s\n btc child Public %s\n\n",
        mnemonic, masterWIF, btcPrivate, btcPublic)

}

// returns a new masterkey along with its base58encoded form
func NewMaster(passw string) (private, public *Key, err error) {
    entropy, err := bip39.NewEntropy(256)
    if err != nil {
        log.Error(err)
        return nil, nil, err
    }
    mnemonic, err := bip39.NewMnemonic(entropy)
    if err != nil {
        log.Error(err)
        return nil, nil, err
    }
    return NewFromMnemonic(mnemonic, passw)

}

func NewFromMnemonic(mnemonic, passw string) (private, public *Key, err error) {
    // Generate a Bip32 HD wallet for the mnemonic and a user supplied password
    seed := bip39.NewSeed(mnemonic, passw)
    // Create master private key from seed
    master, err := hdkeychain.NewMaster(seed, &chaincfg.MainNetParams)
    if err != nil {
        return nil, nil, err
    }
    private = &Key{k: master, mnemonic: mnemonic, private: true}
    neut, err := master.Neuter()
    if err != nil {
        return nil, nil, err
    }
    public = &Key{k: neut}
    return private, public, nil
}

func (k *Key) Mnemonic() (string, error) {
    if k.mnemonic == "" {
        return "", fmt.Errorf("no mnemonic available")
    }
    return k.mnemonic, nil
}

func (k *Key) MasterWIF() (string, error) {
    compress := false //?
    priv, err := k.k.ECPrivKey()
    if err != nil {
        return "", err
    }
    wf, err := btcutil.NewWIF(priv, &chaincfg.MainNetParams, compress)
    if err != nil {
        return "", err
    }
    return wf.String(), nil
}

// creates the Wallet Import Format string encoding of a WIF structure.
func (k *Key) WIF(coinTyp CoinType, account, index uint32) (string, error) {
    acctXExternalX, err := k.deriveKey(coinTyp, account, index)
    if err != nil {
        return "", err
    }
    compress := false //?
    priv, err := acctXExternalX.k.ECPrivKey()
    if err != nil {
        return "", err
    }
    wf, err := btcutil.NewWIF(priv, &chaincfg.MainNetParams, compress)
    if err != nil {
        return "", err
    }
    return wf.String(), nil
}

type CoinType uint32

const (
    BTC CoinType = 1
)

func (k *Key) deriveKey(coinTyp CoinType, account, index uint32) (*Key, error) {
    // m/49'
    purpose, err := k.k.Child(49)
    if err != nil {
        return nil, err
    }

    // m/49'/1'
    coinType, err := purpose.Child(uint32(coinTyp))
    if err != nil {
        return nil, err
    }

    // m/49'/1'/0'
    acctX, err := coinType.Child(account)
    if err != nil {
        return nil, err
    }
    // Derive the extended key for the account 0 external chain.  This
    // gives the path:
    //   m/0H/0
    // 0 is external, 1 is internal address (used for change, wallet software)
    acctXExt, err := acctX.Child(0)
    if err != nil {
        return nil, err
    }
    // Derive the Indexth extended key for the account X external chain.
    // m/49'/1'/0'/0
    acctXExternalX, err := acctXExt.Child(index)
    if err != nil {
        return nil, err
    }
    return &Key{private: k.private, k: acctXExternalX}, nil
}

func (k *Key) PublicAddr(coinTyp CoinType, account, index uint32) (string, error) {
    if k.private {
        // prevent insecure behaviour
        return "", fmt.Errorf("Don't use the private key to gen btc addresses, use the extended public key instead")
    }
    acctXExternalX, err := k.deriveKey(coinTyp, account, index)
    if err != nil {
        return "", err
    }
    // BIP49 segwit pay-to-script-hash style address.
    pubKey, err := acctXExternalX.k.ECPubKey()
    if err != nil {
        return "", err
    }

    keyHash := btcutil.Hash160(pubKey.SerializeCompressed())
    scriptSig, err := txscript.NewScriptBuilder().AddOp(txscript.OP_0).AddData(keyHash).Script()
    if err != nil {
        return "", err
    }
    addr, err := btcutil.NewAddressScriptHash(scriptSig, &chaincfg.MainNetParams)
    if err != nil {
        return "", err
    }
    return addr.String(), nil
}

type Key struct {
    private  bool
    k        *hdkeychain.ExtendedKey
    mnemonic string
}

// receives the master public key (Neuster) and returns a list of addresses ?
func (masterPublicKey *Key) DeriveAddress(coin CoinType, account, startIndex, limit uint32) ([]string, error) {
    var keys []string
    for i := startIndex; i <= startIndex+limit; i++ {
        k, err := masterPublicKey.PublicAddr(coin, account, i)
        if err != nil {
            log.Error(err)
            continue
        }
        keys = append(keys, k)
    }
    return keys, nil
}

func (k *Key) PrivateECDSA() (*ecdsa.PrivateKey, error) {
    if k.private == false {
        return nil, fmt.Errorf("key is not private")
    }
    ecPriv, err := k.k.ECPrivKey()
    if err != nil {
        return nil, err
    }
    return ecPriv.ToECDSA(), nil
}
winteraz commented 6 years ago

I believe the BTC is lost. I fixed the issue by using(from now on) a pay-to-pubkey-hash address resulted from ExtendedKey.Address instead of the previously segwit pay-to-script-hash style address. Disclosure: I don't know what I'm doing but seems to work so I'm closing this.