Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate hd public keys when creating accounts #696

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/wallet/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,9 @@ class Wallet extends EventEmitter {

if (!HD.isPublic(key))
throw new Error('Must add HD public keys to watch only wallet.');

if (!key.isAccount(this.accountDepth))
throw new Error(`Expected account key ${this.accountDepth}.`);
} else {
assert(this.master.key);
const type = this.network.keyPrefix.coinType;
Expand Down
172 changes: 172 additions & 0 deletions test/wallet-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

const assert = require('./util/assert');
const consensus = require('../lib/protocol/consensus');
const Network = require('../lib/protocol/network');
const util = require('../lib/utils/util');
const hash256 = require('bcrypto/lib/hash256');
const random = require('bcrypto/lib/random');
Expand All @@ -21,6 +22,11 @@ const HD = require('../lib/hd');
const Wallet = require('../lib/wallet/wallet');
const nodejsUtil = require('util');

const {
Mnemonic,
HDPrivateKey
} = HD;

const KEY1 = 'xprv9s21ZrQH143K3Aj6xQBymM31Zb4BVc7wxqfUhMZrzewdDVCt'
+ 'qUP9iWfcHgJofs25xbaUpCps9GDXj83NiWvQCAkWQhVj5J4CorfnpKX94AZ';

Expand All @@ -31,6 +37,12 @@ const KEY2 = 'xprv9s21ZrQH143K3mqiSThzPtWAabQ22Pjp3uSNnZ53A5bQ4udp'
const PUBKEY = 'xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhaw'
+ 'A7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj';

const PHRASE1 = 'abandon abandon abandon abandon abandon abandon '
+ 'abandon abandon abandon abandon abandon about';

const PHRASE2 = 'ability ability ability ability ability ability '
+ 'ability ability ability ability ability acid';

const workers = new WorkerPool({
enabled: true,
size: 2
Expand Down Expand Up @@ -890,6 +902,166 @@ describe('Wallet', function() {
assert(fmt.includes('lookahead'));
});

it('should fail w/ watch-only with incorrect depth hd pubkey', async() => {
const network = Network.get('main');
const mnemonic = Mnemonic.fromPhrase(PHRASE1);

// m'
const priv = HDPrivateKey.fromMnemonic(mnemonic);

// m'/44'
const bip44Key = priv.derive(44, true);

// m'/44'/0' Oops, wrong one.
const bitcoinKey = bip44Key.derive(0, true);
const xpub = bitcoinKey.xpubkey(network.type);

assert.rejects(async () => {
await wdb.create({
watchOnly: true,
accountKey: xpub
});
}, {
name: 'Error',
message: 'Expected account key 0.'
});
});

it('should fail w/ watch-only with incorrect network', async() => {
const network = Network.get('regtest');
const mnemonic = Mnemonic.fromPhrase(PHRASE1);

// m'
const priv = HDPrivateKey.fromMnemonic(mnemonic);

// m'/44'
const bip44Key = priv.derive(44, true);

// m'/44'/0'
const bitcoinKey = bip44Key.derive(0, true);

// m'/44'/0'/0'
const accountKey = bitcoinKey.derive(0, true);
const xpub = accountKey.xpubkey(network.type);

assert.rejects(async () => {
await wdb.create({
watchOnly: true,
accountKey: xpub
});
}, {
name: 'Error',
message: 'Network mismatch for xpubkey.'
});
});

it.skip('should fail w/ watch-only with unrecognized purpose', async() => {
const network = Network.get('main');
const mnemonic = Mnemonic.fromPhrase(PHRASE1);

// m'
const priv = HDPrivateKey.fromMnemonic(mnemonic);

// m'/400'
const bip44Key = priv.derive(400, true);

// m'/400'/0'
const bitcoinKey = bip44Key.derive(0, true);

// m'/400'/0'/0'
const accountKey = bitcoinKey.derive(0, true);
const pub = accountKey.xpubkey(network.type);

assert.rejects(async () => {
await wdb.create({
watchOnly: true,
accountKey: pub
});
}, {
name: 'Error',
message: 'Unrecognized purpose for key.'
});
});

it('should fail w/ watch-only with incorrect account hd pubkey', async() => {
const network = Network.get('main');
const mnemonic = Mnemonic.fromPhrase(PHRASE1);

// m'
const priv = HDPrivateKey.fromMnemonic(mnemonic);

// m'/44'
const bip44Key = priv.derive(44, true);

// m'/44'/0'
const bitcoinKey = bip44Key.derive(0, true);

// m'/44'/0'/0'
const accountKey = bitcoinKey.derive(0, true);
const xpub = accountKey.xpubkey(network.type);

// m'/44'/0'/5' Oops, wrong one.
const account5Key = bitcoinKey.derive(5, true);
const xpub5 = account5Key.xpubkey(network.type);

const wallet = await wdb.create({
watchOnly: true,
accountKey: xpub
});

assert.rejects(async () => {
await wallet.createAccount({
name: 'foo',
accountKey: xpub5
});
}, {
name: 'Error',
message: 'Expected account key 1.'
});
});

it.skip('should fail w/ watch-only with master key mismatch', async() => {
const network = Network.get('main');
const mnemonic1 = Mnemonic.fromPhrase(PHRASE1);
const mnemonic2 = Mnemonic.fromPhrase(PHRASE2);

// m'
const priv1 = HDPrivateKey.fromMnemonic(mnemonic1);
const priv2 = HDPrivateKey.fromMnemonic(mnemonic2);

// m'/44'
const bip44Key1 = priv1.derive(44, true);
const bip44Key2 = priv2.derive(44, true);

// m'/44'/0'
const bitcoinKey1 = bip44Key1.derive(0, true);
const bitcoinKey2 = bip44Key2.derive(0, true);

// m'/44'/0'/0'
const accountKey1 = bitcoinKey1.derive(0, true);
const xpub1 = accountKey1.xpubkey(network.type);

// m'/44'/0'/0'
const accountKey2 = bitcoinKey2.derive(0, true);
const xpub2 = accountKey2.xpubkey(network.type);

const wallet = await wdb.create({
watchOnly: true,
accountKey: xpub1
});

assert.rejects(async () => {
// Oops, wrong one.
await wallet.createAccount({
name: 'foo',
accountKey: xpub2
});
}, {
name: 'Error',
message: 'Master key mismatch.'
});
});

it('should fail to create duplicate account', async () => {
const wallet = await wdb.create();
const name = 'foo';
Expand Down