diff --git a/package.json b/package.json index 671df04..01dda4e 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "xo": "^0.24.0" }, "dependencies": { + "bip39": "^3.0.2", "bitcoinjs-lib": "^5.0.3", "tiny-emitter": "^2.1.0" } diff --git a/src/address-formats/p2pkh.js b/src/address-formats/p2pkh.js index dbf7bf7..37017f1 100644 --- a/src/address-formats/p2pkh.js +++ b/src/address-formats/p2pkh.js @@ -3,7 +3,8 @@ const {base58} = require('./charsets'); const p2pkh = { prefix: '1', - charset: base58 + charset: base58, + bip39: '44' }; p2pkh.derive = pubkey => { diff --git a/src/address-formats/p2wpkh-p2sh.js b/src/address-formats/p2wpkh-p2sh.js index f2a0241..518783a 100644 --- a/src/address-formats/p2wpkh-p2sh.js +++ b/src/address-formats/p2wpkh-p2sh.js @@ -3,7 +3,8 @@ const {base58} = require('./charsets'); const p2wpkhp2sh = { prefix: '3', - charset: base58 + charset: base58, + bip39: '49' }; p2wpkhp2sh.derive = pubkey => { diff --git a/src/address-formats/p2wpkh.js b/src/address-formats/p2wpkh.js index cc14aad..f7e2df1 100644 --- a/src/address-formats/p2wpkh.js +++ b/src/address-formats/p2wpkh.js @@ -3,7 +3,8 @@ const {bech32} = require('./charsets'); const p2wpkh = { prefix: 'bc1q', - charset: bech32 + charset: bech32, + bip39: '84' }; p2wpkh.derive = pubkey => { diff --git a/src/index.js b/src/index.js index 00401b5..49b6480 100644 --- a/src/index.js +++ b/src/index.js @@ -3,7 +3,8 @@ const Emitter = require('tiny-emitter'); const ONE_SECOND = 1000; const keyFormats = new Map(Object.entries({ - wif: require('./key-formats/wif') + wif: require('./key-formats/wif'), + bip39: require('./key-formats/bip39') })); const addressFormats = new Map(Object.entries({ @@ -35,6 +36,8 @@ class Vain extends Emitter { return new Promise(resolve => { const startTime = Date.now(); + const {generateKey, addressFormat} = this; + let found; let attempts = 0; let keyData; @@ -44,8 +47,8 @@ class Vain extends Emitter { while (!found) { attempts++; - keyData = this.generateKey(); - address = this.addressFormat.derive(keyData.publicKey); + keyData = generateKey({addressFormat}); + address = addressFormat.derive(keyData.publicKey); if (address.startsWith(this.prefix)) { found = true; diff --git a/src/key-formats/bip39.js b/src/key-formats/bip39.js new file mode 100644 index 0000000..2f23554 --- /dev/null +++ b/src/key-formats/bip39.js @@ -0,0 +1,20 @@ +const bip39 = require('bip39'); +const bitcoin = require('bitcoinjs-lib'); + +const generatebip39Key = ({addressFormat}) => { + const mnemonic = bip39.generateMnemonic(); + const seed = bip39.mnemonicToSeedSync(mnemonic); + + const node = bitcoin.bip32.fromSeed(seed); + const derivationPath = `m/${addressFormat.bip39}'/0'/0'/0/0`; + const {publicKey} = node.derivePath(derivationPath); + + const format = () => ({ + derivationPath, + mnemonic + }); + + return {publicKey, format}; +}; + +module.exports = generatebip39Key; diff --git a/test/unit.js b/test/unit.js index b926090..b434b41 100644 --- a/test/unit.js +++ b/test/unit.js @@ -1,5 +1,6 @@ import test from 'ava'; import * as bitcoin from 'bitcoinjs-lib'; +import * as bip39 from 'bip39'; import Vain from '..'; test('Vain is exported', t => { @@ -53,6 +54,62 @@ test('Vain derives a p2wpkh vanity address', async t => { t.is(address, wifAddress); }); +test('Vain derives a p2pkh vanity seed', async t => { + const options = { + keyFormat: 'bip39', + addressFormat: 'p2pkh', + prefix: 'A' + }; + const vain = new Vain(options); + const {address, derivationPath, mnemonic} = await vain.start(); + + const seed = await bip39.mnemonicToSeed(mnemonic); + const node = bitcoin.bip32.fromSeed(seed); + const {publicKey} = node.derivePath(derivationPath); + const {address: seedAddress} = bitcoin.payments.p2pkh({pubkey: publicKey}); + + t.true(address.startsWith(`1${options.prefix}`)); + t.is(address, seedAddress); +}); + +test('Vain derives a p2wpkh-p2sh vanity seed', async t => { + const options = { + keyFormat: 'bip39', + addressFormat: 'p2wpkh-p2sh', + prefix: 'A' + }; + const vain = new Vain(options); + const {address, derivationPath, mnemonic} = await vain.start(); + + const seed = await bip39.mnemonicToSeed(mnemonic); + const node = bitcoin.bip32.fromSeed(seed); + const {publicKey} = node.derivePath(derivationPath); + const {address: seedAddress} = bitcoin.payments.p2sh({ + redeem: bitcoin.payments.p2wpkh({pubkey: publicKey}) + }); + + t.true(address.startsWith(`3${options.prefix}`)); + t.is(address, seedAddress); +}); + +test('Vain derives a p2wpkh vanity seed', async t => { + const options = { + keyFormat: 'bip39', + addressFormat: 'p2wpkh', + prefix: 'a' + }; + const vain = new Vain(options); + const {address, derivationPath, mnemonic} = await vain.start(); + + const seed = await bip39.mnemonicToSeed(mnemonic); + const node = bitcoin.bip32.fromSeed(seed); + const {publicKey} = node.derivePath(derivationPath); + const {address: seedAddress} = bitcoin.payments.p2wpkh({pubkey: publicKey}); + + t.true(address.startsWith(`bc1q${options.prefix}`)); + t.is(address, seedAddress); +}); + test('Vain defaults to p2pkh if no address format is set', async t => { const options = { prefix: 'A'