diff --git a/Navigation.js b/Navigation.js index c41754c8c..21ab9b33c 100644 --- a/Navigation.js +++ b/Navigation.js @@ -341,7 +341,7 @@ const InitRoot = () => ( component={UnlockWithScreenRoot} options={{ headerShown: false, animationEnabled: false }} /> - + ); diff --git a/class/wallets/multisig-hd-wallet.js b/class/wallets/multisig-hd-wallet.js index 3297df82f..76831e898 100644 --- a/class/wallets/multisig-hd-wallet.js +++ b/class/wallets/multisig-hd-wallet.js @@ -853,11 +853,16 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { psbt.addOutput(outputData); }); + let signaturesMade = 0; if (!skipSigning) { for (let cc = 0; cc < c; cc++) { for (const cosigner of this._cosigners) { if (!MultisigHDWallet.isXpubString(cosigner)) { // ok this is a mnemonic, lets try to sign + if (signaturesMade >= this.getM()) { + // dont sign more than we need, otherwise there will be "Too many signatures" error + continue; + } let seed = bip39.mnemonicToSeed(cosigner); if (cosigner.startsWith(ELECTRUM_SEED_PREFIX)) { seed = MultisigHDWallet.convertElectrumMnemonicToSeed(cosigner); @@ -865,6 +870,7 @@ export class MultisigHDWallet extends AbstractHDElectrumWallet { const hdRoot = bitcoin.bip32.fromSeed(seed); psbt.signInputHD(cc, hdRoot); + signaturesMade++; } } } diff --git a/package-lock.json b/package-lock.json index 48c75242c..2a9a146bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19061,9 +19061,9 @@ "integrity": "sha512-LAUFsgcVHHJkU6AYjEDi9e6fJfahrep4IBXPNREKzG9uvHhjXno0Lv8TKNRMzrx6wGntpM/1bxs5pSTstpKllg==" }, "react-native-modal": { - "version": "11.5.6", - "resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-11.5.6.tgz", - "integrity": "sha512-APGNfbvgC4hXbJqcSADu79GLoMKIHUmgR3fDQ7rCGZNBypkStSP8imZ4PKK/OzIZZfjGU9aP49jhMgGbhY9KHA==", + "version": "11.6.1", + "resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-11.6.1.tgz", + "integrity": "sha512-Xsd79nuBvbLOYwpRuur4btwJFQ3WkV9zp5LbAREidUtjl/Ajlp4QbNBx888FOPjo+EpOO3+U0riCRqs1dkdIzQ==", "requires": { "prop-types": "^15.6.2", "react-native-animatable": "1.3.3" diff --git a/package.json b/package.json index e9db09c4f..34ad3e36a 100644 --- a/package.json +++ b/package.json @@ -135,7 +135,7 @@ "react-native-level-fs": "3.0.1", "react-native-linear-gradient": "2.5.6", "react-native-localize": "1.4.2", - "react-native-modal": "11.5.6", + "react-native-modal": "11.6.1", "react-native-navigation-bar-color": "git+https://github.com/BlueWallet/react-native-navigation-bar-color.git#34e44b8f44e442133de9d35c35f2679d40982804", "react-native-obscure": "1.2.1", "react-native-passcode-auth": "git+https://github.com/BlueWallet/react-native-passcode-auth.git#a2ff977ba92b36f8d0a5567f59c05cc608e8bd12", diff --git a/screen/settings/about.js b/screen/settings/about.js index 41df6177b..cd39c69e9 100644 --- a/screen/settings/about.js +++ b/screen/settings/about.js @@ -54,14 +54,14 @@ const About = () => { paddingTop: 0, borderRadius: 8, }, - buttonLink :{ + buttonLink: { backgroundColor: colors.lightButton, borderRadius: 12, - justifyContent: 'center', + justifyContent: 'center', padding: 8, - flexDirection: 'row', + flexDirection: 'row', }, - textLink :{ + textLink: { color: colors.foregroundColor, marginLeft: 8, fontWeight: '600', @@ -160,10 +160,7 @@ const About = () => { Electrum server - + {loc.settings.about_sm_github} diff --git a/tests/setup.js b/tests/setup.js index b7b059a97..05ce3c514 100644 --- a/tests/setup.js +++ b/tests/setup.js @@ -1,9 +1,5 @@ /* global jest */ -import mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock'; - -jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage); - jest.mock('react-native-watch-connectivity', () => { return { getIsWatchAppInstalled: jest.fn(() => Promise.resolve(false)), @@ -94,3 +90,20 @@ jest.mock('react-native-gesture-handler', () => jest.requireActual('react-native jest.mock('react-native-document-picker', () => ({})); jest.mock('react-native-haptic-feedback', () => ({})); + +const realmInstanceMock = { + close: function () {}, + objects: function () { + const wallets = { + filtered: function () { + return []; + }, + }; + return wallets; + }, +}; +jest.mock('realm', () => { + return { + open: jest.fn(() => realmInstanceMock), + }; +}); diff --git a/tests/unit/multisig-hd-wallet.test.js b/tests/unit/multisig-hd-wallet.test.js index 7b9cf75f9..eeb969738 100644 --- a/tests/unit/multisig-hd-wallet.test.js +++ b/tests/unit/multisig-hd-wallet.test.js @@ -1568,6 +1568,66 @@ describe('multisig-wallet (native segwit)', () => { assert.strictEqual(w.getN(), 1); assert.strictEqual(w.getM(), 2); }); + + it('can sign valid tx if we have more keys than quorum ("Too many signatures" error)', async () => { + const w = new MultisigHDWallet(); + w.setSecret( + '# BlueWallet Multisig setup file\n' + + '# this file may contain private information\n' + + '#\n' + + 'Name: Multisig Vault\n' + + 'Policy: 3 of 6\n' + + "Derivation: m/48'/0'/0'/2'\n" + + 'Format: P2WSH\n' + + '\n' + + 'seed: start local figure rose pony artist voice agent pyramid still spot walk\n' + + '# warning! sensitive information, do not disclose ^^^ \n' + + '\n' + + 'seed: empty fall vanish sheriff vibrant diary route lock purity noodle ripple clutch\n' + + '# warning! sensitive information, do not disclose ^^^ \n' + + '\n' + + 'seed: else heart suggest proof travel announce reason priority trick bargain author duty\n' + + '# warning! sensitive information, do not disclose ^^^ \n' + + '\n' + + 'seed: craft response kitchen column feed fitness pill loyal capital together usage either\n' + + '# warning! sensitive information, do not disclose ^^^ \n' + + '\n' + + 'seed: trigger zebra image engine inhale employ floor soul glimpse version extra pizza\n' + + '# warning! sensitive information, do not disclose ^^^ \n' + + '\n' + + 'seed: thank post talent polar hire model trophy elevator wide green hungry gossip\n' + + '# warning! sensitive information, do not disclose ^^^', + ); + + const utxos = [ + { + height: 662352, + value: 100000, + address: 'bc1qlkh0zgq5ypcdfs9rdvrucra96c5gmjgaufm0au8cglkkrah29nesrkvewg', + txId: 'e112e3b109aff5fe76d4fde90bd3c2df58bfb250280a4404421fff42d6801fd2', + vout: 0, + txid: 'e112e3b109aff5fe76d4fde90bd3c2df58bfb250280a4404421fff42d6801fd2', + amount: 100000, + wif: false, + confirmations: 1, + txhex: + '020000000001020d0f713ba314566ea9b7e7d64eb8538dd0a88826377945464e9bb25eed61d665010000000000000080f570a2bb8faff02b848a4a5b2d334324a1ccc6cebf1c1cc27e231316f579e66d01000000000000008002a086010000000000220020fdaef120142070d4c0a36b07cc0fa5d6288dc91de276fef0f847ed61f6ea2cf3d6eb060000000000160014696154a1ed38813c4c45b58ece291c1a8d9cd7d102483045022100d02ef858d129ba50aeee126e41e9cca5fa58232def7934d6705747e81bcd61a402206d2565c336cd32cb128ae9e80af60d610154af0d4e77cb60e015dead349b368b012103471950f9952608d9db6d3698b731d89387b7b55026ae020919ee7da7a2a4866d0247304402204088a68fc4654c0cedb724f6e8fe3820845d5e7184b0363806ea77f8a739f58702202bf7b3b20d6d6db28617e8edd6d0ea3870ab7e3bc62307ad77fa9717a3689bcb01210371d2366d9fc32c5a83bb78d7ced38d5e318a96dc43a18074742e567defe4585d00000000', + }, + ]; + + const { psbt, tx } = w.createTransaction( + utxos, + [{ address: '13HaCAB4jf7FYSZexJxoczyDDnutzZigjS' }], // sendMax + 1, + w._getInternalAddressByIndex(0), + ); + + assert.ok(tx); + assert.ok(psbt); + + assert.strictEqual(psbt.data.inputs.length, 1); + assert.strictEqual(psbt.data.outputs.length, 1); + }); }); describe('multisig-cosigner', () => {