btcsuite / btcutil

Provides bitcoin-specific convenience functions and types
477 stars 410 forks source link

[Question] How do you use a existing public master key to generate multiple bitcoin addresses? #77

Closed shackra closed 8 years ago

shackra commented 8 years ago

Hello!

Let's say that this is my public master key that I generated from a private key that was generated by a BIP39 seed:

xpub661MyMwAqRbcGNVENt2tgk3uebENbktcaKroxgwa9T5W3btQ18PL2f7vp78LNoioGhZcgSH1i2cH48YcQttiEaAh86TuJpsUu2J7jQWnmbC

So far, I have this code:

package main

import (
    "os"

    "github.com/btcsuite/btcd/chaincfg"
    "github.com/btcsuite/btcutil/hdkeychain"
)

var xpubmkeydummy = "xpub661MyMwAqRbcGNVENt2tgk3uebENbktcaKroxgwa9T5W3bt" +
    "Q18PL2f7vp78LNoioGhZcgSH1i2cH48YcQttiEaAh86TuJpsUu2J7jQWnmbC"

func GetNewAddress(i int) (string, error) {
    xpubmkey := os.Getenv("xpubmkey")
    if xpubmkey == "" {
        xpubmkey = xpubmkeydummy
    }
    masterkey, err := hdkeychain.NewKeyFromString(xpubmkey)
    if err != nil {
        return "", err
    }
    acct, err := masterkey.Child(0)
    if err != nil {
        return "", err
    }
    acctext, err := acct.Child(0)
    if err != nil {
        return "", err
    }
    acctn, err := acctext.Child(uint32(i))
    if err != nil {
        return "", err
    }
    addr, err := acctn.Address(&chaincfg.MainNetParams)
    if err != nil {
        return "", err
    }
    return addr.String(), nil
}

My main concern is if I'm doing everything as specified in the use case of Unsecure money receiver: N(m/iH/0) from the BIP32 document, I fear doing something dumb or wrong that could cost me Bitcoins.

Thanks!

jrick commented 8 years ago

As the linked document states, an attacker would be able to see all incoming payments to any address from that external chain, but not necessarily be able to steal any of the payments (since private keys can reside on another isolated server the attacker has not gained access to). This could be a privacy issue, but assuming the private keys have not been compromised, the coins can not be stolen.

jrick commented 8 years ago

Also, your code does not match the example HD path from that document. The account key is hardened, meaning it requires the private key to derive it. You can not derive the account public key without the master private key.

shackra commented 8 years ago

Also, your code does not match the example HD path from that document. The account key is hardened, meaning it requires the private key to derive it. You can not derive the account public key without the master private key.

But, that was intended since I already have a master public key generated, and, making a requirement to have the private master key around on my application would worse the things, am I right? so what I'm trying to achieve is intended, thought, I don't know if I'm going in the correct course.

jrick commented 8 years ago

Oh, i misunderstood you originally.

Using hardened account keys is highly recommended due to being unable to generate the next account key without the master privkey. If an attacker gets in, only keys from those accounts available to the attacker will be known by them, rather than revealing addresses from accounts not even created yet (or created for another purpose).

You will need to design your application in such a way that you can create the hardened account keys on the isolated server that has the private keys, transferring them to the insecure server to generate receiving addresses for the account. The insecure server will not be able to create any new accounts. If it could, an attacker could too.

shackra commented 8 years ago

So...

You will need to design your application in such a way that you can create the hardened account keys on the isolated server that has the private keys, transferring them to the insecure server to generate receiving addresses for the account.

I guess I kind understand you. I'll do in code what I understood and please tell me if I'm doing it correctly:

package main

import (
    "fmt"

    "github.com/btcsuite/btcd/chaincfg"
    "github.com/btcsuite/btcutil/hdkeychain"
)

func main() {
    var xprivnohard = "xprv9s21ZrQH143K3tQmGrVtKc7B6ZPtCJAmD6wDAJXxb7YXAoZFTb55Uro" +
        "SxrU7k823vSQmYPDhdseRKqP1mgSUWDneinai2seUd7RLX2xkmGW"
    // m, right?
    masterprivkey, _ := hdkeychain.NewKeyFromString(xprivnohard)
    // m/10H, and hardened, right?
    acct10, _ := masterprivkey.Child(hdkeychain.HardenedKeyStart + 10)
    // m/10H/0, right? this is everything I need or I also need m/10H/1?
    acct10Ext, _ := acct10.Child(0)
    // m/10H/0/33 will generate a correct Bitcoin address and all, right?
    acct10Ext33, _ := acct10Ext.Child(33)
    address, _ := acct10Ext33.Address(&chaincfg.MainNetParams)
    fmt.Printf("Load this in a insecure server: %s\n", acct10Ext)
    fmt.Printf("BTW, here is the address of the leaf(?) 33: %s\n", address.String())
}

The output is this:

Load this in a insecure server: xprv9xsqyYRSqfhDvmKA2L82A6mc6BAepdtgooicVyUtLtTXQNjuVuEK9hUEeFqJJ7txfBeCizAiUtCsAYvcCa2LFaSowi3Nf7ziKHTvG5UiWeF
BTW, here is the address of the leaf(?) 33: 1EQxMzQZP3r4AEncLgsPGT9GFAr8nxfurY

So far, so good? please tell me if I misunderstood. Later, the key is moved to the insecure server, with a code that happens to look like this:

package main

import (
    "fmt"

    "github.com/btcsuite/btcd/chaincfg"
    "github.com/btcsuite/btcutil/hdkeychain"
)

func main() {
    var key = "xprv9xsqyYRSqfhDvmKA2L82A6mc6BAepdtgooicVyUtLtTXQNjuVuEK" +
        "9hUEeFqJJ7txfBeCizAiUtCsAYvcCa2LFaSowi3Nf7ziKHTvG5UiWeF"
    // m/10H/0, right?
    acct10Ext, _ := hdkeychain.NewKeyFromString(key)
    // m/10H/0/33 will generate a correct Bitcoin address and all, right?
    acct10Ext33, _ := acct10Ext.Child(33)
    address, _ := acct10Ext33.Address(&chaincfg.MainNetParams)
    fmt.Printf("here is the address of the leaf(?) 33: %s\n", address.String())
}

And outputs this:

here is the address of the leaf(?) 33: 1EQxMzQZP3r4AEncLgsPGT9GFAr8nxfurY

Well, I'm getting the same Bitcoin address, maybe I'm nailing it?


I have a last question: Is possible to build a lightweight wallet with btcutil and/or other packages from the btcsuite? If so, there is any examples on loading the m/ private key, getting the balance of that private key and spending the coins in it? sorry if I'm such a trouble but to wonder in the source code of this project is overwhelming to me and I get lost easily.

shackra commented 8 years ago

And I was never acknowledged to be wrong or correct... I would hate to lost coins just to test if I'm right.

jrick commented 8 years ago

Your application appears to be using the correct HD paths now.

You can of course create a very simple custom "wallet" that only manages keys, but to track balances, you will need to track of all unspent outputs controlled by those keys as well. This would require receiving updates regarding those outputs through some means such as SPV with bloom filters, or using btcd and btcwallet's custom implementation that uses JSON-RPC notifications over a websocket connection. This is where most of the complexity lies and is probably why you find the rest of our code overwhelming.

BTW: testnet should be used for testing.

jrick commented 8 years ago

I take back what I said about your newer code being correct. It's not. You also need to neuter the key so only the account extended public key is given to the insecure server, not the extended private key.

shackra commented 8 years ago

really?

That's what I was fearing. Luckily I'll use Blockchain.info payment receiving API...

Thanks for the support!

El lunes 29 de agosto del 2016 a las 0809 horas, Josh Rickmar escribió:

I take back what I said about your newer code being correct. It's not. You also need to neuter the key so only the account extended public key is given to the insecure server, not the extended private key.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub, or mute the thread.

👋 Pax et bonum. Jorge Araya Navarro https://es.gravatar.com/shackra