From afa9fad9d2b2065c11dd47e792423e090a3d8b49 Mon Sep 17 00:00:00 2001 From: Brendon Duncan Date: Mon, 3 Sep 2018 14:12:12 +1200 Subject: [PATCH] Now parses wallet import data. --- src/js/services/bitcoin-uri.service.js | 70 +++++++++++++++++++-- src/js/services/bitcoin-uri.service.spec.js | 35 +++++++++++ 2 files changed, 100 insertions(+), 5 deletions(-) diff --git a/src/js/services/bitcoin-uri.service.js b/src/js/services/bitcoin-uri.service.js index c20c98b93..3da84f3da 100644 --- a/src/js/services/bitcoin-uri.service.js +++ b/src/js/services/bitcoin-uri.service.js @@ -84,13 +84,53 @@ return result; } - function infoFromImport(data) { + function infoFromWalletImportText(data) { var split = data.split('|'); // Copay seems to use extra parameter for coin. if (split.length < 5 || split.length > 6) { return null; } + var type = parseInt(split[0], 10); + if (isNaN(type)) { + return null; + } + + var data = split[1]; + var network = split[2]; + if (!(network === 'livenet' || network === 'testnet')) { + return null; + } + var isTestnet = network === 'testnet'; + + var derivationPath = split[3]; + if (!/^m\/\d+'\/\d+'\/\d+'$/.test(derivationPath)) { + return null; + } + + var hasPassphraseText = split[4]; + if (!(hasPassphraseText === 'true' || hasPassphraseText === 'false')) { + return null; + } + var hasPassphrase = hasPassphraseText === 'true'; + + var coin; // Intentionally undefined as may not be present + if (split.length > 5) { + var coinText = split[5]; + if (!(coinText === 'bch' || coinText === 'btc')) { + return null; + } + coin = coinText; + } + + return { + type: type, + data: data, + isTestnet: isTestnet, + derivationPath: derivationPath, + hasPassphrase: hasPassphrase, + coin: coin + }; } /* @@ -105,6 +145,12 @@ bareUrl: '', coin: '', copayInvitation: '', + import: { // testnet info in root, coin info in root if available + data: '', + derivationPath: '', + hasPassphrase: false, + type: 1, + }, isValid: false, label: '', message: '', @@ -173,16 +219,19 @@ addressAndParams = colonSplit[1].trim(); console.log('No prefix.'); - } else if (/^https?$/.test(colonSplit[1])) { + } else if (/^https?$/.test(colonSplit[1])) { // Plain URL addressAndParams = trimmed; + } else if (colonSplit[2].indexOf('|') == 0) { // Import + addressAndParams = trimmed } else { - // Something with a colon in the middle that we don't recognise + // Something we don't recognise return parsed; } // Remove erroneous leading slashes - var leadingSlashes = /^\/*([^\/]+(?:.*))$/.exec(addressAndParams); + //var leadingSlashes = /^\/*([^\/]+(?:.*))$/.exec(addressAndParams); + var leadingSlashes = /^\/*(.*)$/.exec(addressAndParams); if (!leadingSlashes) { return parsed; } @@ -262,7 +311,6 @@ var copayInvitationRe = /^[0-9A-HJ-NP-Za-km-z]{70,80}$/; //var legacyRe = /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/; //var legacyTestnetRe = /^[mn][a-km-zA-HJ-NP-Z1-9]{25,34}$/; - var importRe = /^[123]|$/; var privateKeyEncryptedRe = /^6P[1-9A-HJ-NP-Za-km-z]{56}$/; var privateKeyForUncompressedPublicKeyRe = /^5[1-9A-HJ-NP-Za-km-z]{50}$/; var privateKeyForUncompressedPublicKeyTestnetRe = /^9[1-9A-HJ-NP-Za-km-z]{50}$/; @@ -273,6 +321,7 @@ var bitpayAddrMainnet = bitpayAddrOnMainnet(address); var cashAddrTestnet = cashAddrOnTestnet(addressLowerCase); var cashAddrMainnet = cashAddrOnMainnet(addressLowerCase); + var importInfo = infoFromWalletImportText(address); var privateKey = ''; if (parsed.isTestnet && cashAddrTestnet) { @@ -342,6 +391,17 @@ } else if (urlRe.test(address)) { parsed.bareUrl = trimmed; parsed.isValid = true; + + } else if (importInfo) { + parsed.import = { + type: importInfo.type, + data: importInfo.data, + derivationPath: importInfo.derivationPath, + hasPassphrase: importInfo.hasPassphrase + }; + parsed.coin = importInfo.coin; + parsed.isTestnet = importInfo.isTestnet; + parsed.isValid = true; } } else { diff --git a/src/js/services/bitcoin-uri.service.spec.js b/src/js/services/bitcoin-uri.service.spec.js index 032255373..2ddbd0d2e 100644 --- a/src/js/services/bitcoin-uri.service.spec.js +++ b/src/js/services/bitcoin-uri.service.spec.js @@ -270,6 +270,41 @@ describe('bitcoinUriService', function() { expect(parsed.copayInvitation).toBe('PD5B7rEEj72st9d5nFszyuKxJP6FAGS7idVC2SMqiMxUcWVd8JifZDJw1UgjUctxefUFE3Sz6qLbch'); }); + + it ('import BCH wallet no password', function() { + var parsed = bitcoinUriService.parse("1|suggest route obvious broccoli good position hidden tone history around final lobster|livenet|m/44'/0'/0'|false"); + + expect(parsed.isValid).toBe(true); + expect(parsed.import.type).toBe(1); + expect(parsed.import.data).toBe('suggest route obvious broccoli good position hidden tone history around final lobster'); + expect(parsed.isTestnet).toBe(false); + expect(parsed.import.derivationPath).toBe("m/44'/0'/0'"); + expect(parsed.import.hasPassphrase).toBe(false); + }); + + it ('import BCH wallet with passphrase', function() { + var parsed = bitcoinUriService.parse("1|fringe hazard all hobby trap myth fire stand sock empty soon east|livenet|m/44'/0'/0'|true"); + + expect(parsed.isValid).toBe(true); + expect(parsed.import.type).toBe(1); + expect(parsed.import.data).toBe('fringe hazard all hobby trap myth fire stand sock empty soon east'); + expect(parsed.isTestnet).toBe(false); + expect(parsed.import.derivationPath).toBe("m/44'/0'/0'"); + expect(parsed.import.hasPassphrase).toBe(true); + }); + + it ('import BTC wallet testnet', function() { + // From copay + var parsed = bitcoinUriService.parse("1|cat wealth column firm wet sauce tornado era feature monster click eyebrow|testnet|m/44'/1'/0'|false|btc"); + + expect(parsed.isValid).toBe(true); + expect(parsed.import.type).toBe(1); + expect(parsed.import.data).toBe('cat wealth column firm wet sauce tornado era feature monster click eyebrow'); + expect(parsed.isTestnet).toBe(true); + expect(parsed.import.derivationPath).toBe("m/44'/1'/0'"); + expect(parsed.import.hasPassphrase).toBe(false); + }); + // Invalid addresses from https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md it('invalid cashAddr style 1', function() { var parsed = bitcoinUriService.parse('prefix:x64nx6hz');