Start writing trezor one mocks
MODIFY
WalletWasabi.Tests/HwiTests/NoDeviceConnectedTests/HwiProcessBridgeMock.cs
WalletWasabi.Tests/HwiTests/NoDeviceConnectedTests/HwiProcessBridgeMock.cs
@@ -4,6 +4,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WalletWasabi.Hwi2.Models;
+using WalletWasabi.Hwi2.Parsers;
using WalletWasabi.Interfaces;
namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
@@ -24,70 +25,77 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
throw new NotImplementedException($"Cannot mock {nameof(openConsole)} mode.");
}
- if (CompareArguments(arguments, "enumerate"))
- {
+ string type = null;
+ string rawPath = null;
+
if (Model == HardwareWalletModels.TrezorT)
{
- var response = "[{\"type\": \"trezor\", \"path\": \"webusb: 001:4\", \"needs_pin_sent\": false, \"needs_passphrase_sent\": false, \"error\": \"Not initialized\"}]";
- var code = 0;
- return Task.FromResult((response, code));
+ type = "trezor";
+ rawPath = "webusb: 001:4";
}
+ else if (Model == HardwareWalletModels.TrezorOne)
+ {
+ type = "trezor";
+ rawPath = "hid:\\\\\\\\?\\\\hid#vid_534c&pid_0001&mi_00#7&6f0b727&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}";
}
- else if (CompareArguments(arguments, "--device-path \"webusb: 001:4\" --device-type \"trezor\" wipe"))
+
+ string path = HwiParser.NormalizeRawDevicePath(rawPath);
+ string devicePathAndTypeArgumentString = $"--device-path \"{path}\" --device-type \"{type}\"";
+
+ const string successTrueResponse = "{\"success\": true}\r\n";
+
+ if (CompareArguments(arguments, "enumerate"))
{
if (Model == HardwareWalletModels.TrezorT)
{
- var response = "{\"success\": true}\r\n";
+ var response = $"[{{\"type\": \"{type}\", \"path\": \"{rawPath}\", \"needs_pin_sent\": false, \"needs_passphrase_sent\": false, \"error\": \"Not initialized\"}}]";
var code = 0;
return Task.FromResult((response, code));
}
- }
- else if (CompareArguments(arguments, "--device-path \"webusb: 001:4\" --device-type \"trezor\" setup"))
- {
- if (Model == HardwareWalletModels.TrezorT)
+ else if (Model == HardwareWalletModels.TrezorOne)
{
- var response = "{\"error\": \"setup requires interactive mode\", \"code\": -9}";
+ var response = $"[{{\"type\": \"{type}\", \"path\": \"{rawPath}\", \"needs_pin_sent\": true, \"needs_passphrase_sent\": false, \"error\": \"Could not open client or get fingerprint information: Trezor is locked. Unlock by using 'promptpin' and then 'sendpin'.\", \"code\": -12}}]\r\n";
var code = 0;
return Task.FromResult((response, code));
}
}
- else if (CompareArguments(arguments, "--device-path \"webusb: 001:4\" --device-type \"trezor\" --interactive setup"))
+ else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} wipe"))
{
- if (Model == HardwareWalletModels.TrezorT)
+ if (Model == HardwareWalletModels.TrezorT || Model == HardwareWalletModels.TrezorOne)
{
- var response = "{\"success\": \"true\"\r\n}";
+ var response = successTrueResponse;
var code = 0;
return Task.FromResult((response, code));
}
}
- else if (CompareArguments(arguments, "--device-path \"webusb: 001:4\" --device-type \"trezor\" --interactive restore"))
+ else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} setup"))
{
if (Model == HardwareWalletModels.TrezorT)
{
- var response = "{\"success\": \"true\"\r\n}";
+ var response = "{\"error\": \"setup requires interactive mode\", \"code\": -9}";
var code = 0;
return Task.FromResult((response, code));
}
}
- else if (CompareArguments(arguments, "--device-path \"webusb: 001:4\" --device-type \"trezor\" --interactive backup"))
+ else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} --interactive setup"))
{
if (Model == HardwareWalletModels.TrezorT)
{
- var response = "{\"error\": \"The Trezor does not support creating a backup via software\", \"code\": -9}";
+ var response = successTrueResponse;
var code = 0;
return Task.FromResult((response, code));
}
}
- else if (CompareArguments(arguments, "--device-path \"webusb: 001:4\" --device-type \"trezor\" backup"))
+ else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} --interactive restore"))
{
if (Model == HardwareWalletModels.TrezorT)
{
- var response = "{\"error\": \"The Trezor does not support creating a backup via software\", \"code\": -9}";
+ var response = successTrueResponse;
var code = 0;
return Task.FromResult((response, code));
}
}
- else if (CompareArguments(arguments, "--device-path \"webusb: 001:4\" --device-type \"trezor\" promptpin"))
+ else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} promptpin"))
{
if (Model == HardwareWalletModels.TrezorT)
{
@@ -96,7 +104,7 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
return Task.FromResult((response, code));
}
}
- else if (CompareArguments(arguments, "--device-path \"webusb: 001:4\" --device-type \"trezor\" sendpin", true))
+ else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} sendpin", true))
{
if (Model == HardwareWalletModels.TrezorT)
{
@@ -105,25 +113,16 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
return Task.FromResult((response, code));
}
}
- else if (CompareArguments(arguments, "--device-path \"webusb: 001:4\" --device-type \"trezor\" getxpub m/84h/0h/0h", false))
+ else if (CompareGetXbpubArguments(arguments, out string xpub))
{
if (Model == HardwareWalletModels.TrezorT)
{
- var response = "{\"xpub\": \"xpub6DHjDx4gzLV37gJWMxYJAqyKRGN46MT61RHVizdU62cbVUYu9L95cXKzX62yJ2hPbN11EeprS8sSn8kj47skQBrmycCMzFEYBQSntVKFQ5M\"}\r\n";
+ var response = $"{{\"xpub\": \"{xpub}\"}}\r\n";
var code = 0;
return Task.FromResult((response, code));
}
}
- else if (CompareArguments(arguments, "--device-path \"webusb: 001:4\" --device-type \"trezor\" getxpub m/84h/0h/0h/1", false))
- {
- if (Model == HardwareWalletModels.TrezorT)
- {
- var response = "{\"xpub\": \"xpub6FJS1ne3STcKdQ9JLXNzZXidmCNZ9dxLiy7WVvsRkcmxjJsrDKJKEAXq4MGyEBM3vHEw2buqXezfNK5SNBrkwK7Fxjz1TW6xzRr2pUyMWFu\"}\r\n";
- var code = 0;
- return Task.FromResult((response, code));
- }
- }
- else if (CompareArguments(out bool t1, arguments, "--device-path \"webusb: 001:4\" --device-type \"trezor\" displayaddress --path m/84h/0h/0h --wpkh", false))
+ else if (CompareArguments(out bool t1, arguments, $"{devicePathAndTypeArgumentString} displayaddress --path m/84h/0h/0h --wpkh", false))
{
if (Model == HardwareWalletModels.TrezorT)
{
@@ -134,7 +133,7 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
return Task.FromResult((response, code));
}
}
- else if (CompareArguments(out bool t2, arguments, "--device-path \"webusb: 001:4\" --device-type \"trezor\" displayaddress --path m/84h/0h/0h/1 --wpkh", false))
+ else if (CompareArguments(out bool t2, arguments, $"{devicePathAndTypeArgumentString} displayaddress --path m/84h/0h/0h/1 --wpkh", false))
{
if (Model == HardwareWalletModels.TrezorT)
{
@@ -186,5 +185,28 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
private static bool CompareArguments(string arguments, string desired, bool useStartWith = false)
=> CompareArguments(out _, arguments, desired, useStartWith);
+
+ private static bool CompareGetXbpubArguments(string arguments, out string extPubKey)
+ {
+ extPubKey = null;
+ string command = "getxpub";
+ if (arguments.Contains(command, StringComparison.Ordinal)
+ && (arguments.Contains("--device-path", StringComparison.Ordinal) && arguments.Contains("--device-type", StringComparison.Ordinal)
+ || arguments.Contains("--fingerprint")))
+ {
+ // The +1 is the space.
+ var keyPath = arguments.Substring(arguments.IndexOf(command) + command.Length + 1);
+ if (keyPath == "m/84h/0h/0h")
+ {
+ extPubKey = "xpub6DHjDx4gzLV37gJWMxYJAqyKRGN46MT61RHVizdU62cbVUYu9L95cXKzX62yJ2hPbN11EeprS8sSn8kj47skQBrmycCMzFEYBQSntVKFQ5M";
+ }
+ else if (keyPath == "m/84h/0h/0h/1")
+ {
+ extPubKey = "xpub6FJS1ne3STcKdQ9JLXNzZXidmCNZ9dxLiy7WVvsRkcmxjJsrDKJKEAXq4MGyEBM3vHEw2buqXezfNK5SNBrkwK7Fxjz1TW6xzRr2pUyMWFu";
+ }
+ }
+
+ return extPubKey != null;
+ }
}
}

MODIFY
WalletWasabi.Tests/HwiTests/NoDeviceConnectedTests/MockedDeviceTests.cs
WalletWasabi.Tests/HwiTests/NoDeviceConnectedTests/MockedDeviceTests.cs
@@ -9,6 +9,7 @@ using WalletWasabi.Helpers;
using WalletWasabi.Hwi2;
using WalletWasabi.Hwi2.Exceptions;
using WalletWasabi.Hwi2.Models;
+using WalletWasabi.Hwi2.Parsers;
using WalletWasabi.KeyManagement;
using Xunit;
@@ -99,6 +100,34 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
}
}
+ [Theory]
+ [MemberData(nameof(GetDifferentNetworkValues))]
+ public async Task TrezorOneMockTestsAsync(Network network)
+ {
+ var client = new HwiClient(network, new HwiProcessBridgeMock(HardwareWalletModels.TrezorOne));
+
+ using (var cts = new CancellationTokenSource(ReasonableRequestTimeout))
+ {
+ IEnumerable<HwiEnumerateEntry> enumerate = await client.EnumerateAsync(cts.Token);
+ Assert.Single(enumerate);
+ HwiEnumerateEntry entry = enumerate.Single();
+ Assert.Equal(HardwareWalletVendors.Trezor, entry.Type);
+ string rawPath = "hid:\\\\\\\\?\\\\hid#vid_534c&pid_0001&mi_00#7&6f0b727&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}";
+ string normalizedPath = HwiParser.NormalizeRawDevicePath(rawPath);
+ Assert.Equal(normalizedPath, entry.Path);
+ Assert.False(entry.NeedsPassphraseSent);
+ Assert.True(entry.NeedsPinSent);
+ Assert.Equal("Could not open client or get fingerprint information: Trezor is locked. Unlock by using 'promptpin' and then 'sendpin'.", entry.Error);
+ Assert.Equal(HwiErrorCode.DeviceNotReady, entry.Code);
+ Assert.Null(entry.Fingerprint);
+
+ var deviceType = entry.Type.Value;
+ var devicePath = entry.Path;
+
+ await client.WipeAsync(deviceType, devicePath, cts.Token);
+ }
+ }
+
#endregion Tests
#region HelperMethods

MODIFY
WalletWasabi/Hwi2/Parsers/HwiParser.cs
WalletWasabi/Hwi2/Parsers/HwiParser.cs
@@ -259,6 +259,15 @@ namespace WalletWasabi.Hwi2.Parsers
code: code);
}
+ public static string NormalizeRawDevicePath(string rawPath)
+ {
+ // There's some strangeness going on here.
+ // Seems like when we get a complex path like: "hid:\\\\\\\\?\\\\hid#vid_534c&pid_0001&mi_00#7&6f0b727&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}"
+ // While reading it out as the json, the duplicated \s are removed magically by newtonsoft.json.
+ // However the normalized path is accepted by HWI (not sure if the raw path is accepted also.)
+ return rawPath.Replace(@"\\", @"\");
+ }
+
public static bool TryParseVersion(string hwiResponse, string substringFrom, out Version version)
{
int startIndex = hwiResponse.IndexOf(substringFrom) + substringFrom.Length;

Start writing trezor one mocks