Add LedgerNanoS Mock
MODIFY
WalletWasabi.Tests/HwiTests/NoDeviceConnectedTests/HwiProcessBridgeMock.cs
WalletWasabi.Tests/HwiTests/NoDeviceConnectedTests/HwiProcessBridgeMock.cs
@@ -43,6 +43,11 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
type = "coldcard";
rawPath = @"\\\\?\\hid#vid_d13e&pid_cc10&mi_00#7&1b239988&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}";
}
+ else if (Model == HardwareWalletModels.LedgerNanoS)
+ {
+ type = "ledger";
+ rawPath = "\\\\\\\\?\\\\hid#vid_2c97&pid_0001&mi_00#7&e45ae20&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}";
+ }
else
{
throw new NotImplementedException("Mock missing");
@@ -73,6 +78,12 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
var code = 0;
return Task.FromResult((response, code));
}
+ else if (Model == HardwareWalletModels.LedgerNanoS)
+ {
+ var response = $"[{{\"type\": \"{type}\", \"path\": \"{rawPath}\", \"fingerprint\": \"4054d6f6\", \"needs_pin_sent\": false, \"needs_passphrase_sent\": false}}]\r\n";
+ var code = 0;
+ return Task.FromResult((response, code));
+ }
}
else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} wipe"))
{
@@ -88,6 +99,12 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
var code = 0;
return Task.FromResult((response, code));
}
+ else if (Model == HardwareWalletModels.LedgerNanoS)
+ {
+ var response = "{\"error\": \"The Ledger Nano S does not support wiping via software\", \"code\": -9}\r\n";
+ var code = 0;
+ return Task.FromResult((response, code));
+ }
}
else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} setup"))
{
@@ -103,6 +120,12 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
var code = 0;
return Task.FromResult((response, code));
}
+ else if (Model == HardwareWalletModels.LedgerNanoS)
+ {
+ var response = "{\"error\": \"The Ledger Nano S does not support software setup\", \"code\": -9}\r\n";
+ var code = 0;
+ return Task.FromResult((response, code));
+ }
}
else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} --interactive setup"))
{
@@ -118,6 +141,12 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
var code = 0;
return Task.FromResult((response, code));
}
+ else if (Model == HardwareWalletModels.LedgerNanoS)
+ {
+ var response = "{\"error\": \"The Ledger Nano S does not support software setup\", \"code\": -9}\r\n";
+ var code = 0;
+ return Task.FromResult((response, code));
+ }
}
else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} --interactive restore"))
{
@@ -133,6 +162,12 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
var code = 0;
return Task.FromResult((response, code));
}
+ else if (Model == HardwareWalletModels.LedgerNanoS)
+ {
+ var response = "{\"error\": \"The Ledger Nano S does not support restoring via software\", \"code\": -9}\r\n";
+ var code = 0;
+ return Task.FromResult((response, code));
+ }
}
else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} promptpin"))
{
@@ -148,6 +183,12 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
var code = 0;
return Task.FromResult((response, code));
}
+ else if (Model == HardwareWalletModels.LedgerNanoS)
+ {
+ var response = "{\"error\": \"The Ledger Nano S does not need a PIN sent from the host\", \"code\": -9}\r\n";
+ var code = 0;
+ return Task.FromResult((response, code));
+ }
}
else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} sendpin", true))
{
@@ -163,10 +204,16 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
var code = 0;
return Task.FromResult((response, code));
}
+ else if (Model == HardwareWalletModels.LedgerNanoS)
+ {
+ var response = "{\"error\": \"The Ledger Nano S does not need a PIN sent from the host\", \"code\": -9}\r\n";
+ var code = 0;
+ return Task.FromResult((response, code));
+ }
}
else if (CompareGetXbpubArguments(arguments, out string xpub))
{
- if (Model == HardwareWalletModels.TrezorT || Model == HardwareWalletModels.ColdcardMk1 || Model == HardwareWalletModels.TrezorOne)
+ if (Model == HardwareWalletModels.TrezorT || Model == HardwareWalletModels.ColdcardMk1 || Model == HardwareWalletModels.TrezorOne || Model == HardwareWalletModels.LedgerNanoS)
{
var response = $"{{\"xpub\": \"{xpub}\"}}\r\n";
var code = 0;
@@ -175,7 +222,7 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
}
else if (CompareArguments(out bool t1, arguments, $"{devicePathAndTypeArgumentString} displayaddress --path m/84h/0h/0h --wpkh", false))
{
- if (Model == HardwareWalletModels.TrezorT || Model == HardwareWalletModels.ColdcardMk1 || Model == HardwareWalletModels.TrezorOne)
+ if (Model == HardwareWalletModels.TrezorT || Model == HardwareWalletModels.ColdcardMk1 || Model == HardwareWalletModels.TrezorOne || Model == HardwareWalletModels.LedgerNanoS)
{
string response = t1
? "{\"address\": \"tb1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7rtzlxy\"}\r\n"
@@ -186,7 +233,7 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
}
else if (CompareArguments(out bool t2, arguments, $"{devicePathAndTypeArgumentString} displayaddress --path m/84h/0h/0h/1 --wpkh", false))
{
- if (Model == HardwareWalletModels.TrezorT || Model == HardwareWalletModels.ColdcardMk1 || Model == HardwareWalletModels.TrezorOne)
+ if (Model == HardwareWalletModels.TrezorT || Model == HardwareWalletModels.ColdcardMk1 || Model == HardwareWalletModels.TrezorOne || Model == HardwareWalletModels.LedgerNanoS)
{
string response = t2
? "{\"address\": \"tb1qmaveee425a5xjkjcv7m6d4gth45jvtnjqhj3l6\"}\r\n"

MODIFY
WalletWasabi.Tests/HwiTests/NoDeviceConnectedTests/MockedDeviceTests.cs
WalletWasabi.Tests/HwiTests/NoDeviceConnectedTests/MockedDeviceTests.cs
@@ -266,6 +266,88 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
}
}
+ [Theory]
+ [MemberData(nameof(GetDifferentNetworkValues))]
+ public async Task LedgerNanoSTestsAsync(Network network)
+ {
+ var client = new HwiClient(network, new HwiProcessBridgeMock(HardwareWalletModels.LedgerNanoS));
+
+ using (var cts = new CancellationTokenSource(ReasonableRequestTimeout))
+ {
+ IEnumerable<HwiEnumerateEntry> enumerate = await client.EnumerateAsync(cts.Token);
+ Assert.Single(enumerate);
+ HwiEnumerateEntry entry = enumerate.Single();
+ Assert.Equal(HardwareWalletVendors.Ledger, entry.Type);
+ 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.Type.Value;
+ var devicePath = entry.Path;
+
+ var wipe = await Assert.ThrowsAsync<HwiException>(async () => await client.WipeAsync(deviceType, devicePath, cts.Token));
+ Assert.Equal("The Ledger Nano S 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 S 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 S 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 S 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 S 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 NotSupportedException($"{network} not supported.");
+ }
+
+ Assert.Equal(expectedAddress1, address1);
+ Assert.Equal(expectedAddress2, address2);
+ }
+ }
+
#endregion Tests
#region HelperMethods

Add LedgerNanoS Mock