FIX: crashes after importing malformed xpub as watch-only (closes #1399)

This commit is contained in:
Overtorment 2020-07-29 15:52:31 +01:00
parent 110c801dde
commit 12016f1277
6 changed files with 71 additions and 29 deletions

View File

@ -229,6 +229,9 @@ export class AppStorage {
case WatchOnlyWallet.type:
unserializedWallet = WatchOnlyWallet.fromJson(key);
unserializedWallet.init();
if (unserializedWallet.isHd() && !unserializedWallet.isXpubValid()) {
continue;
}
break;
case HDLegacyP2PKHWallet.type:
unserializedWallet = HDLegacyP2PKHWallet.fromJson(key);

View File

@ -971,20 +971,6 @@ export class AbstractHDElectrumWallet extends AbstractHDWallet {
}).address;
}
/**
* Converts zpub to xpub
*
* @param {String} zpub
* @returns {String} xpub
*/
static _zpubToXpub(zpub) {
let data = b58.decode(zpub);
data = data.slice(4);
data = Buffer.concat([Buffer.from('0488b21e', 'hex'), data]);
return b58.encode(data);
}
static _getTransactionsFromHistories(histories) {
const txs = [];
for (const history of Object.values(histories)) {

View File

@ -1,4 +1,5 @@
import { BitcoinUnit, Chain } from '../../models/bitcoinUnits';
import b58 from 'bs58check';
const createHash = require('create-hash');
export class AbstractWallet {
@ -217,4 +218,32 @@ export class AbstractWallet {
async wasEverUsed() {
throw new Error('Not implemented');
}
/**
* Converts zpub to xpub
*
* @param {String} zpub
* @returns {String} xpub
*/
static _zpubToXpub(zpub) {
let data = b58.decode(zpub);
data = data.slice(4);
data = Buffer.concat([Buffer.from('0488b21e', 'hex'), data]);
return b58.encode(data);
}
/**
* Converts ypub to xpub
* @param {String} ypub - wallet ypub
* @returns {*}
*/
static _ypubToXpub(ypub) {
let data = b58.decode(ypub);
if (data.readUInt32BE() !== 0x049d7cb2) throw new Error('Not a valid ypub extended key!');
data = data.slice(4);
data = Buffer.concat([Buffer.from('0488b21e', 'hex'), data]);
return b58.encode(data);
}
}

View File

@ -121,20 +121,6 @@ export class HDSegwitP2SHWallet extends AbstractHDElectrumWallet {
return psbt;
}
/**
* Converts ypub to xpub
* @param {String} ypub - wallet ypub
* @returns {*}
*/
static _ypubToXpub(ypub) {
let data = b58.decode(ypub);
if (data.readUInt32BE() !== 0x049d7cb2) throw new Error('Not a valid ypub extended key!');
data = data.slice(4);
data = Buffer.concat([Buffer.from('0488b21e', 'hex'), data]);
return b58.encode(data);
}
/**
* Creates Segwit P2SH Bitcoin address
* @param hdNode

View File

@ -3,6 +3,7 @@ import { HDSegwitP2SHWallet } from './hd-segwit-p2sh-wallet';
import { HDLegacyP2PKHWallet } from './hd-legacy-p2pkh-wallet';
import { HDSegwitBech32Wallet } from './hd-segwit-bech32-wallet';
const bitcoin = require('bitcoinjs-lib');
const HDNode = require('bip32');
export class WatchOnlyWallet extends LegacyWallet {
static type = 'watchOnly';
@ -41,7 +42,7 @@ export class WatchOnlyWallet extends LegacyWallet {
}
valid() {
if (this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub')) return true;
if (this.secret.startsWith('xpub') || this.secret.startsWith('ypub') || this.secret.startsWith('zpub')) return this.isXpubValid();
try {
bitcoin.address.toOutputScript(this.getAddress());
@ -213,4 +214,24 @@ export class WatchOnlyWallet extends LegacyWallet {
setUseWithHardwareWalletEnabled(enabled) {
this.use_with_hardware_wallet = !!enabled;
}
isXpubValid() {
let xpub;
try {
if (this.secret.startsWith('zpub')) {
xpub = this.constructor._zpubToXpub(this.secret);
} else if (this.secret.startsWith('ypub')) {
xpub = this.constructor._ypubToXpub(this.secret);
} else {
xpub = this.secret;
}
const hdNode = HDNode.fromBase58(xpub);
hdNode.derive(0);
return true;
} catch (_) {}
return false;
}
}

View File

@ -29,9 +29,26 @@ describe('Watch only wallet', () => {
assert.strictEqual(w.isHd(), true);
assert.strictEqual(w.getMasterFingerprint(), false);
assert.strictEqual(w.getMasterFingerprintHex(), '00000000');
assert.ok(w.isXpubValid(), w.secret);
}
});
it('can validate xpub', () => {
const w = new WatchOnlyWallet();
w.setSecret('xpub6CQdfC3v9gU86eaSn7AhUFcBVxiGhdtYxdC5Cw2vLmFkfth2KXCMmYcPpvZviA89X6DXDs4PJDk5QVL2G2xaVjv7SM4roWHr1gR4xB3Z7Ps');
assert.ok(w.isXpubValid());
w.setSecret('ypub6XRzrn3HB1tjhhvrHbk1vnXCecZEdXohGzCk3GXwwbDoJ3VBzZ34jNGWbC6WrS7idXrYjjXEzcPDX5VqnHEnuNf5VAXgLfSaytMkJ2rwVqy');
assert.ok(w.isXpubValid());
w.setSecret('zpub6r7jhKKm7BAVx3b3nSnuadY1WnshZYkhK8gKFoRLwK9rF3Mzv28BrGcCGA3ugGtawi1WLb2vyjQAX9ZTDGU5gNk2bLdTc3iEXr6tzR1ipNP');
assert.ok(w.isXpubValid());
w.setSecret('xpub6CQdfC3v9gU86eaSn7AhUFcBVxiGhdtYxdC5Cw2vLmFkfth2KXCMmYcPpvZviA89X6D');
assert.ok(!w.isXpubValid());
w.setSecret('ypub6XRzrn3HB1tjhhvrHbk1vnXCecZEdXohGzCk3GXwwbDoJ3VBzZ34jNGWbC6WrS7idXr');
assert.ok(!w.isXpubValid());
w.setSecret('ypub6XRzrn3HB1tjhhvrHbk1vnXCecZEdXohGzCk3GXwwbDoJ3VBzZ34jNGWbC6WrS7idXr');
assert.ok(!w.isXpubValid());
});
it('can create PSBT base64 without signature for HW wallet', async () => {
const w = new WatchOnlyWallet();
w.setSecret('zpub6rjLjQVqVnj7crz9E4QWj4WgczmEseJq22u2B6k2HZr6NE2PQx3ZYg8BnbjN9kCfHymSeMd2EpwpM5iiz5Nrb3TzvddxW2RMcE3VXdVaXHk');