Example #1
0
        public async Task <IActionResult> VaultBridgeConnection(string cryptoCode = null,
                                                                [ModelBinder(typeof(WalletIdModelBinder))]
                                                                WalletId walletId = null)
        {
            if (!HttpContext.WebSockets.IsWebSocketRequest)
            {
                return(NotFound());
            }
            cryptoCode = cryptoCode ?? walletId.CryptoCode;
            bool versionChecked = false;

            using (var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10)))
            {
                var cancellationToken = cts.Token;
                var network           = Networks.GetNetwork <BTCPayNetwork>(cryptoCode);
                if (network == null)
                {
                    return(NotFound());
                }
                var websocket = await HttpContext.WebSockets.AcceptWebSocketAsync();

                var hwi = new Hwi.HwiClient(network.NBitcoinNetwork)
                {
                    Transport = new HwiWebSocketTransport(websocket)
                };
                Hwi.HwiDeviceClient device      = null;
                HwiEnumerateEntry   deviceEntry = null;
                HDFingerprint?      fingerprint = null;
                string password        = null;
                var    websocketHelper = new WebSocketHelper(websocket);

                async Task <bool> RequireDeviceUnlocking()
                {
                    if (deviceEntry == null)
                    {
                        await websocketHelper.Send("{ \"error\": \"need-device\"}", cancellationToken);

                        return(true);
                    }
                    if (deviceEntry.Code is HwiErrorCode.DeviceNotInitialized)
                    {
                        await websocketHelper.Send("{ \"error\": \"need-initialized\"}", cancellationToken);

                        return(true);
                    }
                    if (deviceEntry.Code is HwiErrorCode.DeviceNotReady)
                    {
                        if (IsTrezorT(deviceEntry))
                        {
                            await websocketHelper.Send("{ \"error\": \"need-passphrase-on-device\"}", cancellationToken);

                            return(true);
                        }
                        else if (deviceEntry.NeedsPinSent is true)
                        {
                            await websocketHelper.Send("{ \"error\": \"need-pin\"}", cancellationToken);

                            return(true);
                        }
                        else if (deviceEntry.NeedsPassphraseSent is true && password is null)
                        {
                            await websocketHelper.Send("{ \"error\": \"need-passphrase\"}", cancellationToken);

                            return(true);
                        }
                    }
                    return(false);
                }

                JObject o = null;
                try
                {
                    while (true)
                    {
                        var command = await websocketHelper.NextMessageAsync(cancellationToken);

                        switch (command)
                        {
                        case "set-passphrase":
                            device.Password = await websocketHelper.NextMessageAsync(cancellationToken);

                            password = device.Password;
                            break;

                        case "ask-sign":
                            if (await RequireDeviceUnlocking())
                            {
                                continue;
                            }
                            if (walletId == null)
                            {
                                await websocketHelper.Send("{ \"error\": \"invalid-walletId\"}", cancellationToken);

                                continue;
                            }
                            if (fingerprint is null)
                            {
                                fingerprint = (await device.GetXPubAsync(new KeyPath("44'"), cancellationToken)).ExtPubKey.ParentFingerprint;
                            }
                            await websocketHelper.Send("{ \"info\": \"ready\"}", cancellationToken);

                            o = JObject.Parse(await websocketHelper.NextMessageAsync(cancellationToken));
                            var authorization = await _authorizationService.AuthorizeAsync(User, Policies.CanModifyStoreSettings);

                            if (!authorization.Succeeded)
                            {
                                await websocketHelper.Send("{ \"error\": \"not-authorized\"}", cancellationToken);

                                continue;
                            }
                            var psbt = PSBT.Parse(o["psbt"].Value <string>(), network.NBitcoinNetwork);
                            var derivationSettings = GetDerivationSchemeSettings(walletId);
                            derivationSettings.RebaseKeyPaths(psbt);
                            var signing = derivationSettings.GetSigningAccountKeySettings();
                            if (signing.GetRootedKeyPath()?.MasterFingerprint != fingerprint)
                            {
                                await websocketHelper.Send("{ \"error\": \"wrong-wallet\"}", cancellationToken);

                                continue;
                            }
                            var signableInputs = psbt.Inputs
                                                 .SelectMany(i => i.HDKeyPaths)
                                                 .Where(i => i.Value.MasterFingerprint == fingerprint)
                                                 .ToArray();
                            if (signableInputs.Length > 0)
                            {
                                var actualPubKey = (await device.GetXPubAsync(signableInputs[0].Value.KeyPath)).GetPublicKey();
                                if (actualPubKey != signableInputs[0].Key)
                                {
                                    await websocketHelper.Send("{ \"error\": \"wrong-keypath\"}", cancellationToken);

                                    continue;
                                }
                            }
                            try
                            {
                                psbt = await device.SignPSBTAsync(psbt, cancellationToken);
                            }
                            catch (Hwi.HwiException)
                            {
                                await websocketHelper.Send("{ \"error\": \"user-reject\"}", cancellationToken);

                                continue;
                            }
                            o = new JObject();
                            o.Add("psbt", psbt.ToBase64());
                            await websocketHelper.Send(o.ToString(), cancellationToken);

                            break;

                        case "display-address":
                            if (await RequireDeviceUnlocking())
                            {
                                continue;
                            }
                            var k = RootedKeyPath.Parse(await websocketHelper.NextMessageAsync(cancellationToken));
                            await device.DisplayAddressAsync(GetScriptPubKeyType(k), k.KeyPath, cancellationToken);

                            await websocketHelper.Send("{ \"info\": \"ok\"}", cancellationToken);

                            break;

                        case "ask-pin":
                            if (device == null)
                            {
                                await websocketHelper.Send("{ \"error\": \"need-device\"}", cancellationToken);

                                continue;
                            }
                            try
                            {
                                await device.PromptPinAsync(cancellationToken);
                            }
                            catch (HwiException ex) when(ex.ErrorCode == HwiErrorCode.DeviceAlreadyUnlocked)
                            {
                                await websocketHelper.Send("{ \"error\": \"device-already-unlocked\"}", cancellationToken);

                                continue;
                            }
                            await websocketHelper.Send("{ \"info\": \"prompted, please input the pin\"}", cancellationToken);

                            var pin = int.Parse(await websocketHelper.NextMessageAsync(cancellationToken), CultureInfo.InvariantCulture);
                            if (await device.SendPinAsync(pin, cancellationToken))
                            {
                                goto askdevice;
                            }
                            else
                            {
                                await websocketHelper.Send("{ \"error\": \"incorrect-pin\"}", cancellationToken);

                                continue;
                            }

                        case "ask-xpub":
                            if (await RequireDeviceUnlocking())
                            {
                                continue;
                            }
                            await websocketHelper.Send("{ \"info\": \"ok\"}", cancellationToken);

                            var     askedXpub     = JObject.Parse(await websocketHelper.NextMessageAsync(cancellationToken));
                            var     addressType   = askedXpub["addressType"].Value <string>();
                            var     accountNumber = askedXpub["accountNumber"].Value <int>();
                            JObject result        = new JObject();
                            var     factory       = network.NBXplorerNetwork.DerivationStrategyFactory;
                            if (fingerprint is null)
                            {
                                fingerprint = (await device.GetXPubAsync(new KeyPath("44'"), cancellationToken)).ExtPubKey.ParentFingerprint;
                            }
                            result["fingerprint"] = fingerprint.Value.ToString();

                            DerivationStrategyBase strategy = null;
                            KeyPath          keyPath        = null;
                            BitcoinExtPubKey xpub           = null;

                            if (!network.NBitcoinNetwork.Consensus.SupportSegwit && addressType != "legacy")
                            {
                                await websocketHelper.Send("{ \"error\": \"segwit-notsupported\"}", cancellationToken);

                                continue;
                            }

                            if (!network.NBitcoinNetwork.Consensus.SupportTaproot && addressType == "taproot")
                            {
                                await websocketHelper.Send("{ \"error\": \"taproot-notsupported\"}", cancellationToken);

                                continue;
                            }
                            if (addressType == "taproot")
                            {
                                keyPath = new KeyPath("86'").Derive(network.CoinType).Derive(accountNumber, true);
                                xpub    = await device.GetXPubAsync(keyPath);

                                strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
                                {
                                    ScriptPubKeyType = ScriptPubKeyType.TaprootBIP86
                                });
                            }
                            else if (addressType == "segwit")
                            {
                                keyPath = new KeyPath("84'").Derive(network.CoinType).Derive(accountNumber, true);
                                xpub    = await device.GetXPubAsync(keyPath);

                                strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
                                {
                                    ScriptPubKeyType = ScriptPubKeyType.Segwit
                                });
                            }
                            else if (addressType == "segwitWrapped")
                            {
                                keyPath = new KeyPath("49'").Derive(network.CoinType).Derive(accountNumber, true);
                                xpub    = await device.GetXPubAsync(keyPath);

                                strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
                                {
                                    ScriptPubKeyType = ScriptPubKeyType.SegwitP2SH
                                });
                            }
                            else if (addressType == "legacy")
                            {
                                keyPath = new KeyPath("44'").Derive(network.CoinType).Derive(accountNumber, true);
                                xpub    = await device.GetXPubAsync(keyPath);

                                strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
                                {
                                    ScriptPubKeyType = ScriptPubKeyType.Legacy
                                });
                            }
                            else
                            {
                                await websocketHelper.Send("{ \"error\": \"invalid-addresstype\"}", cancellationToken);

                                continue;
                            }
                            result.Add(new JProperty("strategy", strategy.ToString()));
                            result.Add(new JProperty("accountKey", xpub.ToString()));
                            result.Add(new JProperty("keyPath", keyPath.ToString()));
                            await websocketHelper.Send(result.ToString(), cancellationToken);

                            break;

                        case "ask-passphrase":
                            if (command == "ask-passphrase")
                            {
                                if (deviceEntry == null)
                                {
                                    await websocketHelper.Send("{ \"error\": \"need-device\"}", cancellationToken);

                                    continue;
                                }
                                // The make the trezor T ask for password
                                await device.GetXPubAsync(new KeyPath("44'"), cancellationToken);
                            }
                            goto askdevice;

                        case "ask-device":
askdevice:
                            if (!versionChecked)
                            {
                                var version = await hwi.GetVersionAsync(cancellationToken);

                                if (version.Major < 2)
                                {
                                    await websocketHelper.Send("{ \"error\": \"vault-outdated\"}", cancellationToken);

                                    continue;
                                }
                                versionChecked = true;
                            }
                            password    = null;
                            deviceEntry = null;
                            device      = null;
                            var entries = (await hwi.EnumerateEntriesAsync(cancellationToken)).ToList();
                            deviceEntry = entries.FirstOrDefault();
                            if (deviceEntry == null)
                            {
                                await websocketHelper.Send("{ \"error\": \"no-device\"}", cancellationToken);

                                continue;
                            }
                            device      = new HwiDeviceClient(hwi, deviceEntry.DeviceSelector, deviceEntry.Model, deviceEntry.Fingerprint);
                            fingerprint = device.Fingerprint;
                            JObject json = new JObject();
                            json.Add("model", device.Model);
                            json.Add("fingerprint", device.Fingerprint?.ToString());
                            await websocketHelper.Send(json.ToString(), cancellationToken);

                            break;
                        }
                    }
                }
                catch (FormatException ex)
                {
                    JObject obj = new JObject();
                    obj.Add("error", "invalid-network");
                    obj.Add("details", ex.ToString());
                    try
                    {
                        await websocketHelper.Send(obj.ToString(), cancellationToken);
                    }
                    catch { }
                }
                catch (Exception ex)
                {
                    JObject obj = new JObject();
                    obj.Add("error", "unknown-error");
                    obj.Add("message", ex.Message);
                    obj.Add("details", ex.ToString());
                    try
                    {
                        await websocketHelper.Send(obj.ToString(), cancellationToken);
                    }
                    catch { }
                }
                finally
                {
                    await websocketHelper.DisposeAsync(cancellationToken);
                }
            }
            return(new EmptyResult());
        }
Example #2
0
 private static bool IsTrezorT(HwiEnumerateEntry deviceEntry)
 {
     return(deviceEntry.Model.Contains("Trezor_T", StringComparison.OrdinalIgnoreCase));
 }
Example #3
0
        public async Task TrezorTKataAsync()
        {
            // --- USER INTERACTIONS ---
            //
            // Connect and initialize your Trezor T with the following seed phrase:
            // more maid moon upgrade layer alter marine screen benefit way cover alcohol
            // Run this test.
            // displayaddress request: refuse 1 time
            // displayaddress request: confirm 2 times
            // displayaddress request: confirm 1 time
            // signtx request: refuse 1 time
            // signtx request: Hold to confirm
            //
            // --- USER INTERACTIONS ---

            var network = Network.Main;
            var client  = new HwiClient(network);

            using var cts = new CancellationTokenSource(ReasonableRequestTimeout);
            var enumerate = await client.EnumerateAsync(cts.Token);

            Assert.Single(enumerate);
            HwiEnumerateEntry entry = enumerate.Single();

            Assert.NotNull(entry.Path);
            Assert.Equal(HardwareWalletModels.Trezor_T, entry.Model);
            Assert.NotNull(entry.Fingerprint);

            string devicePath = entry.Path;
            HardwareWalletModels deviceType  = entry.Model;
            HDFingerprint        fingerprint = entry.Fingerprint.Value;

            await Assert.ThrowsAsync <HwiException>(async() => await client.SetupAsync(deviceType, devicePath, false, cts.Token));

            await Assert.ThrowsAsync <HwiException>(async() => await client.RestoreAsync(deviceType, devicePath, false, cts.Token));

            // Trezor T doesn't support it.
            await Assert.ThrowsAsync <HwiException>(async() => await client.PromptPinAsync(deviceType, devicePath, cts.Token));

            // Trezor T doesn't support it.
            await Assert.ThrowsAsync <HwiException>(async() => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token));

            KeyPath   keyPath1 = KeyManager.DefaultAccountKeyPath;
            KeyPath   keyPath2 = KeyManager.DefaultAccountKeyPath.Derive(1);
            ExtPubKey xpub1    = await client.GetXpubAsync(deviceType, devicePath, keyPath1, cts.Token);

            ExtPubKey xpub2 = await client.GetXpubAsync(deviceType, devicePath, keyPath2, cts.Token);

            Assert.NotNull(xpub1);
            Assert.NotNull(xpub2);
            Assert.NotEqual(xpub1, xpub2);

            // USER SHOULD REFUSE ACTION
            await Assert.ThrowsAsync <HwiException>(async() => await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token));

            // USER: CONFIRM
            BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token);

            // USER: CONFIRM
            BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(fingerprint, keyPath2, cts.Token);

            Assert.NotNull(address1);
            Assert.NotNull(address2);
            Assert.NotEqual(address1, address2);
            var expectedAddress1 = xpub1.PubKey.GetAddress(ScriptPubKeyType.Segwit, network);
            var expectedAddress2 = xpub2.PubKey.GetAddress(ScriptPubKeyType.Segwit, network);

            Assert.Equal(expectedAddress1, address1);
            Assert.Equal(expectedAddress2, address2);

            // USER SHOULD REFUSE ACTION
            var result = await Assert.ThrowsAsync <HwiException>(async() => await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token));

            Assert.Equal(HwiErrorCode.ActionCanceled, result.ErrorCode);

            // USER: Hold to confirm
            PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token);

            Transaction signedTx = signedPsbt.GetOriginalTransaction();

            Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash());

            var checkResult = signedTx.Check();

            Assert.Equal(TransactionCheckResult.Success, checkResult);
        }
Example #4
0
        public async Task TrezorTKataAsync()
        {
            // --- USER INTERACTIONS ---
            //
            // Connect an already initialized device and unlock it.
            // Run this test.
            // displayaddress request: refuse
            // displayaddress request: confirm
            // displayaddress request: confirm
            // signtx request: confirm
            //
            // --- USER INTERACTIONS ---

            var network = Network.Main;
            var client  = new HwiClient(network);

            using var cts = new CancellationTokenSource(ReasonableRequestTimeout);
            var enumerate = await client.EnumerateAsync(cts.Token);

            Assert.Single(enumerate);
            HwiEnumerateEntry entry = enumerate.Single();

            Assert.NotNull(entry.Path);
            Assert.Equal(HardwareWalletModels.Trezor_T, entry.Model);
            Assert.True(entry.Fingerprint.HasValue);

            string devicePath = entry.Path;
            HardwareWalletModels deviceType  = entry.Model;
            HDFingerprint        fingerprint = entry.Fingerprint.Value;

            await Assert.ThrowsAsync <HwiException>(async() => await client.SetupAsync(deviceType, devicePath, false, cts.Token));

            await Assert.ThrowsAsync <HwiException>(async() => await client.RestoreAsync(deviceType, devicePath, false, cts.Token));

            // Trezor T doesn't support it.
            await Assert.ThrowsAsync <HwiException>(async() => await client.PromptPinAsync(deviceType, devicePath, cts.Token));

            // Trezor T doesn't support it.
            await Assert.ThrowsAsync <HwiException>(async() => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token));

            KeyPath   keyPath1 = KeyManager.DefaultAccountKeyPath;
            KeyPath   keyPath2 = KeyManager.DefaultAccountKeyPath.Derive(1);
            ExtPubKey xpub1    = await client.GetXpubAsync(deviceType, devicePath, keyPath1, cts.Token);

            ExtPubKey xpub2 = await client.GetXpubAsync(deviceType, devicePath, keyPath2, cts.Token);

            Assert.NotNull(xpub1);
            Assert.NotNull(xpub2);
            Assert.NotEqual(xpub1, xpub2);

            // USER SHOULD REFUSE ACTION
            await Assert.ThrowsAsync <HwiException>(async() => await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token));

            // USER: CONFIRM
            BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token);

            // USER: CONFIRM
            BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(fingerprint, keyPath2, cts.Token);

            Assert.NotNull(address1);
            Assert.NotNull(address2);
            Assert.NotEqual(address1, address2);
            var expectedAddress1 = xpub1.PubKey.GetAddress(ScriptPubKeyType.Segwit, network);
            var expectedAddress2 = xpub2.PubKey.GetAddress(ScriptPubKeyType.Segwit, network);

            Assert.Equal(expectedAddress1, address1);
            Assert.Equal(expectedAddress2, address2);

            // USER: CONFIRM
            PSBT psbt       = BuildPsbt(network, fingerprint, xpub1, keyPath1);
            PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, psbt, cts.Token);

            Transaction signedTx = signedPsbt.GetOriginalTransaction();

            Assert.Equal(psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash());

            var checkResult = signedTx.Check();

            Assert.Equal(TransactionCheckResult.Success, checkResult);
        }
 private static bool IsTrezorT(HwiEnumerateEntry deviceEntry)
 {
     return(deviceEntry.Model == HardwareWalletModels.Trezor_T || deviceEntry.Model == HardwareWalletModels.Trezor_T_Simulator);
 }
        public DetectedHardwareWalletViewModel(WalletManager walletManager, string walletName, HwiEnumerateEntry device)
        {
            WalletManager = walletManager;
            WalletName    = walletName;
            CancelCts     = new CancellationTokenSource();

            Type = device.Model switch
            {
                HardwareWalletModels.Coldcard or HardwareWalletModels.Coldcard_Simulator => WalletType.Coldcard,
                                                                      HardwareWalletModels.Ledger_Nano_S or HardwareWalletModels.Ledger_Nano_X => WalletType.Ledger,
                                                                      HardwareWalletModels.Trezor_1 or HardwareWalletModels.Trezor_1_Simulator or HardwareWalletModels.Trezor_T or HardwareWalletModels.Trezor_T_Simulator => WalletType.Trezor,
                                                                      _ => WalletType.Hardware
            };

            TypeName = device.Model.FriendlyName();

            NextCommand = ReactiveCommand.CreateFromTask(async() =>
            {
                try
                {
                    var walletFilePath = WalletManager.WalletDirectories.GetWalletFilePaths(WalletName).walletFilePath;
                    var km             = await HardwareWalletOperationHelpers.GenerateWalletAsync(device, walletFilePath, WalletManager.Network, CancelCts.Token);
                    km.SetIcon(Type);

                    Navigate().To(new AddedWalletPageViewModel(walletManager, km));
                }
                catch (Exception ex)
                {
                    Logger.LogError(ex);
                    await ShowErrorAsync(Title, ex.ToUserFriendlyString(), "Error occured during adding your wallet.");
                    Navigate().Back();
                }
            });

            NoCommand = ReactiveCommand.Create(() => Navigate().Back());

            EnableAutoBusyOn(NextCommand);
        }
Example #7
0
		public async Task LedgerNanoSKataAsync()
		{
			// --- USER INTERACTIONS ---
			//
			// Connect and initialize your Nano S with the following seed phrase:
			// more maid moon upgrade layer alter marine screen benefit way cover alcohol
			// Run this test.
			// displayaddress request(derivation path): approve
			// displayaddress request: reject
			// displayaddress request(derivation path): approve
			// displayaddress request: approve
			// displayaddress request(derivation path): approve
			// displayaddress request: approve
			// signtx request: reject
			// signtx request: accept
			// confirm transaction: accept and send
			// unverified inputs: continue
			// signtx request: accept
			// confirm transaction: accept and send
			//
			// --- USER INTERACTIONS ---

			var network = Network.Main;
			var client = new HwiClient(network);
			using var cts = new CancellationTokenSource(ReasonableRequestTimeout);
			var enumerate = await client.EnumerateAsync(cts.Token);
			HwiEnumerateEntry entry = Assert.Single(enumerate);
			Assert.NotNull(entry.Path);
			Assert.Equal(HardwareWalletModels.Ledger_Nano_S, entry.Model);
			Assert.True(entry.Fingerprint.HasValue);
			Assert.Null(entry.Code);
			Assert.Null(entry.Error);
			Assert.Null(entry.SerialNumber);
			Assert.False(entry.NeedsPassphraseSent);
			Assert.False(entry.NeedsPinSent);

			string devicePath = entry.Path;
			HardwareWalletModels deviceType = entry.Model;
			HDFingerprint fingerprint = entry.Fingerprint.Value;

			await Assert.ThrowsAsync<HwiException>(async () => await client.SetupAsync(deviceType, devicePath, false, cts.Token));

			await Assert.ThrowsAsync<HwiException>(async () => await client.RestoreAsync(deviceType, devicePath, false, cts.Token));

			await Assert.ThrowsAsync<HwiException>(async () => await client.PromptPinAsync(deviceType, devicePath, cts.Token));

			await Assert.ThrowsAsync<HwiException>(async () => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token));

			KeyPath keyPath1 = KeyManager.DefaultAccountKeyPath;
			KeyPath keyPath2 = KeyManager.DefaultAccountKeyPath.Derive(1);
			ExtPubKey xpub1 = await client.GetXpubAsync(deviceType, devicePath, keyPath1, cts.Token);
			ExtPubKey xpub2 = await client.GetXpubAsync(deviceType, devicePath, keyPath2, cts.Token);
			Assert.NotNull(xpub1);
			Assert.NotNull(xpub2);
			Assert.NotEqual(xpub1, xpub2);

			// USER SHOULD REFUSE ACTION
			await Assert.ThrowsAsync<HwiException>(async () => await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token));

			// USER: CONFIRM
			BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token);
			// USER: CONFIRM
			BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(fingerprint, keyPath2, cts.Token);
			Assert.NotNull(address1);
			Assert.NotNull(address2);
			Assert.NotEqual(address1, address2);
			var expectedAddress1 = xpub1.PubKey.GetAddress(ScriptPubKeyType.Segwit, network);
			var expectedAddress2 = xpub2.PubKey.GetAddress(ScriptPubKeyType.Segwit, network);
			Assert.Equal(expectedAddress1, address1);
			Assert.Equal(expectedAddress2, address2);

			// USER: REFUSE
			var ex = await Assert.ThrowsAsync<HwiException>(async () => await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token));
			Assert.Equal(HwiErrorCode.BadArgument, ex.ErrorCode);

			// USER: CONFIRM
			var nullFailEx = await Assert.ThrowsAsync<PSBTException>(async () => await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token));
			Assert.Equal(nullFailEx.Message.Contains("NullFail"), true);

			// USER: CONFIRM
			PSBT signedPsbt = await Gui.Controls.WalletExplorer.SendTabViewModel.SignPsbtWithoutInputTxsAsync(client, fingerprint, Psbt, cts.Token);

			Transaction signedTx = signedPsbt.GetOriginalTransaction();
			Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash());

			var checkResult = signedTx.Check();
			Assert.Equal(TransactionCheckResult.Success, checkResult);
		}
Example #8
0
        public async Task LedgerNanoXTestsAsync(Network network)
        {
            var client = new HwiClient(network, new HwiProcessBridgeMock(HardwareWalletModels.Ledger_Nano_X));

            using var cts = new CancellationTokenSource(ReasonableRequestTimeout);
            IEnumerable <HwiEnumerateEntry> enumerate = await client.EnumerateAsync(cts.Token);

            Assert.Single(enumerate);
            HwiEnumerateEntry entry = enumerate.Single();

            Assert.Equal(HardwareWalletModels.Ledger_Nano_X, entry.Model);
            Assert.Equal(@"\\?\hid#vid_2c97&pid_0001&mi_00#7&e45ae20&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}", entry.Path);
            Assert.False(entry.NeedsPassphraseSent);
            Assert.False(entry.NeedsPinSent);
            Assert.Null(entry.Error);
            Assert.Null(entry.Code);
            Assert.True(entry.IsInitialized());
            Assert.Equal("4054d6f6", entry.Fingerprint.ToString());

            var deviceType = entry.Model;
            var devicePath = entry.Path;

            var wipe = await Assert.ThrowsAsync <HwiException>(async() => await client.WipeAsync(deviceType, devicePath, cts.Token));

            Assert.Equal("The Ledger Nano X does not support wiping via software", wipe.Message);
            Assert.Equal(HwiErrorCode.UnavailableAction, wipe.ErrorCode);

            var setup = await Assert.ThrowsAsync <HwiException>(async() => await client.SetupAsync(deviceType, devicePath, false, cts.Token));

            Assert.Equal("The Ledger Nano X does not support software setup", setup.Message);
            Assert.Equal(HwiErrorCode.UnavailableAction, setup.ErrorCode);

            var restore = await Assert.ThrowsAsync <HwiException>(async() => await client.RestoreAsync(deviceType, devicePath, false, cts.Token));

            Assert.Equal("The Ledger Nano X does not support restoring via software", restore.Message);
            Assert.Equal(HwiErrorCode.UnavailableAction, restore.ErrorCode);

            var promptpin = await Assert.ThrowsAsync <HwiException>(async() => await client.PromptPinAsync(deviceType, devicePath, cts.Token));

            Assert.Equal("The Ledger Nano X does not need a PIN sent from the host", promptpin.Message);
            Assert.Equal(HwiErrorCode.UnavailableAction, promptpin.ErrorCode);

            var sendpin = await Assert.ThrowsAsync <HwiException>(async() => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token));

            Assert.Equal("The Ledger Nano X does not need a PIN sent from the host", sendpin.Message);
            Assert.Equal(HwiErrorCode.UnavailableAction, sendpin.ErrorCode);

            KeyPath   keyPath1 = KeyManager.DefaultAccountKeyPath;
            KeyPath   keyPath2 = KeyManager.DefaultAccountKeyPath.Derive(1);
            ExtPubKey xpub1    = await client.GetXpubAsync(deviceType, devicePath, keyPath1, cts.Token);

            ExtPubKey xpub2 = await client.GetXpubAsync(deviceType, devicePath, keyPath2, cts.Token);

            var expecteXpub1 = NBitcoinHelpers.BetterParseExtPubKey("xpub6DHjDx4gzLV37gJWMxYJAqyKRGN46MT61RHVizdU62cbVUYu9L95cXKzX62yJ2hPbN11EeprS8sSn8kj47skQBrmycCMzFEYBQSntVKFQ5M");
            var expecteXpub2 = NBitcoinHelpers.BetterParseExtPubKey("xpub6FJS1ne3STcKdQ9JLXNzZXidmCNZ9dxLiy7WVvsRkcmxjJsrDKJKEAXq4MGyEBM3vHEw2buqXezfNK5SNBrkwK7Fxjz1TW6xzRr2pUyMWFu");

            Assert.Equal(expecteXpub1, xpub1);
            Assert.Equal(expecteXpub2, xpub2);

            BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token);

            BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath2, cts.Token);

            BitcoinAddress expectedAddress1;
            BitcoinAddress expectedAddress2;

            if (network == Network.Main)
            {
                expectedAddress1 = BitcoinAddress.Create("bc1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7fdevah", Network.Main);
                expectedAddress2 = BitcoinAddress.Create("bc1qmaveee425a5xjkjcv7m6d4gth45jvtnj23fzyf", Network.Main);
            }
            else if (network == Network.TestNet)
            {
                expectedAddress1 = BitcoinAddress.Create("tb1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7rtzlxy", Network.TestNet);
                expectedAddress2 = BitcoinAddress.Create("tb1qmaveee425a5xjkjcv7m6d4gth45jvtnjqhj3l6", Network.TestNet);
            }
            else if (network == Network.RegTest)
            {
                expectedAddress1 = BitcoinAddress.Create("bcrt1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7pzmj3d", Network.RegTest);
                expectedAddress2 = BitcoinAddress.Create("bcrt1qmaveee425a5xjkjcv7m6d4gth45jvtnjz7tugn", Network.RegTest);
            }
            else
            {
                throw new NotSupportedNetworkException(network);
            }

            Assert.Equal(expectedAddress1, address1);
            Assert.Equal(expectedAddress2, address2);
        }
Example #9
0
        public async Task TrezorTMockTestsAsync(Network network)
        {
            var client = new HwiClient(network, new HwiProcessBridgeMock(HardwareWalletModels.Trezor_T));

            using var cts = new CancellationTokenSource(ReasonableRequestTimeout);
            IEnumerable <HwiEnumerateEntry> enumerate = await client.EnumerateAsync(cts.Token);

            Assert.Single(enumerate);
            HwiEnumerateEntry entry = enumerate.Single();

            Assert.Equal(HardwareWalletModels.Trezor_T, entry.Model);
            Assert.Equal("webusb: 001:4", entry.Path);
            Assert.False(entry.NeedsPassphraseSent);
            Assert.False(entry.NeedsPinSent);
            Assert.NotNull(entry.Error);
            Assert.NotEmpty(entry.Error);
            Assert.Equal(HwiErrorCode.DeviceNotInitialized, entry.Code);
            Assert.False(entry.IsInitialized());
            Assert.Null(entry.Fingerprint);

            var deviceType = entry.Model;
            var devicePath = entry.Path;

            await client.WipeAsync(deviceType, devicePath, cts.Token);

            await client.SetupAsync(deviceType, devicePath, false, cts.Token);

            await client.RestoreAsync(deviceType, devicePath, false, cts.Token);

            // Trezor T doesn't support it.
            var promptpin = await Assert.ThrowsAsync <HwiException>(async() => await client.PromptPinAsync(deviceType, devicePath, cts.Token));

            Assert.Equal("The PIN has already been sent to this device", promptpin.Message);
            Assert.Equal(HwiErrorCode.DeviceAlreadyUnlocked, promptpin.ErrorCode);

            var sendpin = await Assert.ThrowsAsync <HwiException>(async() => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token));

            Assert.Equal("The PIN has already been sent to this device", sendpin.Message);
            Assert.Equal(HwiErrorCode.DeviceAlreadyUnlocked, sendpin.ErrorCode);

            KeyPath   keyPath1 = KeyManager.DefaultAccountKeyPath;
            KeyPath   keyPath2 = KeyManager.DefaultAccountKeyPath.Derive(1);
            ExtPubKey xpub1    = await client.GetXpubAsync(deviceType, devicePath, keyPath1, cts.Token);

            ExtPubKey xpub2 = await client.GetXpubAsync(deviceType, devicePath, keyPath2, cts.Token);

            var expecteXpub1 = NBitcoinHelpers.BetterParseExtPubKey("xpub6DHjDx4gzLV37gJWMxYJAqyKRGN46MT61RHVizdU62cbVUYu9L95cXKzX62yJ2hPbN11EeprS8sSn8kj47skQBrmycCMzFEYBQSntVKFQ5M");
            var expecteXpub2 = NBitcoinHelpers.BetterParseExtPubKey("xpub6FJS1ne3STcKdQ9JLXNzZXidmCNZ9dxLiy7WVvsRkcmxjJsrDKJKEAXq4MGyEBM3vHEw2buqXezfNK5SNBrkwK7Fxjz1TW6xzRr2pUyMWFu");

            Assert.Equal(expecteXpub1, xpub1);
            Assert.Equal(expecteXpub2, xpub2);

            BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token);

            BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath2, cts.Token);

            BitcoinAddress expectedAddress1;
            BitcoinAddress expectedAddress2;

            if (network == Network.Main)
            {
                expectedAddress1 = BitcoinAddress.Create("bc1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7fdevah", Network.Main);
                expectedAddress2 = BitcoinAddress.Create("bc1qmaveee425a5xjkjcv7m6d4gth45jvtnj23fzyf", Network.Main);
            }
            else if (network == Network.TestNet)
            {
                expectedAddress1 = BitcoinAddress.Create("tb1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7rtzlxy", Network.TestNet);
                expectedAddress2 = BitcoinAddress.Create("tb1qmaveee425a5xjkjcv7m6d4gth45jvtnjqhj3l6", Network.TestNet);
            }
            else if (network == Network.RegTest)
            {
                expectedAddress1 = BitcoinAddress.Create("bcrt1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7pzmj3d", Network.RegTest);
                expectedAddress2 = BitcoinAddress.Create("bcrt1qmaveee425a5xjkjcv7m6d4gth45jvtnjz7tugn", Network.RegTest);
            }
            else
            {
                throw new NotSupportedNetworkException(network);
            }

            Assert.Equal(expectedAddress1, address1);
            Assert.Equal(expectedAddress2, address2);
        }
    public async Task ColdCardMk1MockTestsAsync(Network network)
    {
        var client = new HwiClient(network, new HwiProcessBridgeMock(HardwareWalletModels.Coldcard));

        using var cts = new CancellationTokenSource(ReasonableRequestTimeout);
        IEnumerable <HwiEnumerateEntry> enumerate = await client.EnumerateAsync(cts.Token);

        Assert.Single(enumerate);
        HwiEnumerateEntry entry = enumerate.Single();

        Assert.Equal(HardwareWalletModels.Coldcard, entry.Model);
        string rawPath        = "\\\\\\\\?\\\\hid#vid_d13e&pid_cc10&mi_00#7&1b239988&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}";
        string normalizedPath = HwiParser.NormalizeRawDevicePath(rawPath);

        Assert.Equal(normalizedPath, entry.Path);
        Assert.Null(entry.NeedsPassphraseSent);
        Assert.Null(entry.NeedsPinSent);
        Assert.Null(entry.Error);
        Assert.Null(entry.Code);
        Assert.Equal("a3d0d797", entry.Fingerprint.ToString());
        Assert.True(entry.IsInitialized());

        var deviceType = entry.Model;
        var devicePath = entry.Path;

        var wipe = await Assert.ThrowsAsync <HwiException>(async() => await client.WipeAsync(deviceType, devicePath, cts.Token));

        Assert.Equal("The Coldcard does not support wiping via software", wipe.Message);
        Assert.Equal(HwiErrorCode.UnavailableAction, wipe.ErrorCode);

        // ColdCard doesn't support it.
        var setup = await Assert.ThrowsAsync <HwiException>(async() => await client.SetupAsync(deviceType, devicePath, false, cts.Token));

        Assert.Equal("The Coldcard does not support software setup", setup.Message);
        Assert.Equal(HwiErrorCode.UnavailableAction, setup.ErrorCode);

        // ColdCard doesn't support it.
        var restore = await Assert.ThrowsAsync <HwiException>(async() => await client.RestoreAsync(deviceType, devicePath, false, cts.Token));

        Assert.Equal("The Coldcard does not support restoring via software", restore.Message);
        Assert.Equal(HwiErrorCode.UnavailableAction, restore.ErrorCode);

        // ColdCard doesn't support it.
        var promptpin = await Assert.ThrowsAsync <HwiException>(async() => await client.PromptPinAsync(deviceType, devicePath, cts.Token));

        Assert.Equal("The Coldcard does not need a PIN sent from the host", promptpin.Message);
        Assert.Equal(HwiErrorCode.UnavailableAction, promptpin.ErrorCode);

        // ColdCard doesn't support it.
        var sendpin = await Assert.ThrowsAsync <HwiException>(async() => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token));

        Assert.Equal("The Coldcard does not need a PIN sent from the host", sendpin.Message);
        Assert.Equal(HwiErrorCode.UnavailableAction, sendpin.ErrorCode);

        KeyPath   keyPath1 = KeyManager.GetAccountKeyPath(network);
        KeyPath   keyPath2 = KeyManager.GetAccountKeyPath(network).Derive(1);
        ExtPubKey xpub1    = await client.GetXpubAsync(deviceType, devicePath, keyPath1, cts.Token);

        ExtPubKey xpub2 = await client.GetXpubAsync(deviceType, devicePath, keyPath2, cts.Token);

        ExtPubKey expectedXpub1;
        ExtPubKey expectedXpub2;

        if (network == Network.TestNet)
        {
            expectedXpub1 = NBitcoinHelpers.BetterParseExtPubKey("xpub6CaGC5LjEw1YWw8br7AURnB6ioJY2bEVApXh8NMsPQ9mdDbzN51iwVrnmGSof3MfjjRrntnE8mbYeTW5ywgvCXdjqF8meQEwnhPDQV2TW7c");
            expectedXpub2 = NBitcoinHelpers.BetterParseExtPubKey("xpub6E7pup6CRRS5jM1r3HVYQhHwQHpddJALjRDbsVDtsnQJozHrfE8Pua2X5JhtkWCxdcmGhPXWxV7DoJtSgZSUvUy6cvDchVQt2RGEd4mD4FA");
        }
        else
        {
            expectedXpub1 = NBitcoinHelpers.BetterParseExtPubKey("xpub6DHjDx4gzLV37gJWMxYJAqyKRGN46MT61RHVizdU62cbVUYu9L95cXKzX62yJ2hPbN11EeprS8sSn8kj47skQBrmycCMzFEYBQSntVKFQ5M");
            expectedXpub2 = NBitcoinHelpers.BetterParseExtPubKey("xpub6FJS1ne3STcKdQ9JLXNzZXidmCNZ9dxLiy7WVvsRkcmxjJsrDKJKEAXq4MGyEBM3vHEw2buqXezfNK5SNBrkwK7Fxjz1TW6xzRr2pUyMWFu");
        }
        Assert.Equal(expectedXpub1, xpub1);
        Assert.Equal(expectedXpub2, xpub2);

        BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token);

        BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath2, cts.Token);

        BitcoinAddress expectedAddress1;
        BitcoinAddress expectedAddress2;

        if (network == Network.Main)
        {
            expectedAddress1 = BitcoinAddress.Create("bc1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7fdevah", Network.Main);
            expectedAddress2 = BitcoinAddress.Create("bc1qmaveee425a5xjkjcv7m6d4gth45jvtnj23fzyf", Network.Main);
        }
        else if (network == Network.TestNet)
        {
            expectedAddress1 = BitcoinAddress.Create("tb1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7rtzlxy", Network.TestNet);
            expectedAddress2 = BitcoinAddress.Create("tb1qmaveee425a5xjkjcv7m6d4gth45jvtnjqhj3l6", Network.TestNet);
        }
        else if (network == Network.RegTest)
        {
            expectedAddress1 = BitcoinAddress.Create("bcrt1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7pzmj3d", Network.RegTest);
            expectedAddress2 = BitcoinAddress.Create("bcrt1qmaveee425a5xjkjcv7m6d4gth45jvtnjz7tugn", Network.RegTest);
        }
        else
        {
            throw new NotSupportedNetworkException(network);
        }

        Assert.Equal(expectedAddress1, address1);
        Assert.Equal(expectedAddress2, address2);
    }
Example #11
0
        public async Task <IActionResult> VaultBridgeConnection(string cryptoCode = null,
                                                                [ModelBinder(typeof(WalletIdModelBinder))]
                                                                WalletId walletId = null)
        {
            if (!HttpContext.WebSockets.IsWebSocketRequest)
            {
                return(NotFound());
            }
            cryptoCode = cryptoCode ?? walletId.CryptoCode;
            using (var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10)))
            {
                var cancellationToken = cts.Token;
                var network           = Networks.GetNetwork <BTCPayNetwork>(cryptoCode);
                if (network == null)
                {
                    return(NotFound());
                }
                var websocket = await HttpContext.WebSockets.AcceptWebSocketAsync();

                var hwi = new Hwi.HwiClient(network.NBitcoinNetwork)
                {
                    Transport = new HwiWebSocketTransport(websocket)
                };
                Hwi.HwiDeviceClient device      = null;
                HwiEnumerateEntry   deviceEntry = null;
                HDFingerprint?      fingerprint = null;
                string password        = null;
                int?   pin             = null;
                var    websocketHelper = new WebSocketHelper(websocket);

                async Task <bool> RequireDeviceUnlocking()
                {
                    if (deviceEntry == null)
                    {
                        await websocketHelper.Send("{ \"error\": \"need-device\"}", cancellationToken);

                        return(true);
                    }
                    if (deviceEntry.NeedsPinSent is true && pin is null)
                    {
                        await websocketHelper.Send("{ \"error\": \"need-pin\"}", cancellationToken);

                        return(true);
                    }
                    if (deviceEntry.NeedsPassphraseSent is true && password == null)
                    {
                        await websocketHelper.Send("{ \"error\": \"need-passphrase\"}", cancellationToken);

                        return(true);
                    }
                    return(false);
                }

                JObject o = null;
                try
                {
                    while (true)
                    {
                        var command = await websocketHelper.NextMessageAsync(cancellationToken);

                        switch (command)
                        {
                        case "set-passphrase":
                            device.Password = await websocketHelper.NextMessageAsync(cancellationToken);

                            password = device.Password;
                            break;

                        case "ask-sign":
                            if (await RequireDeviceUnlocking())
                            {
                                continue;
                            }
                            if (walletId == null)
                            {
                                await websocketHelper.Send("{ \"error\": \"invalid-walletId\"}", cancellationToken);

                                continue;
                            }
                            if (fingerprint is null)
                            {
                                fingerprint = (await device.GetXPubAsync(new KeyPath("44'"), cancellationToken)).ExtPubKey.ParentFingerprint;
                            }
                            await websocketHelper.Send("{ \"info\": \"ready\"}", cancellationToken);

                            o = JObject.Parse(await websocketHelper.NextMessageAsync(cancellationToken));
                            var authorization = await _authorizationService.AuthorizeAsync(User, Policies.CanModifyStoreSettings.Key);

                            if (!authorization.Succeeded)
                            {
                                await websocketHelper.Send("{ \"error\": \"not-authorized\"}", cancellationToken);

                                continue;
                            }
                            var psbt = PSBT.Parse(o["psbt"].Value <string>(), network.NBitcoinNetwork);
                            var derivationSettings = GetDerivationSchemeSettings(walletId);
                            derivationSettings.RebaseKeyPaths(psbt);
                            var signing = derivationSettings.GetSigningAccountKeySettings();
                            if (signing.GetRootedKeyPath()?.MasterFingerprint != fingerprint)
                            {
                                await websocketHelper.Send("{ \"error\": \"wrong-wallet\"}", cancellationToken);

                                continue;
                            }
                            try
                            {
                                psbt = await device.SignPSBTAsync(psbt, cancellationToken);
                            }
                            catch (Hwi.HwiException)
                            {
                                await websocketHelper.Send("{ \"error\": \"user-reject\"}", cancellationToken);

                                continue;
                            }
                            o = new JObject();
                            o.Add("psbt", psbt.ToBase64());
                            await websocketHelper.Send(o.ToString(), cancellationToken);

                            break;

                        case "ask-pin":
                            if (device == null)
                            {
                                await websocketHelper.Send("{ \"error\": \"need-device\"}", cancellationToken);

                                continue;
                            }
                            await device.PromptPinAsync(cancellationToken);

                            await websocketHelper.Send("{ \"info\": \"prompted, please input the pin\"}", cancellationToken);

                            pin = int.Parse(await websocketHelper.NextMessageAsync(cancellationToken), CultureInfo.InvariantCulture);
                            if (await device.SendPinAsync(pin.Value, cancellationToken))
                            {
                                await websocketHelper.Send("{ \"info\": \"the pin is correct\"}", cancellationToken);
                            }
                            else
                            {
                                await websocketHelper.Send("{ \"error\": \"incorrect-pin\"}", cancellationToken);

                                continue;
                            }
                            break;

                        case "ask-xpubs":
                            if (await RequireDeviceUnlocking())
                            {
                                continue;
                            }
                            JObject          result  = new JObject();
                            var              factory = network.NBXplorerNetwork.DerivationStrategyFactory;
                            var              keyPath = new KeyPath("84'").Derive(network.CoinType).Derive(0, true);
                            BitcoinExtPubKey xpub    = await device.GetXPubAsync(keyPath);

                            if (fingerprint is null)
                            {
                                fingerprint = (await device.GetXPubAsync(new KeyPath("44'"), cancellationToken)).ExtPubKey.ParentFingerprint;
                            }
                            result["fingerprint"] = fingerprint.Value.ToString();
                            var strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
                            {
                                ScriptPubKeyType = ScriptPubKeyType.Segwit
                            });
                            AddDerivationSchemeToJson("segwit", result, keyPath, xpub, strategy);
                            keyPath = new KeyPath("49'").Derive(network.CoinType).Derive(0, true);
                            xpub    = await device.GetXPubAsync(keyPath);

                            strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
                            {
                                ScriptPubKeyType = ScriptPubKeyType.SegwitP2SH
                            });
                            AddDerivationSchemeToJson("segwitWrapped", result, keyPath, xpub, strategy);
                            keyPath = new KeyPath("44'").Derive(network.CoinType).Derive(0, true);
                            xpub    = await device.GetXPubAsync(keyPath);

                            strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
                            {
                                ScriptPubKeyType = ScriptPubKeyType.Legacy
                            });
                            AddDerivationSchemeToJson("legacy", result, keyPath, xpub, strategy);
                            await websocketHelper.Send(result.ToString(), cancellationToken);

                            break;

                        case "ask-device":
                            password    = null;
                            pin         = null;
                            deviceEntry = null;
                            device      = null;
                            var entries = (await hwi.EnumerateEntriesAsync(cancellationToken)).ToList();
                            deviceEntry = entries.FirstOrDefault();
                            if (deviceEntry == null)
                            {
                                await websocketHelper.Send("{ \"error\": \"no-device\"}", cancellationToken);

                                continue;
                            }
                            device      = new HwiDeviceClient(hwi, deviceEntry.DeviceSelector, deviceEntry.Model, deviceEntry.Fingerprint);
                            fingerprint = device.Fingerprint;
                            JObject json = new JObject();
                            json.Add("model", device.Model.ToString());
                            json.Add("fingerprint", device.Fingerprint?.ToString());
                            await websocketHelper.Send(json.ToString(), cancellationToken);

                            break;
                        }
                    }
                }
                catch (Exception ex)
                {
                    JObject obj = new JObject();
                    obj.Add("error", "unknown-error");
                    obj.Add("details", ex.ToString());
                    try
                    {
                        await websocketHelper.Send(obj.ToString(), cancellationToken);
                    }
                    catch { }
                }
                finally
                {
                    await websocketHelper.DisposeAsync(cancellationToken);
                }
            }
            return(new EmptyResult());
        }