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()); }
private static bool IsTrezorT(HwiEnumerateEntry deviceEntry) { return(deviceEntry.Model.Contains("Trezor_T", StringComparison.OrdinalIgnoreCase)); }
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); }
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); }
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); }
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); }
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); }
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()); }