bitcoinjs-lib icon indicating copy to clipboard operation
bitcoinjs-lib copied to clipboard

How to generate multiple bitcoin addresses using deterministic wallet?

Open aronfelipe opened this issue 5 years ago • 7 comments

I want to receive Bitcoin. I need to create one address every time an user makes a GET on a certain endpoint on my API. So the user makes the call, I generate the address on my server, return it to the client and save the address within the user on database.

aronfelipe avatar Aug 09 '19 17:08 aronfelipe

First, learn about bip32 with examples here:

https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.spec.ts

bip32 can be accessed by bitcoinjs.bip32 as well, no need to add as a dependency.

Then pick a derivation scheme. BIP49 using p2sh-p2wpkh is probably best, but BIP84 p2wpkh is also good, but might have trouble with compatibility for some wallets.

Then generate the xpub of the last hardened derivation and upload it to your server.

Then when you save the user's address in your database also save the address index for derivation in the same row.

junderw avatar Aug 10 '19 00:08 junderw

I'm struggling with the same problem.

I have a webserver that is probably difficult-to-secure, so I want to run a distributing-only wallet on it. My API would contain the full service wallet.

I'm thinking in the API:

    const mnemonic =
      'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
    const seed = bip39.mnemonicToSeedSync(mnemonic);
    const root = bip32.fromSeed(seed);

    const path = "m/49'/1'/0'";  // ??? is this possible?
    const account = root.derivePath(path);
    const accountXPub = account.neutered().toBase58();  // ??? is this a hardened key?

And on the webserver:

    // get the accountXPub via an API call
    const accountXPub = ...;
    const root = bip32.fromPublicKey(accountXPub); // ??? would this work?
    const child = root
      .derive(0)
      .derive(index);  // ??? can it derive this from the public key alone?
   // send the child's address to the user for payment

Then the webserver sends the index, together with the user query to the API. The API can reconstruct the address, this time with the private key, using that index.

In the API again:

    const child = account
        .derive(0)
        .derive(index); // can now withdraw whatever the user paid

The webserver should not contain private keys at any time.

Would this work?

I also don't want this to happen:

However, because the extended public key contains the chain code, if a child private key is known, or somehow leaked, it can be used with the chain code to derive all the other child private keys. A single leaked child private key, together with a parent chain code, reveals all the private keys of all the children. Worse, the child private key together with a parent chain code can be used to deduce the parent private key.

pieterjandesmedt avatar May 19 '20 19:05 pieterjandesmedt

Found it, I think.

import bip32 from 'bip32';
import bip39 from 'bip39';
import bitcoin from 'bitcoinjs-lib';
import assert from 'assert';

const testnet = bitcoin.networks.testnet;

function getAddress(node, network) {
	return bitcoin.payments.p2sh({
		redeem: bitcoin.payments.p2wpkh({ pubkey: node.publicKey, network }),
		network,
	}).address;
}

// This happens in the secure API
const mnemonic =
	'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
const seed = bip39.mnemonicToSeedSync(mnemonic);
const root = bip32.fromSeed(seed, testnet);

const accountpath = "m/49'/1'/0'"; // 1' is testnet, change to 0' for mainnet
const account = root.derivePath(accountpath);
const accountXPub = account.neutered().toBase58(); // testnet is tpub not xpub

// This happens in the less-secure webserver
// The webserver must not store private keys
const index = 5; // whatever

// The webserver gets the accountXPub on startup via an API call
// const accountXPub = axios.get(.../accountxpub);
const webRoot = bip32.fromBase58(accountXPub, testnet);
const webserverChild = webRoot
	.derive(0)
	.derive(index);
console.log('webserverChild:', webserverChild)
// send the child's address to the user for payment
console.log('webserverChild address:', getAddress(webserverChild, testnet))


// The index gets sent together with the user's query to the API
// The API reconstructs the address, but it can also construct the private key
const APIChild = account
	.derive(0)
	.derive(index); // can now withdraw whatever the user paid
console.log('APIChild:', APIChild);
console.log('APIChild address:', getAddress(APIChild, testnet));
console.log('APIChild.toWIF():', APIChild.toWIF())

assert.strictEqual(getAddress(webserverChild, testnet), getAddress(APIChild, testnet)); // same public key
// but the webserver node doesn't have a private key
assert.throws(webserverChild.toWIF);

pieterjandesmedt avatar May 19 '20 22:05 pieterjandesmedt

good, except your getAddress function uses p2pkh which is BIP44.

BIP49 must be p2sh-p2wpkh.

junderw avatar May 19 '20 22:05 junderw

@pieterjandesmedt modified your code to use the correct address generation and testnet network for everything.

junderw avatar May 22 '20 01:05 junderw

Thanks a lot!

pieterjandesmedt avatar May 22 '20 06:05 pieterjandesmedt

After searching for more than 2 hours , I finally got a useful example. Thank you so much @pieterjandesmedt

HarishRocks avatar Jul 01 '22 05:07 HarishRocks