From 7dcfbdc3d9351818e7b67ce6e698d6efce79d3ef Mon Sep 17 00:00:00 2001 From: benk10 Date: Fri, 23 Apr 2021 20:06:01 +0200 Subject: [PATCH] Chore: Refactoring new device setup (#1111) * New device refactoring * Hide hot wallet if RPC is not connected * Replace old url with new one * Update new_device_keys.jinja * Fixes for adding keys to existing device * Cleanup old code for adding keys * Fix cypress * Update cypress * Fix hot wallet issue * Hide generate option on add keys hot wallet Co-authored-by: Kim Neunert --- cypress/support/commands.js | 10 +- package-lock.json | 32 +- .../specter/devices/bitcoin_core.py | 4 + .../specter/server_endpoints/devices.py | 363 ++++++++-------- .../specter/templates/base.jinja | 2 +- .../specter/templates/device/device.jinja | 2 +- .../new_device_keys.jinja} | 397 ++---------------- .../new_device/new_device_mnemonic.jinja | 191 +++++++++ .../device/new_device/new_device_type.jinja | 49 +++ .../templates/device/new_device_manual.jinja | 8 - .../sidebar/components/sidebar_btn.jinja | 2 +- .../templates/includes/sidebar/sidebar.jinja | 2 +- .../wallet/new_wallet/new_wallet.jinja | 2 +- tests/test_controller.py | 2 +- 14 files changed, 488 insertions(+), 578 deletions(-) rename src/cryptoadvance/specter/templates/device/{new_device.jinja => new_device/new_device_keys.jinja} (53%) create mode 100644 src/cryptoadvance/specter/templates/device/new_device/new_device_mnemonic.jinja create mode 100644 src/cryptoadvance/specter/templates/device/new_device/new_device_type.jinja diff --git a/cypress/support/commands.js b/cypress/support/commands.js index fe50d643c..c6a73beec 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -35,13 +35,13 @@ Cypress.Commands.add("addDevice", (name) => { // Creating a Device cy.contains('Select Your Device Type') cy.get('#trezor_device_card') - cy.get('#step1 > [type="text"]').type("specter") + cy.get('#device-type-searchbar').type("specter") cy.contains('Select Your Device Type') cy.get('#trezor_device_card').should('not.have.class', 'disabled') cy.get('#specter_device_card').click() cy.get('h2 > input').type(name) - cy.get('#wizard-previous').click() - cy.get('#step1 > .note').click() + cy.go('back') + cy.get('#device-type-container > .note').click() cy.get('#device_name').type(name) cy.get('#device_type').select("Specter-DIY") cy.get('#txt').type("[6ea15da6/84h/1h/0h]vpub5Yw9Qps1aUVBpD3eyKVUhe5K8gbaWav9ArB4FudKeCLPb5vSsX8afAWfYvEASbkv4qxKzxSRyd3wqdgM3ir2fbrUJwiHDAJUNWGeqMDQfTj") @@ -64,9 +64,9 @@ Cypress.Commands.add("addHotDevice", (name) => { // Creating a Device cy.contains('Select Your Device Type') cy.get('#bitcoincore_device_card').click() - cy.get('#wizard-next').click() + cy.get('#submit-mnemonic').click() cy.get('#device_name').type(name) - cy.get('#wizard-submit').click() + cy.get('#submit-keys').click() cy.get('#devices_list > .item > div').contains(name) }) }) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 3c5d08ec1..5365de29b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,7 +1,7 @@ { "name": "specter-desktop-cypress-testing", "version": "0.0.1", - "lockfileVersion": 2, + "lockfileVersion": 1, "requires": true, "packages": { "": { @@ -3483,21 +3483,6 @@ "tweetnacl": "~0.14.0" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -3523,6 +3508,21 @@ } } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", diff --git a/src/cryptoadvance/specter/devices/bitcoin_core.py b/src/cryptoadvance/specter/devices/bitcoin_core.py index 2a4341a03..92253e6ba 100644 --- a/src/cryptoadvance/specter/devices/bitcoin_core.py +++ b/src/cryptoadvance/specter/devices/bitcoin_core.py @@ -43,6 +43,10 @@ class BitcoinCore(Device): keys_range=[0, 1000], keys_purposes=[], ): + if type(keys_range[0]) == str: + keys_range[0] = int(keys_range[0]) + if type(keys_range[1]) == str: + keys_range[1] = int(keys_range[1]) seed = bip39.mnemonic_to_seed(mnemonic, passphrase) root = bip32.HDKey.from_seed(seed) network = networks.NETWORKS["test" if testnet else "main"] diff --git a/src/cryptoadvance/specter/server_endpoints/devices.py b/src/cryptoadvance/specter/server_endpoints/devices.py index cead83f71..b34a226bc 100644 --- a/src/cryptoadvance/specter/server_endpoints/devices.py +++ b/src/cryptoadvance/specter/server_endpoints/devices.py @@ -1,4 +1,4 @@ -import copy, random +import copy, random, json from flask import ( Flask, @@ -15,6 +15,8 @@ from flask import current_app as app from mnemonic import Mnemonic from ..helpers import is_testnet, generate_mnemonic from ..key import Key +from ..device_manager import get_device_class +from ..devices.bitcoin_core import BitcoinCore from ..wallet_manager import purposes from ..specter_error import handle_exception @@ -25,138 +27,195 @@ devices_endpoint = Blueprint("devices_endpoint", __name__) ################## New device ####################### - -# New device -@devices_endpoint.route("/new_device/", methods=["GET", "POST"]) +# New device type +@devices_endpoint.route("/new_device_type/", methods=["GET", "POST"]) @login_required -def new_device(): +def new_device_type(): + return render_template( + "device/new_device/new_device_type.jinja", + specter=app.specter, + rand=rand, + ) + + +@devices_endpoint.route("/new_device_keys/", methods=["GET", "POST"]) +@login_required +def new_device_keys(device_type): err = None - strength = 128 - mnemonic = generate_mnemonic(strength=strength) + mnemonic = "" + passphrase = "" + file_password = "" + range_start = 0 + range_end = 1000 + existing_device = None if request.method == "POST": - if request.form.get("existing_device"): - device = app.specter.device_manager.get_by_alias( - request.form.get("existing_device") - ) - device_type = device.device_type + mnemonic = request.form.get("mnemonic", "") + passphrase = request.form.get("passphrase", "") + file_password = request.form.get("file_password", "") + range_start = int(request.form.get("range_start", "0")) + range_end = int(request.form.get("range_end", "1000")) + existing_device = request.form.get("existing_device", None) + if existing_device: + device = app.specter.device_manager.get_by_alias(existing_device) else: - device_type = request.form.get("devices") device_name = request.form.get("device_name", "") if not device_name: err = "Device name must not be empty" elif device_name in app.specter.device_manager.devices_names: err = "Device with this name already exists" xpubs_rows_count = int(request.form["xpubs_rows_count"]) + 1 - if device_type != "bitcoincore": - keys = [] - for i in range(0, xpubs_rows_count): - purpose = request.form.get( - "xpubs-table-row-{}-purpose".format(i), "Custom" - ) - xpub = request.form.get("xpubs-table-row-{}-xpub-hidden".format(i), "-") - if xpub != "-": - try: - keys.append(Key.parse_xpub(xpub, purpose=purpose)) - except: - err = "Failed to parse these xpubs:\n" + "\n".join(xpub) - break - if not keys and not err: - err = "xpubs name must not be empty" - if err is None: - if request.form.get("existing_device"): - device.add_keys(keys) - flash("{} keys were added successfully".format(len(keys))) - return redirect( - url_for("devices_endpoint.device", device_alias=device.alias) + keys = [] + paths = [] + keys_purposes = [] + for i in range(0, xpubs_rows_count): + purpose = request.form.get("xpubs-table-row-{}-purpose".format(i), "Custom") + xpub = request.form.get("xpubs-table-row-{}-xpub-hidden".format(i), "-") + path = request.form.get( + "xpubs-table-row-{}-derivation-hidden".format(i), "" + ) + if path != "": + paths.append(path) + keys_purposes.append(purpose) + if xpub != "-": + try: + keys.append(Key.parse_xpub(xpub, purpose=purpose)) + except: + err = "Failed to parse these xpubs:\n" + "\n".join(xpub) + break + if not keys and not err: + if device_type == "bitcoincore": + if not paths: + err = "No paths were specified, please provide at lease one." + if err is None: + if existing_device: + device.add_hot_wallet_keys( + mnemonic, + passphrase, + paths, + file_password, + app.specter.wallet_manager, + is_testnet(app.specter.chain), + keys_range=[range_start, range_end], + keys_purposes=keys_purposes, + ) + flash("{} keys were added successfully".format(len(paths))) + return redirect( + url_for( + "devices_endpoint.device", device_alias=device.alias + ) + ) + device = app.specter.device_manager.add_device( + name=device_name, device_type=device_type, keys=[] ) - device = app.specter.device_manager.add_device( - name=device_name, device_type=device_type, keys=keys - ) - flash("{} was added successfully!".format(device_name)) + try: + device.setup_device(file_password, app.specter.wallet_manager) + device.add_hot_wallet_keys( + mnemonic, + passphrase, + paths, + file_password, + app.specter.wallet_manager, + is_testnet(app.specter.chain), + keys_range=[range_start, range_end], + keys_purposes=keys_purposes, + ) + flash("{} was added successfully!".format(device_name)) + return redirect( + url_for( + "devices_endpoint.device", device_alias=device.alias + ) + + "?newdevice=true" + ) + except Exception as e: + handle_exception(e) + flash(f"Failed to setup hot wallet. Error: {e}", "error") + app.specter.device_manager.remove_device( + device, + app.specter.wallet_manager, + bitcoin_datadir=app.specter.bitcoin_datadir, + chain=app.specter.chain, + ) + else: + err = "xpubs list must not be empty" + elif not err: + if existing_device: + device.add_keys(keys) + flash("{} keys were added successfully".format(len(keys))) return redirect( url_for("devices_endpoint.device", device_alias=device.alias) - + "?newdevice=true" ) - else: - flash(err, "error") - else: - if len(request.form["mnemonic"].split(" ")) not in [12, 15, 18, 21, 24]: - err = "Invalid mnemonic entered: Must contain either: 12, 15, 18, 21, or 24 words." - mnemo = Mnemonic("english") - if not mnemo.check(request.form["mnemonic"]): - err = "Invalid mnemonic entered." - range_start = int(request.form["range_start"]) - range_end = int(request.form["range_end"]) - if range_start > range_end: - err = "Invalid address range selected." - mnemonic = request.form["mnemonic"] - paths = [] - keys_purposes = [] - for i in range(0, xpubs_rows_count): - purpose = request.form.get( - "xpubs-table-row-{}-purpose".format(i), "Custom" - ) - path = request.form.get( - "xpubs-table-row-{}-derivation-hidden".format(i), "" - ) - if path != "": - paths.append(path) - keys_purposes.append(purpose) - if not paths: - err = "No paths were specified, please provide at lease one." - if err is None: - passphrase = request.form["passphrase"] - file_password = request.form["file_password"] - if request.form.get("existing_device"): - device.add_hot_wallet_keys( - mnemonic, - passphrase, - paths, - file_password, - app.specter.wallet_manager, - is_testnet(app.specter.chain), - keys_range=[range_start, range_end], - keys_purposes=keys_purposes, - ) - flash("{} keys were added successfully".format(len(paths))) - return redirect( - url_for("devices_endpoint.device", device_alias=device.alias) - ) - device = app.specter.device_manager.add_device( - name=device_name, device_type=device_type, keys=[] - ) - try: - device.setup_device(file_password, app.specter.wallet_manager) - device.add_hot_wallet_keys( - mnemonic, - passphrase, - paths, - file_password, - app.specter.wallet_manager, - is_testnet(app.specter.chain), - keys_range=[range_start, range_end], - keys_purposes=keys_purposes, - ) - flash("{} was added successfully!".format(device_name)) - return redirect( - url_for("devices_endpoint.device", device_alias=device.alias) - + "?newdevice=true" - ) - except Exception as e: - handle_exception(e) - flash(f"Failed to setup hot wallet. Error: {e}", "error") - app.specter.device_manager.remove_device( - device, - app.specter.wallet_manager, - bitcoin_datadir=app.specter.bitcoin_datadir, - chain=app.specter.chain, - ) - else: - flash(err, "error") + device = app.specter.device_manager.add_device( + name=device_name, device_type=device_type, keys=keys + ) + flash("{} was added successfully!".format(device_name)) + return redirect( + url_for("devices_endpoint.device", device_alias=device.alias) + + "?newdevice=true" + ) + return render_template( - "device/new_device.jinja", + "device/new_device/new_device_keys.jinja", + device_class=get_device_class(device_type), mnemonic=mnemonic, + passphrase=passphrase, + file_password=file_password, + range_start=range_start, + range_end=range_end, + existing_device=app.specter.device_manager.get_by_alias(existing_device) + if existing_device + else None, + error=err, + specter=app.specter, + rand=rand, + ) + + +@devices_endpoint.route("/new_device_mnemonic/", methods=["GET", "POST"]) +@login_required +def new_device_mnemonic(): + err = None + strength = 128 + mnemonic = generate_mnemonic(strength=strength) + existing_device = None + if request.method == "POST": + if len(request.form["mnemonic"].split(" ")) not in [12, 15, 18, 21, 24]: + err = "Invalid mnemonic entered: Must contain either: 12, 15, 18, 21, or 24 words." + mnemo = Mnemonic("english") + if not mnemo.check(request.form["mnemonic"]): + err = "Invalid mnemonic entered." + range_start = int(request.form["range_start"]) + range_end = int(request.form["range_end"]) + if range_start > range_end: + err = "Invalid address range selected." + mnemonic = request.form["mnemonic"] + passphrase = request.form["passphrase"] + file_password = request.form["file_password"] + existing_device = request.form.get("existing_device", None) + print("file_password") + print(file_password) + if existing_device: + existing_device = app.specter.device_manager.get_by_alias(existing_device) + if not err: + return render_template( + "device/new_device/new_device_keys.jinja", + mnemonic=mnemonic, + passphrase=passphrase, + file_password=file_password, + range_start=range_start, + range_end=range_end, + device_class=BitcoinCore, + existing_device=existing_device, + error=err, + specter=app.specter, + rand=rand, + ) + + return render_template( + "device/new_device/new_device_mnemonic.jinja", strength=strength, + mnemonic=mnemonic, + existing_device=existing_device, + error=err, specter=app.specter, rand=rand, ) @@ -312,62 +371,26 @@ def device(device_alias): elif action == "add_keys": strength = 128 mnemonic = generate_mnemonic(strength=strength) - return render_template( - "device/new_device.jinja", - mnemonic=mnemonic, - strength=strength, - existing_device=device, - device_alias=device_alias, - specter=app.specter, - rand=rand, - ) - elif action == "morekeys": if device.hot_wallet: - if len(request.form["mnemonic"].split(" ")) not in [12, 15, 18, 21, 24]: - err = "Invalid mnemonic entered: Must contain either: 12, 15, 18, 21, or 24 words." - mnemo = Mnemonic("english") - if not mnemo.check(request.form["mnemonic"]): - err = "Invalid mnemonic entered." - range_start = int(request.form["range_start"]) - range_end = int(request.form["range_end"]) - if range_start > range_end: - err = "Invalid address range selected." - if err is None: - mnemonic = request.form["mnemonic"] - paths = [ - l.strip() - for l in request.form["derivation_paths"].split("\n") - if len(l) > 0 - ] - passphrase = request.form["passphrase"] - file_password = request.form["file_password"] - device.add_hot_wallet_keys( - mnemonic, - passphrase, - paths, - file_password, - app.specter.wallet_manager, - is_testnet(app.specter.chain), - keys_range=[range_start, range_end], - ) + return render_template( + "device/new_device/new_device_mnemonic.jinja", + mnemonic=mnemonic, + strength=strength, + existing_device=device, + device_alias=device_alias, + device_class=get_device_class(device.device_type), + specter=app.specter, + rand=rand, + ) else: - # refactor to fn - xpubs = request.form["xpubs"] - keys, failed = Key.parse_xpubs(xpubs) - err = None - if len(failed) > 0: - err = "Failed to parse these xpubs:\n" + "\n".join(failed) - return render_template( - "device/new_device_manual.jinja", - device=device, - device_alias=device_alias, - xpubs=xpubs, - error=err, - specter=app.specter, - rand=rand, - ) - if err is None: - device.add_keys(keys) + return render_template( + "device/new_device/new_device_keys.jinja", + existing_device=device, + device_alias=device_alias, + device_class=get_device_class(device.device_type), + specter=app.specter, + rand=rand, + ) elif action == "settype": device_type = request.form["device_type"] device.set_type(device_type) diff --git a/src/cryptoadvance/specter/templates/base.jinja b/src/cryptoadvance/specter/templates/base.jinja index f6e166737..d7785a28f 100644 --- a/src/cryptoadvance/specter/templates/base.jinja +++ b/src/cryptoadvance/specter/templates/base.jinja @@ -165,7 +165,7 @@ - +

Add signing devices you wish to use.

diff --git a/src/cryptoadvance/specter/templates/device/device.jinja b/src/cryptoadvance/specter/templates/device/device.jinja index 37f09b6a3..b07f38987 100644 --- a/src/cryptoadvance/specter/templates/device/device.jinja +++ b/src/cryptoadvance/specter/templates/device/device.jinja @@ -100,7 +100,7 @@