import BIP32Factory from 'bip32'; import b58 from 'bs58check'; import ecc from '../blue_modules/noble_ecc'; import { MultisigHDWallet } from './wallets/multisig-hd-wallet'; const bip32 = BIP32Factory(ecc); export class MultisigCosigner { constructor(data) { this._data = data; this._fp = false; this._xpub = false; this._path = false; 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); if (json.xfp && json.xpub && json.path) { this._fp = json.xfp; this._xpub = json.xpub; this._path = json.path; this._cosigners = [true]; this._valid = true; // a bit more logic here: according to the formal BIP48 spec, this xpub field _can_ start with 'xpub', but // the actual type of segwit can be inferred from the path if ( this._xpub.startsWith('xpub') && [MultisigHDWallet.PATH_NATIVE_SEGWIT, MultisigHDWallet.PATH_WRAPPED_SEGWIT].includes(this._path) ) { const w = new MultisigHDWallet(); w.addCosigner(this._xpub, '00000000', this._path); w.setDerivationPath(this._path); this._xpub = w.convertXpubToMultisignatureXpub(this._xpub); } return; } } catch (_) { this._valid = false; } // is it cobo crypto-account URv2 ? try { const json = JSON.parse(data); if (json && json.ExtPubKey && json.MasterFingerprint && json.AccountKeyPath) { this._fp = json.MasterFingerprint; this._xpub = json.ExtPubKey; this._path = json.AccountKeyPath; this._cosigners = [true]; this._valid = true; return; } } catch (_) { this._valid = false; } // is it coldcard json? try { const json = JSON.parse(data); if (json.p2sh && json.p2sh_deriv && json.xfp) { const cc = new MultisigCosigner(MultisigCosigner.exportToJson(json.xfp, json.p2sh, json.p2sh_deriv)); this._valid = true; this._cosigners.push(cc); } if (json.p2wsh_p2sh && json.p2wsh_p2sh_deriv && json.xfp) { const cc = new MultisigCosigner(MultisigCosigner.exportToJson(json.xfp, json.p2wsh_p2sh, json.p2wsh_p2sh_deriv)); this._valid = true; this._cosigners.push(cc); } if (json.p2wsh && json.p2wsh_deriv && json.xfp) { const cc = new MultisigCosigner(MultisigCosigner.exportToJson(json.xfp, json.p2wsh, json.p2wsh_deriv)); this._valid = true; this._cosigners.push(cc); } } catch (_) { this._valid = false; } } static isXpubValid(key) { let xpub; try { const tempWallet = new MultisigHDWallet(); xpub = tempWallet._zpubToXpub(key); bip32.fromBase58(xpub); return true; } catch (_) {} return false; } static exportToJson(xfp, xpub, path) { return JSON.stringify({ xfp, xpub, path, }); } isValid() { return this._valid; } getFp() { return this._fp; } getXpub() { return this._xpub; } getPath() { return this._path; } howManyCosignersWeHave() { return this._cosigners.length; } /** * * @returns {Array.} */ getAllCosigners() { return this._cosigners; } isNativeSegwit() { return this.getXpub().startsWith('Zpub'); } isWrappedSegwit() { return this.getXpub().startsWith('Ypub'); } isLegacy() { return this.getXpub().startsWith('xpub'); } getChainCodeHex() { let data = b58.decode(this.getXpub()); data = data.slice(4); data = data.slice(1); data = data.slice(4); data = data.slice(4, 36); return data.toString('hex'); } getKeyHex() { let data = b58.decode(this.getXpub()); data = data.slice(4); data = data.slice(1); data = data.slice(4); data = data.slice(36); return data.toString('hex'); } getParentFingerprintHex() { let data = b58.decode(this.getXpub()); data = data.slice(4); data = data.slice(1); data = data.slice(0, 4); return data.toString('hex'); } getDepthNumber() { let data = b58.decode(this.getXpub()); data = data.slice(4, 5); return data.readInt8(); } }