Start writing Trezor One Kata
MODIFY
WalletWasabi.Tests/HwiTests/DeviceConnectedTests/Katas.cs
WalletWasabi.Tests/HwiTests/DeviceConnectedTests/Katas.cs
@@ -51,19 +51,16 @@ namespace WalletWasabi.Tests.HwiTests.DeviceConnectedTests
Assert.Single(enumerate);
HwiEnumerateEntry entry = enumerate.Single();
Assert.NotNull(entry.Path);
- Assert.True(entry.Type.HasValue);
+ Assert.Equal(HardwareWalletVendors.Trezor, entry.Type);
Assert.True(entry.Fingerprint.HasValue);
string devicePath = entry.Path;
HardwareWalletVendors deviceType = entry.Type.Value;
HDFingerprint fingerprint = entry.Fingerprint.Value;
- await Assert.ThrowsAsync<HwiException>(async () => await client.SetupAsync(deviceType, devicePath, cts.Token));
+ await Assert.ThrowsAsync<HwiException>(async () => await client.SetupAsync(deviceType, devicePath, false, cts.Token));
- await Assert.ThrowsAsync<HwiException>(async () => await client.RestoreAsync(deviceType, devicePath, cts.Token));
-
- // Trezor T doesn't support it.
- await Assert.ThrowsAsync<HwiException>(async () => await client.BackupAsync(deviceType, devicePath, 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));
@@ -109,6 +106,38 @@ namespace WalletWasabi.Tests.HwiTests.DeviceConnectedTests
}
}
+ [Fact]
+ public async Task TrezorOneKataAsync()
+ {
+ // --- USER INTERACTIONS ---
+ //
+ // Connect an already initialized device. Don't unlock it.
+ // Run this test.
+ //
+ // --- 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(HardwareWalletVendors.Trezor, entry.Type);
+ Assert.True(entry.NeedsPinSent);
+ Assert.Equal(HwiErrorCode.DeviceNotReady, entry.Code);
+ Assert.Null(entry.Fingerprint);
+
+ string devicePath = entry.Path;
+ HardwareWalletVendors deviceType = entry.Type.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));
+ }
+ }
+
private static PSBT BuildPsbt(Network network, HDFingerprint fingerprint, ExtPubKey xpub, KeyPath xpubKeyPath)
{
var deriveSendFromKeyPath = new KeyPath("1/0");

MODIFY
WalletWasabi.Tests/HwiTests/NoDeviceConnectedTests/HwiDefaultResponseTests.cs
WalletWasabi.Tests/HwiTests/NoDeviceConnectedTests/HwiDefaultResponseTests.cs
@@ -63,7 +63,7 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
[Theory]
[MemberData(nameof(GetHwiClientConfigurationCombinationValues))]
- public async Task CanEnumerateTestsAsync(HwiClient client)
+ public async Task CanEnumerateAsync(HwiClient client)
{
using (var cts = new CancellationTokenSource(ReasonableRequestTimeout))
{
@@ -82,7 +82,6 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
await Assert.ThrowsAsync<OperationCanceledException>(async () => await client.GetVersionAsync(cts.Token));
await Assert.ThrowsAsync<OperationCanceledException>(async () => await client.GetHelpAsync(cts.Token));
await Assert.ThrowsAsync<OperationCanceledException>(async () => await client.EnumerateAsync(cts.Token));
- await Assert.ThrowsAsync<OperationCanceledException>(async () => await client.SetupAsync(cts.Token));
}
}
@@ -98,10 +97,10 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
foreach (var wrongDevicePath in wrongDeviePaths)
{
await Assert.ThrowsAsync<ArgumentException>(async () => await client.WipeAsync(deviceType, wrongDevicePath, cts.Token));
- await Assert.ThrowsAsync<ArgumentException>(async () => await client.SetupAsync(deviceType, wrongDevicePath, cts.Token));
+ await Assert.ThrowsAsync<ArgumentException>(async () => await client.SetupAsync(deviceType, wrongDevicePath, false, cts.Token));
}
await Assert.ThrowsAsync<ArgumentNullException>(async () => await client.WipeAsync(deviceType, null, cts.Token));
- await Assert.ThrowsAsync<ArgumentNullException>(async () => await client.SetupAsync(deviceType, null, cts.Token));
+ await Assert.ThrowsAsync<ArgumentNullException>(async () => await client.SetupAsync(deviceType, null, false, cts.Token));
}
}
}
@@ -119,9 +118,7 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
client.GetHelpAsync(cts.Token),
client.GetHelpAsync(cts.Token),
client.EnumerateAsync(cts.Token),
- client.EnumerateAsync(cts.Token),
- client.SetupAsync(cts.Token),
- client.SetupAsync(cts.Token)
+ client.EnumerateAsync(cts.Token)
};
cts.CancelAfter(ReasonableRequestTimeout * tasks.Count);
@@ -130,24 +127,26 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
}
}
- [Theory]
- [MemberData(nameof(GetHwiClientConfigurationCombinationValues))]
- public async Task ThrowsHwiExceptionsAsync(HwiClient client)
+ [Fact]
+ public async Task HwiProcessBridgeTestAsync()
{
+ HwiProcessBridge pb = new HwiProcessBridge();
+
using (var cts = new CancellationTokenSource(ReasonableRequestTimeout))
{
- await Assert.ThrowsAsync<HwiException>(async () => await client.SetupAsync(cts.Token));
+ var res = await pb.SendCommandAsync("enumerate", false, cts.Token);
+ Assert.NotEmpty(res.response);
}
}
[Fact]
- public async Task HwiProcessBridgeTestAsync()
+ public async Task OpenConsoleDoesntThrowAsync()
{
HwiProcessBridge pb = new HwiProcessBridge();
using (var cts = new CancellationTokenSource(ReasonableRequestTimeout))
{
- var res = await pb.SendCommandAsync("enumerate", cts.Token);
+ var res = await pb.SendCommandAsync("enumerate", true, cts.Token);
Assert.NotEmpty(res.response);
}
}

RENAME
WalletWasabi.Tests/HwiTests/NoDeviceConnectedTests/IMockHwiProcessBridge.cs
WalletWasabi.Tests/HwiTests/NoDeviceConnectedTests/HwiProcessBridgeMock.cs
@@ -8,17 +8,22 @@ using WalletWasabi.Interfaces;
namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
{
- public class IMockHwiProcessBridge : IProcessBridge
+ public class HwiProcessBridgeMock : IProcessBridge
{
public HardwareWalletModels Model { get; }
- public IMockHwiProcessBridge(HardwareWalletModels model)
+ public HwiProcessBridgeMock(HardwareWalletModels model)
{
Model = model;
}
- public Task<(string response, int exitCode)> SendCommandAsync(string arguments, CancellationToken cancel)
+ public Task<(string response, int exitCode)> SendCommandAsync(string arguments, bool openConsole, CancellationToken cancel)
{
+ if (openConsole)
+ {
+ throw new NotImplementedException($"Cannot mock {nameof(openConsole)} mode.");
+ }
+
if (CompareArguments(arguments, "enumerate"))
{
if (Model == HardwareWalletModels.TrezorT)

MODIFY
WalletWasabi.Tests/HwiTests/NoDeviceConnectedTests/MockedDeviceTests.cs
WalletWasabi.Tests/HwiTests/NoDeviceConnectedTests/MockedDeviceTests.cs
@@ -28,7 +28,7 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
[MemberData(nameof(GetDifferentNetworkValues))]
public async Task TrezorTMockTestsAsync(Network network)
{
- var client = new HwiClient(network, new IMockHwiProcessBridge(HardwareWalletModels.TrezorT));
+ var client = new HwiClient(network, new HwiProcessBridgeMock(HardwareWalletModels.TrezorT));
using (var cts = new CancellationTokenSource(ReasonableRequestTimeout))
{
@@ -48,13 +48,8 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
var devicePath = entry.Path;
await client.WipeAsync(deviceType, devicePath, cts.Token);
- await client.SetupAsync(deviceType, devicePath, cts.Token);
- await client.RestoreAsync(deviceType, devicePath, cts.Token);
-
- // Trezor T doesn't support it.
- var backup = await Assert.ThrowsAsync<HwiException>(async () => await client.BackupAsync(deviceType, devicePath, cts.Token));
- Assert.Equal("The Trezor does not support creating a backup via software", backup.Message);
- Assert.Equal(HwiErrorCode.UnavailableAction, backup.ErrorCode);
+ 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));

MODIFY
WalletWasabi/Hwi2/HwiClient.cs
WalletWasabi/Hwi2/HwiClient.cs
@@ -41,13 +41,13 @@ namespace WalletWasabi.Hwi2
#region Commands
- private async Task<string> SendCommandAsync(IEnumerable<HwiOption> options, HwiCommands? command, string commandArguments, CancellationToken cancel)
+ private async Task<string> SendCommandAsync(IEnumerable<HwiOption> options, HwiCommands? command, string commandArguments, bool openConsole, CancellationToken cancel)
{
string arguments = HwiParser.ToArgumentString(Network, options, command, commandArguments);
try
{
- (string responseString, int exitCode) = await Bridge.SendCommandAsync(arguments, cancel).ConfigureAwait(false);
+ (string responseString, int exitCode) = await Bridge.SendCommandAsync(arguments, openConsole, cancel).ConfigureAwait(false);
if (exitCode != 0)
{
@@ -84,6 +84,7 @@ namespace WalletWasabi.Hwi2
options: BuildOptions(deviceType, devicePath, fingerprint),
command: HwiCommands.PromptPin,
commandArguments: null,
+ openConsole: false,
cancel).ConfigureAwait(false);
}
@@ -99,6 +100,7 @@ namespace WalletWasabi.Hwi2
options: BuildOptions(deviceType, devicePath, fingerprint),
command: HwiCommands.SendPin,
commandArguments: pin.ToString(),
+ openConsole: false,
cancel).ConfigureAwait(false);
}
@@ -115,6 +117,7 @@ namespace WalletWasabi.Hwi2
options: BuildOptions(deviceType, devicePath, fingerprint),
command: HwiCommands.GetXpub,
commandArguments: keyPathString,
+ openConsole: false,
cancel).ConfigureAwait(false);
var extPubKey = HwiParser.ParseExtPubKey(response);
@@ -134,6 +137,7 @@ namespace WalletWasabi.Hwi2
options: BuildOptions(deviceType, devicePath, fingerprint),
command: HwiCommands.DisplayAddress,
commandArguments: $"--path {keyPath.ToString(true, "h")} --wpkh",
+ openConsole: false,
cancel).ConfigureAwait(false);
var address = HwiParser.ParseAddress(response, Network) as BitcoinWitPubKeyAddress;
@@ -157,6 +161,7 @@ namespace WalletWasabi.Hwi2
options: BuildOptions(deviceType, devicePath, fingerprint),
command: HwiCommands.SignTx,
commandArguments: psbtString,
+ openConsole: false,
cancel).ConfigureAwait(false);
PSBT signedPsbt = HwiParser.ParsePsbt(response, Network);
@@ -170,74 +175,43 @@ namespace WalletWasabi.Hwi2
}
public async Task WipeAsync(HardwareWalletVendors deviceType, string devicePath, CancellationToken cancel)
- => await WipeImplAsync(deviceType, devicePath, null, cancel);
-
- public async Task WipeAsync(HDFingerprint fingerprint, CancellationToken cancel)
- => await WipeImplAsync(null, null, fingerprint, cancel);
-
- private async Task WipeImplAsync(HardwareWalletVendors? deviceType, string devicePath, HDFingerprint? fingerprint, CancellationToken cancel)
{
await SendCommandAsync(
- options: BuildOptions(deviceType, devicePath, fingerprint),
+ options: BuildOptions(deviceType, devicePath, null),
command: HwiCommands.Wipe,
commandArguments: null,
+ openConsole: false,
cancel).ConfigureAwait(false);
}
- public async Task SetupAsync(HardwareWalletVendors deviceType, string devicePath, CancellationToken cancel)
- => await SetupImplAsync(deviceType, devicePath, null, cancel);
-
- public async Task SetupAsync(HDFingerprint fingerprint, CancellationToken cancel)
- => await SetupImplAsync(null, null, fingerprint, cancel);
-
- private async Task SetupImplAsync(HardwareWalletVendors? deviceType, string devicePath, HDFingerprint? fingerprint, CancellationToken cancel)
+ public async Task SetupAsync(HardwareWalletVendors deviceType, string devicePath, bool openConsole, CancellationToken cancel)
{
await SendCommandAsync(
- options: BuildOptions(deviceType, devicePath, fingerprint, HwiOption.Interactive),
+ options: BuildOptions(deviceType, devicePath, null, HwiOption.Interactive),
command: HwiCommands.Setup,
commandArguments: null,
+ openConsole: openConsole,
cancel).ConfigureAwait(false);
}
- public async Task RestoreAsync(HardwareWalletVendors deviceType, string devicePath, CancellationToken cancel)
- => await RestoreImplAsync(deviceType, devicePath, null, cancel);
-
- public async Task RestoreAsync(HDFingerprint fingerprint, CancellationToken cancel)
- => await RestoreImplAsync(null, null, fingerprint, cancel);
-
- private async Task RestoreImplAsync(HardwareWalletVendors? deviceType, string devicePath, HDFingerprint? fingerprint, CancellationToken cancel)
+ public async Task RestoreAsync(HardwareWalletVendors deviceType, string devicePath, bool openConsole, CancellationToken cancel)
{
await SendCommandAsync(
- options: BuildOptions(deviceType, devicePath, fingerprint, HwiOption.Interactive),
+ options: BuildOptions(deviceType, devicePath, null, HwiOption.Interactive),
command: HwiCommands.Restore,
commandArguments: null,
+ openConsole: openConsole,
cancel).ConfigureAwait(false);
}
- public async Task BackupAsync(HardwareWalletVendors deviceType, string devicePath, CancellationToken cancel)
- => await BackupImplAsync(deviceType, devicePath, null, cancel);
-
- public async Task BackupAsync(HDFingerprint fingerprint, CancellationToken cancel)
- => await BackupImplAsync(null, null, fingerprint, cancel);
-
- private async Task BackupImplAsync(HardwareWalletVendors? deviceType, string devicePath, HDFingerprint? fingerprint, CancellationToken cancel)
- {
- if (deviceType == HardwareWalletVendors.Trezor)
+ public async Task<Version> GetVersionAsync(CancellationToken cancel)
{
- // HWI would throw the same, don't need the roundtrip.
- throw new HwiException(HwiErrorCode.UnavailableAction, "The Trezor does not support creating a backup via software");
- }
-
- await SendCommandAsync(
- options: BuildOptions(deviceType, devicePath, fingerprint, HwiOption.Interactive),
- command: HwiCommands.Backup,
+ string responseString = await SendCommandAsync(
+ options: new[] { HwiOption.Version },
+ command: null,
commandArguments: null,
+ openConsole: false,
cancel).ConfigureAwait(false);
- }
-
- public async Task<Version> GetVersionAsync(CancellationToken cancel)
- {
- string responseString = await SendCommandAsync(options: new[] { HwiOption.Version }, command: null, commandArguments: null, cancel).ConfigureAwait(false);
var version = HwiParser.ParseVersion(responseString);
return version;
@@ -245,25 +219,29 @@ namespace WalletWasabi.Hwi2
public async Task<string> GetHelpAsync(CancellationToken cancel)
{
- string responseString = await SendCommandAsync(options: new[] { HwiOption.Help }, command: null, commandArguments: null, cancel).ConfigureAwait(false);
+ string responseString = await SendCommandAsync(
+ options: new[] { HwiOption.Help },
+ command: null,
+ commandArguments: null,
+ openConsole: false,
+ cancel).ConfigureAwait(false);
return responseString;
}
public async Task<IEnumerable<HwiEnumerateEntry>> EnumerateAsync(CancellationToken cancel)
{
- string responseString = await SendCommandAsync(options: null, command: HwiCommands.Enumerate, commandArguments: null, cancel).ConfigureAwait(false);
+ string responseString = await SendCommandAsync(
+ options: null,
+ command: HwiCommands.Enumerate,
+ commandArguments: null,
+ openConsole: false,
+ cancel).ConfigureAwait(false);
IEnumerable<HwiEnumerateEntry> response = HwiParser.ParseHwiEnumerateResponse(responseString);
return response;
}
- public async Task<string> SetupAsync(CancellationToken cancel)
- {
- string responseString = await SendCommandAsync(options: null, command: HwiCommands.Setup, commandArguments: null, cancel).ConfigureAwait(false);
- return responseString;
- }
-
#endregion Commands
#region Helpers

DELETE
WalletWasabi/Hwi2/ProcessBridge/HwiMockBridge.cs
-using System;
-using System.Collections.Generic;
-using System.Text;
-using System.Threading;
-using System.Threading.Tasks;
-using WalletWasabi.Interfaces;
-
-namespace WalletWasabi.Hwi2.ProcessBridge
-{
- public class HwiMockBridge : IProcessBridge
- {
- public Task<(string response, int exitCode)> SendCommandAsync(string arguments, CancellationToken cancel)
- {
- return Task.FromResult(("mock mock", 0));
- }
- }
-}

MODIFY
WalletWasabi/Hwi2/ProcessBridge/HwiProcessBridge.cs
WalletWasabi/Hwi2/ProcessBridge/HwiProcessBridge.cs
@@ -13,28 +13,43 @@ namespace WalletWasabi.Hwi2.ProcessBridge
{
public class HwiProcessBridge : IProcessBridge
{
- public async Task<(string response, int exitCode)> SendCommandAsync(string arguments, CancellationToken cancel)
+ public async Task<(string response, int exitCode)> SendCommandAsync(string arguments, bool openConsole, CancellationToken cancel)
{
string responseString;
int exitCode;
string hwiPath = GetHwiPath();
+
+ var redirectStandardOutput = !openConsole;
+ var useShellExecute = openConsole;
+ var createNoWindow = !openConsole;
+ var windowStyle = openConsole ? ProcessWindowStyle.Normal : ProcessWindowStyle.Hidden;
+
using (var process = Process.Start(
new ProcessStartInfo
{
FileName = hwiPath,
Arguments = arguments,
- RedirectStandardOutput = true,
- UseShellExecute = false,
- CreateNoWindow = true,
- WindowStyle = ProcessWindowStyle.Hidden
+ RedirectStandardOutput = redirectStandardOutput,
+ UseShellExecute = useShellExecute,
+ CreateNoWindow = createNoWindow,
+ WindowStyle = windowStyle
}
))
{
await process.WaitForExitAsync(cancel).ConfigureAwait(false);
exitCode = process.ExitCode;
+ if (redirectStandardOutput)
+ {
responseString = await process.StandardOutput.ReadToEndAsync().ConfigureAwait(false);
}
+ else
+ {
+ responseString = exitCode == 0 ?
+ "{\"success\":\"true\"}" :
+ $"{{\"success\":\"false\",\"error\":\"Process terminated with exit code: {exitCode}.\"}}";
+ }
+ }
return (responseString, exitCode);
}

MODIFY
WalletWasabi/Interfaces/IProcessBridge.cs
WalletWasabi/Interfaces/IProcessBridge.cs
@@ -8,6 +8,6 @@ namespace WalletWasabi.Interfaces
{
public interface IProcessBridge
{
- Task<(string response, int exitCode)> SendCommandAsync(string arguments, CancellationToken cancel);
+ Task<(string response, int exitCode)> SendCommandAsync(string arguments, bool openConsole, CancellationToken cancel);
}
}

Start writing Trezor One Kata