diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 748c4f04c..cc78b6e79 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -157,6 +157,8 @@ release_electron_linux_windows: stage: releasing only: - tags + dependencies: + - release_binary_windows before_script: - python3 -V # Print out python version for debugging - apt update diff --git a/pyinstaller/electron/main.js b/pyinstaller/electron/main.js index f405f47bc..021174bf3 100644 --- a/pyinstaller/electron/main.js +++ b/pyinstaller/electron/main.js @@ -348,10 +348,8 @@ function startSpecterd(specterdPath) { if (specterdArgs == null) { specterdArgs = [] } - let specterdExtraArgs = appSettings.specterdCLIArgs.split('--') - specterdExtraArgs = specterdExtraArgs.filter(Boolean) - specterdExtraArgs.forEach((arg, index) => specterdExtraArgs[index] = '--' + arg.trim()) - + let specterdExtraArgs = appSettings.specterdCLIArgs.split(' ') + specterdExtraArgs.forEach((arg, index) => specterdExtraArgs[index] = arg.trim()) specterdArgs = specterdArgs.concat(specterdExtraArgs) } diff --git a/src/cryptoadvance/specter/commands/psbt_creator.py b/src/cryptoadvance/specter/commands/psbt_creator.py index 2bc3f8bfd..569fb8e11 100644 --- a/src/cryptoadvance/specter/commands/psbt_creator.py +++ b/src/cryptoadvance/specter/commands/psbt_creator.py @@ -7,7 +7,7 @@ import requests from cryptoadvance.specter.specter_error import SpecterError from cryptoadvance.specter.util.common import str2bool -from ..helpers import is_testnet, normalize_address +from ..helpers import normalize_address from ..util.descriptor import AddChecksum, Descriptor logger = logging.getLogger(__name__) diff --git a/src/cryptoadvance/specter/internal_node.py b/src/cryptoadvance/specter/internal_node.py index b1bb1dbb5..18dfdab21 100644 --- a/src/cryptoadvance/specter/internal_node.py +++ b/src/cryptoadvance/specter/internal_node.py @@ -4,7 +4,6 @@ import psutil from pathlib import Path -from .helpers import is_testnet from .specter_error import SpecterError, ExtProcTimeoutException from .rpc import ( get_default_datadir, diff --git a/src/cryptoadvance/specter/managers/service_manager/service_manager.py b/src/cryptoadvance/specter/managers/service_manager/service_manager.py index 1c97c11b7..0bbd1d802 100644 --- a/src/cryptoadvance/specter/managers/service_manager/service_manager.py +++ b/src/cryptoadvance/specter/managers/service_manager/service_manager.py @@ -429,16 +429,37 @@ class ServiceManager: @classmethod def get_service_packages(cls): - """returns a list of strings containing the service-classes (+ controller/config-classes) + """returns a list of strings containing the service-classes (+ controller +config-classes +devices) This is used for hiddenimports in pyinstaller """ arr = get_subclasses_for_clazz(Service) + logger.info(f"initial arr: {arr}") arr.extend( get_classlist_of_type_clazz_from_modulelist( Service, ProductionConfig.EXTENSION_LIST ) ) + logger.info(f"After extending: {arr}") + # Before we transform the arr into an array of strings, we iterate through all services to discover + # the devices which might be specified in there + devices_arr = [] + for clazz in arr: + if hasattr(clazz, "devices"): + logger.debug("class {clazz} has devices: {clazz.devices}") + for device in clazz.devices: + try: + import_module(device) + devices_arr.append(device) + except ModuleNotFoundError as e: + pass + + # Transform into array of strings arr = [clazz.__module__ for clazz in arr] + + # Add the devices + arr.extend(devices_arr) + logger.debug(f"After transforming + devices: {arr}") + # Controller-Packagages from the services are not imported via the service but via the baseclass # Therefore hiddenimport don't find them. We have to do it here. cont_arr = [ diff --git a/src/cryptoadvance/specter/node.py b/src/cryptoadvance/specter/node.py index 6e1bf2b96..939b78e33 100644 --- a/src/cryptoadvance/specter/node.py +++ b/src/cryptoadvance/specter/node.py @@ -110,6 +110,10 @@ class AbstractNode(PersistentObject): def network_parameters(self): return get_network(self.chain) + @property + def is_testnet(self): + return is_testnet(self.chain) + @property def is_running(self): if self.network_info["version"] == 999999: @@ -582,10 +586,6 @@ class Node(AbstractNode): def chain(self): return self.info["chain"] - @property - def is_testnet(self): - return is_testnet(self.chain) - @property def asset_labels(self): if not self.is_liquid: diff --git a/src/cryptoadvance/specter/specter.py b/src/cryptoadvance/specter/specter.py index 2010c8fce..265281fe2 100644 --- a/src/cryptoadvance/specter/specter.py +++ b/src/cryptoadvance/specter/specter.py @@ -25,7 +25,7 @@ from cryptoadvance.specter.services.service_encrypted_storage import ( ServiceUnencryptedStorageManager, ) -from .helpers import clean_psbt, deep_update, is_liquid, is_testnet, get_asset_label +from .helpers import clean_psbt, deep_update, is_liquid, get_asset_label from .internal_node import InternalNode from .liquid.rpc import LiquidRPC from .managers.config_manager import ConfigManager diff --git a/src/cryptoadvance/specter/util/wallet_importer.py b/src/cryptoadvance/specter/util/wallet_importer.py index 812dd57d9..098e80d8f 100644 --- a/src/cryptoadvance/specter/util/wallet_importer.py +++ b/src/cryptoadvance/specter/util/wallet_importer.py @@ -8,6 +8,8 @@ from embit.descriptor.arguments import AllowedDerivation from embit.liquid.descriptor import LDescriptor from flask_babel import lazy_gettext as _ +from cryptoadvance.specter.device import Device + from ..key import Key from ..managers.wallet_manager import WalletManager from ..server_endpoints import flash @@ -58,8 +60,36 @@ class WalletImporter: self.unknown_cosigners, self.unknown_cosigners_types, ) = self.parse_signers(device_manager.devices, self.cosigners_types) + try: + self.check_chain(specter.node) + except Exception as e: + logger.exception(e) + raise SpecterError(f"Invalid chain: {e}") self.wallet_type = "multisig" if self.descriptor.is_basic_multisig else "simple" + def check_chain(self, node): + + cosigner: Device + for cosigner in self.cosigners: + + key_chain_list = [key.is_testnet for key in cosigner.keys] + logger.debug(key_chain_list) + if node.is_testnet not in key_chain_list: + raise SpecterError( + f"The device {cosigner} does not have any key for the chain {node.chain}!" + ) + for key, label in self.unknown_cosigners: + if key.is_testnet != node.is_testnet: + raise SpecterError( + f"The device {label} has at least one key for the chain {key.metadata['chain'] } whereas your node is on the chain: {node.chain}!" + ) + key: Key + for key in self.keys: + if key.is_testnet != node.is_testnet: + raise SpecterError( + f"The key {key} belongs to the chain {key.metadata['chain'] } but your node is on the chain {node.chain}!" + ) + def check_descriptor(self): # Sparrow fix: if all keys have None as allowed derivation - set allowed derivation to [0, None] if all( @@ -86,6 +116,15 @@ class WalletImporter: ) def parse_signers(self, devices, cosigners_types): + """returns: + * keys: + * keys which are already existing in the device_manager + * keys:[] + # cosigners: + * [] + # unknown_cosigners:[(, 'Signer - K'), (, 'Signer - Tired no WP')] + + """ keys = [] cosigners = [] unknown_cosigners = [] @@ -119,7 +158,11 @@ class WalletImporter: unknown_cosigners_types.append("other") else: unknown_cosigners_types.append(cosigners_types[i]["type"]) - + logger.debug("parse_signers returning:") + logger.debug(f"keys:{keys}") + logger.debug(f"cosigners:{cosigners}") + logger.debug(f"unknown_cosigners:{unknown_cosigners}") + logger.debug(f"unknown_cosigners_types:{unknown_cosigners_types}") return (keys, cosigners, unknown_cosigners, unknown_cosigners_types) def create_nonexisting_signers(self, device_manager, request_form): diff --git a/src/cryptoadvance/specterext/electrum/devices/__init__.py b/src/cryptoadvance/specterext/electrum/devices/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_managers_service.py b/tests/test_managers_service.py index 8fb354652..4f0e6cf08 100644 --- a/tests/test_managers_service.py +++ b/tests/test_managers_service.py @@ -56,9 +56,12 @@ def test_ServiceManager_get_service_x_dirs(caplog): os.chdir("../") -def test_ServiceManager_get_service_packages(): +def test_ServiceManager_get_service_packages(caplog): + caplog.set_level(logging.DEBUG) packages = ServiceManager.get_service_packages() assert "cryptoadvance.specterext.swan.service" in packages + assert "cryptoadvance.specterext.electrum.service" in packages + assert "cryptoadvance.specterext.electrum.devices.electrum" in packages @pytest.fixture diff --git a/tests/test_util_wallet_importer.py b/tests/test_util_wallet_importer.py index ed5c52248..88cb01461 100644 --- a/tests/test_util_wallet_importer.py +++ b/tests/test_util_wallet_importer.py @@ -1,36 +1,89 @@ +import json import logging import pytest import time from cryptoadvance.specter.key import Key from cryptoadvance.specter.managers.device_manager import DeviceManager +from cryptoadvance.specter.specter_error import SpecterError from cryptoadvance.specter.user import User from cryptoadvance.specter.util.descriptor import Descriptor from cryptoadvance.specter.util.wallet_importer import WalletImporter from mock import MagicMock, call, patch +# coldcard, trezor import +wallet_coldcardtrezor_json = """ + {"label": "MyTestMultisig", + "blockheight": 0, + "descriptor": + "wsh(sortedmulti(1,[fb7c1f11/48h/1h/0h/2h]tpubDExnGppazLhZPNadP8Q5Vgee2QcvbyAf9GvGaEY7ALVJREaG2vdTqv1MHRoDtPaYP3y1DGVx7wrKKhsLhs26GY263uE6Wi3qNbi71AHZ6p7/0/*,[1ef4e492/48h/1h/0h/2h]tpubDFiVCZzdarbyk8kE65tjRhHCambEo8iTx4xkXL8b33BKZj66HWsDnUb3rg4GZz6Mwm6vTNyzRCjYtiScCQJ77ENedb2deDDtcoNQXiUouJQ/0/*))#s0jemlck", + "devices": + [ + {"type": "coldcard", + "label": "MyColdcard"}, + {"type": "trezor", + "label": "MyTestTrezor"} + ] + } +""" + +wallet_ledger_json = """ + { + "address": "bcrt1q66f7yuxegnzpv9jv5pp6pqfy7smq4ydq643d4e", + "address_index": 7, + "address_type": "bech32", + "alias": "myledger_2", + "blockheight": 8244, + "change_address": "bcrt1qk087hncum850z0ghpss2lm49kcfu6lppngdeem", + "change_descriptor": "wpkh([309c1ac3/84h/1h/0h]tpubDCRGLu8Kjg9dKLUqrVCBZA9TSUkga56X3mnfqWqSVWrooavQdCdYzUdY7peGk7KW7H6gmdJobm5rXr1NQmoGhuPf4aqD3GhgtggYdwMwRom/1/*)#qqulrlpr", + "change_index": 0, + "change_keypool": 300, + "description": "Single (Segwit)", + "devices": [ + "myledger" + ], + "keypool": 300, + "keys": [ + { + "derivation": "m/84h/1h/0h", + "fingerprint": "309c1ac3", + "original": "vpub5YP7DKxoj37DLwukjadqw4d5mRN8hScTfv9vvAzXPbM9x6dF3fTLbueRqNeKDo4RW6LAf6bp9LYi56HdaX81DzaAGJh4BvQUmGxr6peWDsP", + "purpose": "#0 Single Sig (Segwit)", + "type": "wpkh", + "xpub": "tpubDCRGLu8Kjg9dKLUqrVCBZA9TSUkga56X3mnfqWqSVWrooavQdCdYzUdY7peGk7KW7H6gmdJobm5rXr1NQmoGhuPf4aqD3GhgtggYdwMwRom" + } + ], + "labels": {}, + "name": "MyLedger", + "recv_descriptor": "wpkh([309c1ac3/84h/1h/0h]tpubDCRGLu8Kjg9dKLUqrVCBZA9TSUkga56X3mnfqWqSVWrooavQdCdYzUdY7peGk7KW7H6gmdJobm5rXr1NQmoGhuPf4aqD3GhgtggYdwMwRom/0/*)#35e7723m", + "sigs_required": 1 + } + """ + + +def test_WalletImporter_parse_wallet_data_import(): + ( + wallet_name, + recv_descriptor, + cosigners_types, + ) = WalletImporter.parse_wallet_data_import(json.loads(wallet_coldcardtrezor_json)) + assert wallet_name == "MyTestMultisig" + assert recv_descriptor.startswith("wsh(sortedmulti(1,[fb7c1") + assert cosigners_types[0]["type"] == "coldcard" + assert cosigners_types[0]["label"] == "MyColdcard" + assert cosigners_types[1]["type"] == "trezor" + assert cosigners_types[1]["label"] == "MyTestTrezor" + @patch("cryptoadvance.specter.util.wallet_importer.flash", print) @patch("cryptoadvance.specter.util.wallet_importer._", lambda x: x) def test_WalletImporter_unit(): specter_mock = MagicMock() - specter_mock.chain = "regtest" + specter_mock.node.chain = "regtest" + specter_mock.node.is_testnet = True # coldcard, trezor import - wallet_json = """ - {"label": "MyTestMultisig", - "blockheight": 0, - "descriptor": - "wsh(sortedmulti(1,[fb7c1f11/48h/1h/0h/2h]tpubDExnGppazLhZPNadP8Q5Vgee2QcvbyAf9GvGaEY7ALVJREaG2vdTqv1MHRoDtPaYP3y1DGVx7wrKKhsLhs26GY263uE6Wi3qNbi71AHZ6p7/0/*,[1ef4e492/48h/1h/0h/2h]tpubDFiVCZzdarbyk8kE65tjRhHCambEo8iTx4xkXL8b33BKZj66HWsDnUb3rg4GZz6Mwm6vTNyzRCjYtiScCQJ77ENedb2deDDtcoNQXiUouJQ/0/*))#s0jemlck", - "devices": - [ - {"type": "coldcard", - "label": "MyColdcard"}, - {"type": "trezor", - "label": "MyTestTrezor"} - ] - } - """ + wallet_json = wallet_coldcardtrezor_json wallet_importer = WalletImporter(wallet_json, specter_mock) assert wallet_importer.wallet_name == "MyTestMultisig" @@ -136,6 +189,18 @@ def test_WalletImporter_unit(): ) +@patch("cryptoadvance.specter.util.wallet_importer.flash", print) +@patch("cryptoadvance.specter.util.wallet_importer._", lambda x: x) +def test_WalletImporter_unit_fail(caplog): + caplog.set_level(logging.DEBUG) + specter_mock = MagicMock() + specter_mock.node.chain = "main" + + wallet_json = wallet_ledger_json + with pytest.raises(SpecterError): + wallet_importer = WalletImporter(wallet_json, specter_mock) + + @pytest.mark.slow def test_WalletImporter_integration(specter_regtest_configured, bitcoin_regtest): """