From a9aa213b212054e451f171818f721465fd7c1f02 Mon Sep 17 00:00:00 2001 From: Overtorment Date: Tue, 8 Dec 2020 14:58:16 +0000 Subject: [PATCH] FIX: scan multisig cosigner in formats: plain Zpub, wallet descriptor --- class/multisig-cosigner.js | 67 +++++++++++++++++++++++++++ tests/unit/multisig-hd-wallet.test.js | 35 ++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/class/multisig-cosigner.js b/class/multisig-cosigner.js index 348a86611..6cab04f26 100644 --- a/class/multisig-cosigner.js +++ b/class/multisig-cosigner.js @@ -1,3 +1,6 @@ +import b58 from 'bs58check'; +const HDNode = require('bip32'); + export class MultisigCosigner { constructor(data) { this._data = data; @@ -7,6 +10,50 @@ export class MultisigCosigner { this._valid = false; this._cosigners = []; + // is it plain simple Zpub/Ypub/xpub? + if (data.startsWith('Zpub') && MultisigCosigner.isXpubValid(data)) { + this._fp = '00000000'; + this._xpub = data; + this._path = "m/48'/0'/0'/2'"; + this._valid = true; + this._cosigners = [true]; + return; + } else if (data.startsWith('Ypub') && MultisigCosigner.isXpubValid(data)) { + this._fp = '00000000'; + this._xpub = data; + this._path = "m/48'/0'/0'/1'"; + this._valid = true; + this._cosigners = [true]; + return; + } else if (data.startsWith('xpub') && MultisigCosigner.isXpubValid(data)) { + this._fp = '00000000'; + this._xpub = data; + this._path = "m/45'"; + this._valid = true; + this._cosigners = [true]; + return; + } + + // is it wallet descriptor? + if (data.startsWith('[')) { + const end = data.indexOf(']'); + const part = data.substr(1, end - 1).replace(/[h]/g, "'"); + this._fp = part.split('/')[0]; + const xpub = data.substr(end + 1); + + if (MultisigCosigner.isXpubValid(xpub)) { + this._xpub = xpub; + this._path = 'm'; + for (let c = 0; c < part.split('/').length; c++) { + if (c === 0) continue; + this._path += '/' + part.split('/')[c]; + } + this._cosigners = [true]; + this._valid = true; + return; + } + } + // is it cobo json? try { const json = JSON.parse(data); @@ -47,6 +94,26 @@ export class MultisigCosigner { } } + 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 isXpubValid(key) { + let xpub; + + try { + xpub = MultisigCosigner._zpubToXpub(key); + HDNode.fromBase58(xpub); + return true; + } catch (_) {} + + return false; + } + static exportToJson(xfp, xpub, path) { return JSON.stringify({ xfp: xfp, diff --git a/tests/unit/multisig-hd-wallet.test.js b/tests/unit/multisig-hd-wallet.test.js index 0d97b49ce..c36968f84 100644 --- a/tests/unit/multisig-hd-wallet.test.js +++ b/tests/unit/multisig-hd-wallet.test.js @@ -1582,6 +1582,41 @@ describe('multisig-cosigner', () => { assert.strictEqual(cosigner.howManyCosignersWeHave(), 1); }); + it('can parse plain Zpub', () => { + const cosigner = new MultisigCosigner(Zpub1); + assert.ok(cosigner.isValid()); + assert.strictEqual(cosigner.getFp(), '00000000'); + assert.strictEqual(cosigner.getXpub(), Zpub1); + assert.strictEqual(cosigner.getPath(), "m/48'/0'/0'/2'"); + assert.strictEqual(cosigner.howManyCosignersWeHave(), 1); + }); + + it('can parse wallet descriptor', () => { + let cosigner = new MultisigCosigner( + '[73c5da0a/48h/0h/0h/2h]Zpub74Jru6aftwwHxCUCWEvP6DgrfFsdA4U6ZRtQ5i8qJpMcC39yZGv3egBhQfV3MS9pZtH5z8iV5qWkJsK6ESs6mSzt4qvGhzJxPeeVS2e1zUG', + ); + assert.ok(cosigner.isValid()); + assert.strictEqual(cosigner.getFp(), '73c5da0a'); + assert.strictEqual( + cosigner.getXpub(), + 'Zpub74Jru6aftwwHxCUCWEvP6DgrfFsdA4U6ZRtQ5i8qJpMcC39yZGv3egBhQfV3MS9pZtH5z8iV5qWkJsK6ESs6mSzt4qvGhzJxPeeVS2e1zUG', + ); + assert.strictEqual(cosigner.getPath(), "m/48'/0'/0'/2'"); + assert.strictEqual(cosigner.howManyCosignersWeHave(), 1); + + cosigner = new MultisigCosigner( + '[73c5da0a/48h/0h/0h/2h]xpub6DkFAXWQ2dHxq2vatrt9qyA3bXYU4ToWQwCHbf5XB2mSTexcHZCeKS1VZYcPoBd5X8yVcbXFHJR9R8UCVpt82VX1VhR28mCyxUFL4r6KFrf', + ); + assert.ok(cosigner.isValid()); + assert.strictEqual(cosigner.getFp(), '73c5da0a'); + assert.strictEqual( + cosigner.getXpub(), + 'xpub6DkFAXWQ2dHxq2vatrt9qyA3bXYU4ToWQwCHbf5XB2mSTexcHZCeKS1VZYcPoBd5X8yVcbXFHJR9R8UCVpt82VX1VhR28mCyxUFL4r6KFrf', + ); + assert.strictEqual(cosigner.getPath(), "m/48'/0'/0'/2'"); + assert.strictEqual(cosigner.howManyCosignersWeHave(), 1); + }); + it('cant parse bs', () => { const cosigner = new MultisigCosigner('asdfasdgsqwrgqwegq'); assert.ok(!cosigner.isValid());