bitcoinjs-lib
bitcoinjs-lib copied to clipboard
How to generate multiple bitcoin addresses using deterministic wallet?
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.
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.
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.
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);
good, except your getAddress function uses p2pkh which is BIP44.
BIP49 must be p2sh-p2wpkh.
@pieterjandesmedt modified your code to use the correct address generation and testnet network for everything.
Thanks a lot!
After searching for more than 2 hours , I finally got a useful example. Thank you so much @pieterjandesmedt