From d9c2edc3d3c425dbdb0dc77070f09b21432ddf34 Mon Sep 17 00:00:00 2001 From: overtorment Date: Wed, 28 Jun 2023 22:56:12 +0100 Subject: [PATCH] FIX: Scan of descriptor QR from Sparrow Wallet not working (closes #5539) --- blue_modules/ur/index.js | 5 ++++ class/wallets/multisig-hd-wallet.js | 25 ++++++++++++++++ package-lock.json | 16 +++++------ package.json | 2 +- tests/unit/multisig-hd-wallet.test.js | 41 ++++++++++++++++++++++++++- 5 files changed, 79 insertions(+), 10 deletions(-) diff --git a/blue_modules/ur/index.js b/blue_modules/ur/index.js index d17e99a06..6ae16df28 100644 --- a/blue_modules/ur/index.js +++ b/blue_modules/ur/index.js @@ -282,6 +282,11 @@ class BlueURDecoder extends URDecoder { return JSON.stringify(results); } + if (decoded.type === 'crypto-output') { + const output = CryptoOutput.fromCBOR(decoded.cbor); + return output.toString(); + } + throw new Error('unsupported data format'); } } diff --git a/class/wallets/multisig-hd-wallet.js b/class/wallets/multisig-hd-wallet.js index 08c356cda..e6be43f97 100644 --- a/class/wallets/multisig-hd-wallet.js +++ b/class/wallets/multisig-hd-wallet.js @@ -601,6 +601,31 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { this.addCosigner(xpub, hexFingerprint.toUpperCase(), path); } } + + if (this.getN() === 0) { + // handling a case when smth went wrong and we didnt parse any cosigners, probably because + // string is a bit non-standard, deesnt have chars like '[' + for (let c = 1; c < s3.length; c++) { + const hexFingerprint = s3[c].split('/')[0]; + let indexOfXpub = s3[c].indexOf('xpub'); + if (indexOfXpub === -1) { + // just for any case + indexOfXpub = s3[c].indexOf('ypub'); + } + if (indexOfXpub === -1) { + // just for any case + indexOfXpub = s3[c].indexOf('zpub'); + } + if (indexOfXpub === -1) { + throw new Error('Could not parse cosigner in a descriptor'); + } + + const xpub = s3[c].substring(indexOfXpub).replaceAll(')', ''); + const path = 'm' + s3[c].substring(hexFingerprint.length, indexOfXpub); + + this.addCosigner(xpub, hexFingerprint.toUpperCase(), path); + } + } } // is it caravan? diff --git a/package-lock.json b/package-lock.json index e4228efbf..87d4c7d78 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@babel/preset-env": "^7.20.0", "@bugsnag/react-native": "7.20.2", "@bugsnag/source-maps": "2.3.1", - "@keystonehq/bc-ur-registry": "0.5.5", + "@keystonehq/bc-ur-registry": "0.6.3", "@ngraveio/bc-ur": "1.1.6", "@noble/secp256k1": "1.6.3", "@react-native-async-storage/async-storage": "1.18.2", @@ -3713,9 +3713,9 @@ } }, "node_modules/@keystonehq/bc-ur-registry": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@keystonehq/bc-ur-registry/-/bc-ur-registry-0.5.5.tgz", - "integrity": "sha512-PoclPHf0OhpIKLfLwzymsu+CjkWf5ZKvaVjpkq3HUalcI4KW8wLk0m8qI2kBVv6F0BQ0ERPqW8OfjLTVqIgWLA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@keystonehq/bc-ur-registry/-/bc-ur-registry-0.6.3.tgz", + "integrity": "sha512-xwOYrRgqfV5wANpHjmO1ilB2bloY2kMxf8xiZ4CG0U5Zkfwa/9wUaqN60xFaG862w/6wnMOHvLDxDm47xNSDlw==", "dependencies": { "@ngraveio/bc-ur": "^1.1.5", "bs58check": "^2.1.2", @@ -25670,9 +25670,9 @@ } }, "@keystonehq/bc-ur-registry": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@keystonehq/bc-ur-registry/-/bc-ur-registry-0.5.5.tgz", - "integrity": "sha512-PoclPHf0OhpIKLfLwzymsu+CjkWf5ZKvaVjpkq3HUalcI4KW8wLk0m8qI2kBVv6F0BQ0ERPqW8OfjLTVqIgWLA==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@keystonehq/bc-ur-registry/-/bc-ur-registry-0.6.3.tgz", + "integrity": "sha512-xwOYrRgqfV5wANpHjmO1ilB2bloY2kMxf8xiZ4CG0U5Zkfwa/9wUaqN60xFaG862w/6wnMOHvLDxDm47xNSDlw==", "requires": { "@ngraveio/bc-ur": "^1.1.5", "bs58check": "^2.1.2", @@ -37439,7 +37439,7 @@ }, "react-native-draggable-flatlist": { "version": "git+ssh://git@github.com/BlueWallet/react-native-draggable-flatlist.git#ebfddc4877e8f65d5391a748db61b9cd030430ba", - "from": "react-native-draggable-flatlist@https://github.com/BlueWallet/react-native-draggable-flatlist#ebfddc4", + "from": "react-native-draggable-flatlist@github:BlueWallet/react-native-draggable-flatlist#ebfddc4", "requires": { "@babel/preset-typescript": "^7.17.12" } diff --git a/package.json b/package.json index ffb72d189..e4ba73cec 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "@babel/preset-env": "^7.20.0", "@bugsnag/react-native": "7.20.2", "@bugsnag/source-maps": "2.3.1", - "@keystonehq/bc-ur-registry": "0.5.5", + "@keystonehq/bc-ur-registry": "0.6.3", "@ngraveio/bc-ur": "1.1.6", "@noble/secp256k1": "1.6.3", "@react-native-async-storage/async-storage": "1.18.2", diff --git a/tests/unit/multisig-hd-wallet.test.js b/tests/unit/multisig-hd-wallet.test.js index 4c4e4e255..cc10c49a6 100644 --- a/tests/unit/multisig-hd-wallet.test.js +++ b/tests/unit/multisig-hd-wallet.test.js @@ -1,6 +1,6 @@ import assert from 'assert'; import { MultisigHDWallet } from '../../class/'; -import { decodeUR, encodeUR } from '../../blue_modules/ur'; +import { BlueURDecoder, decodeUR, encodeUR } from '../../blue_modules/ur'; import { MultisigCosigner } from '../../class/multisig-cosigner'; const bitcoin = require('bitcoinjs-lib'); const Base43 = require('../../blue_modules/base43'); @@ -1982,6 +1982,45 @@ describe('multisig-wallet (native segwit)', () => { assert.strictEqual(w.getPassphrase(2), w2.getPassphrase(2)); assert.strictEqual(w.getPassphrase(3), w2.getPassphrase(3)); }); + + it('can import descriptor from Sparrow', () => { + const payload = + 'UR:CRYPTO-OUTPUT/TAADMETAADMSOEADAOAOLSTAADDLOLAOWKAXHDCLAOCEBDFLNNTKJTIOJSFSURBNFXRPEEHKDLGYRTEMRPYTGYZOCASWENCYMKPAVWJKHYAAHDCXJEFTGSZOIMFEYNDYHYZEJTBAMSJEHLDSRDDIYLSRFYTSZTKNRNYLRNDPAMTLDPZCAHTAADEHOEADAEAOAEAMTAADDYOTADLOCSDYYKAEYKAEYKAOYKAOCYUOHFJPKOAXAAAYCYCSYASAVDTAADDLOLAOWKAXHDCLAXMSZTWZDIGERYDKFSFWTYDPFNDKLNAYSWTTMUHYZTOXHSETPEWSFXPEAYWLJSDEMTAAHDCXSPLTSTDPNTLESANSUTTLPRPFHNVSPFCNMHESOYGASTLRPYVAATNNDKFYHLQZPKLEAHTAADEHOEADAEAOAEAMTAADDYOTADLOCSDYYKAEYKAEYKAOYKAOCYWZFEPLETAXAAAYCYCPCKRENBTAADDLOLAOWKAXHDCLAOLSFWYKYLKTFHJLPYEMGLCEDPFNSNRDDSRFASEOZTGWIALFLUIYDNFXHGVESFEMMEAAHDCXHTZETLJNKPHHAYLSCXWPNDSWPSTPGTEOJKKGHDAELSKPNNBKBSYAWZJTFWNNBDKTAHTAADEHOEADAEAOAEAMTAADDYOTADLOCSDYYKAEYKAEYKAOYKAOCYSKTPJPMSAXAAAYCYCEBKWLAMTDWZGRZE\n'; + const decoder = new BlueURDecoder(); + decoder.receivePart(payload); + console.log(decoder.isComplete()); + + const data = decoder.toString(); + + const w = new MultisigHDWallet(); + w.setSecret(data); + + assert.strictEqual(w.getM(), 2); + assert.strictEqual(w.getN(), 3); + assert.strictEqual(w.getFingerprint(1), 'DC567276'); + assert.strictEqual(w.getFingerprint(2), 'F245AE38'); + assert.strictEqual(w.getFingerprint(3), 'C5D87297'); + assert.strictEqual(w.isNativeSegwit(), true); + assert.strictEqual(w.getCustomDerivationPathForCosigner(1), "m/48'/0'/0'/2'"); + assert.strictEqual(w.getCustomDerivationPathForCosigner(2), "m/48'/0'/0'/2'"); + assert.strictEqual(w.getCustomDerivationPathForCosigner(3), "m/48'/0'/0'/2'"); + assert.strictEqual(w.getFormat(), 'p2wsh'); + + assert.strictEqual( + w.getCosigner(1), + 'xpub6DiYrfRwNnjeX4vHsWMajJVFKrbEEnu8gAW9vDuQzgTWEsEHE16sGWeXXUV1LBWQE1yCTmeprSNcqZ3W74hqVdgDbtYHUv3eM4W2TEUhpan', + ); + assert.strictEqual( + w.getCosigner(2), + 'xpub6DnT4E1fT8VxuAZW29avMjr5i99aYTHBp9d7fiLnpL5t4JEprQqPMbTw7k7rh5tZZ2F5g8PJpssqrZoebzBChaiJrmEvWwUTEMAbHsY39Ge', + ); + assert.strictEqual( + w.getCosigner(3), + 'xpub6DjrnfAyuonMaboEb3ZQZzhQ2ZEgaKV2r64BFmqymZqJqviLTe1JzMr2X2RfQF892RH7MyYUbcy77R7pPu1P71xoj8cDUMNhAMGYzKR4noZ', + ); + + assert.strictEqual(w._getExternalAddressByIndex(0), 'bc1q4taqq6q6l8fvguva6ftvrz3qgdjy6p3w2s0ds0nl6qrjw7t0hfhqgrqcwd'); + }); }); describe('multisig-cosigner', () => {