ColdCard mock tests
MODIFY
WalletWasabi.Tests/HwiTests/DeviceConnectedTests/Katas.cs
WalletWasabi.Tests/HwiTests/DeviceConnectedTests/Katas.cs
@@ -195,6 +195,9 @@ namespace WalletWasabi.Tests.HwiTests.DeviceConnectedTests
HardwareWalletVendors deviceType = entry.Type.Value;
HDFingerprint fingerprint = entry.Fingerprint.Value;
+ // ColdCard doesn't support it.
+ await Assert.ThrowsAsync<HwiException>(async () => await client.WipeAsync(deviceType, devicePath, cts.Token));
+
// ColdCard doesn't support it.
await Assert.ThrowsAsync<HwiException>(async () => await client.SetupAsync(deviceType, devicePath, false, cts.Token));

MODIFY
WalletWasabi.Tests/HwiTests/NoDeviceConnectedTests/HwiProcessBridgeMock.cs
WalletWasabi.Tests/HwiTests/NoDeviceConnectedTests/HwiProcessBridgeMock.cs
@@ -38,6 +38,15 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
type = "trezor";
rawPath = "hid:\\\\\\\\?\\\\hid#vid_534c&pid_0001&mi_00#7&6f0b727&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}";
}
+ else if (Model == HardwareWalletModels.ColdcardMk1)
+ {
+ type = "coldcard";
+ rawPath = @"\\\\?\\hid#vid_d13e&pid_cc10&mi_00#7&1b239988&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}";
+ }
+ else
+ {
+ throw new NotImplementedException("Mock missing");
+ }
string path = HwiParser.NormalizeRawDevicePath(rawPath);
string devicePathAndTypeArgumentString = $"--device-path \"{path}\" --device-type \"{type}\"";
@@ -58,6 +67,12 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
var code = 0;
return Task.FromResult((response, code));
}
+ else if (Model == HardwareWalletModels.ColdcardMk1)
+ {
+ var response = $"[{{\"type\": \"{type}\", \"path\": \"{rawPath}\", \"needs_passphrase\": false, \"fingerprint\": \"a3d0d797\"}}]\r\n";
+ var code = 0;
+ return Task.FromResult((response, code));
+ }
}
else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} wipe"))
{
@@ -67,6 +82,12 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
var code = 0;
return Task.FromResult((response, code));
}
+ else if (Model == HardwareWalletModels.ColdcardMk1)
+ {
+ var response = "{\"error\": \"The Coldcard does not support wiping via software\", \"code\": -9}\r\n";
+ var code = 0;
+ return Task.FromResult((response, code));
+ }
}
else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} setup"))
{
@@ -76,6 +97,12 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
var code = 0;
return Task.FromResult((response, code));
}
+ else if (Model == HardwareWalletModels.ColdcardMk1)
+ {
+ var response = "{\"error\": \"The Coldcard does not support software setup\", \"code\": -9}\r\n";
+ var code = 0;
+ return Task.FromResult((response, code));
+ }
}
else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} --interactive setup"))
{
@@ -85,6 +112,12 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
var code = 0;
return Task.FromResult((response, code));
}
+ else if (Model == HardwareWalletModels.ColdcardMk1)
+ {
+ var response = "{\"error\": \"The Coldcard does not support software setup\", \"code\": -9}\r\n";
+ var code = 0;
+ return Task.FromResult((response, code));
+ }
}
else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} --interactive restore"))
{
@@ -94,6 +127,12 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
var code = 0;
return Task.FromResult((response, code));
}
+ else if (Model == HardwareWalletModels.ColdcardMk1)
+ {
+ var response = "{\"error\": \"The Coldcard does not support restoring via software\", \"code\": -9}\r\n";
+ var code = 0;
+ return Task.FromResult((response, code));
+ }
}
else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} promptpin"))
{
@@ -103,6 +142,12 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
var code = 0;
return Task.FromResult((response, code));
}
+ else if (Model == HardwareWalletModels.ColdcardMk1)
+ {
+ var response = "{\"error\": \"The Coldcard 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))
{
@@ -112,10 +157,16 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
var code = 0;
return Task.FromResult((response, code));
}
+ else if (Model == HardwareWalletModels.ColdcardMk1)
+ {
+ var response = "{\"error\": \"The Coldcard 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)
+ if (Model == HardwareWalletModels.TrezorT || Model == HardwareWalletModels.ColdcardMk1)
{
var response = $"{{\"xpub\": \"{xpub}\"}}\r\n";
var code = 0;
@@ -124,7 +175,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)
+ if (Model == HardwareWalletModels.TrezorT || Model == HardwareWalletModels.ColdcardMk1)
{
string response = t1
? "{\"address\": \"tb1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7rtzlxy\"}\r\n"
@@ -135,7 +186,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)
+ if (Model == HardwareWalletModels.TrezorT || Model == HardwareWalletModels.ColdcardMk1)
{
string response = t2
? "{\"address\": \"tb1qmaveee425a5xjkjcv7m6d4gth45jvtnjqhj3l6\"}\r\n"

MODIFY
WalletWasabi.Tests/HwiTests/NoDeviceConnectedTests/MockedDeviceTests.cs
WalletWasabi.Tests/HwiTests/NoDeviceConnectedTests/MockedDeviceTests.cs
@@ -130,6 +130,96 @@ namespace WalletWasabi.Tests.HwiTests.NoDeviceConnectedTests
}
}
+ [Theory]
+ [MemberData(nameof(GetDifferentNetworkValues))]
+ public async Task ColdCardMk1MockTestsAsync(Network network)
+ {
+ var client = new HwiClient(network, new HwiProcessBridgeMock(HardwareWalletModels.ColdcardMk1));
+
+ using (var cts = new CancellationTokenSource(ReasonableRequestTimeout))
+ {
+ IEnumerable<HwiEnumerateEntry> enumerate = await client.EnumerateAsync(cts.Token);
+ Assert.Single(enumerate);
+ HwiEnumerateEntry entry = enumerate.Single();
+ Assert.Equal(HardwareWalletVendors.Coldcard, entry.Type);
+ 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.Type.Value;
+ var devicePath = entry.Path;
+ HDFingerprint fingerprint = entry.Fingerprint.Value;
+
+ 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.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

MODIFY
WalletWasabi/Hwi2/Models/HwiEnumerateEntry.cs
WalletWasabi/Hwi2/Models/HwiEnumerateEntry.cs
@@ -21,7 +21,7 @@ namespace WalletWasabi.Hwi2.Models
public bool IsInitialized()
{
// Check for error message, too, not only code, because the currently released version doesn't have error code. This can be removed if HWI > 1.0.1 version is updated.
- var notInitialized = Code == HwiErrorCode.DeviceNotInitialized || Error.Contains("Not initialized", StringComparison.OrdinalIgnoreCase);
+ var notInitialized = (Code != null && Code == HwiErrorCode.DeviceNotInitialized) || (Error?.Contains("Not initialized", StringComparison.OrdinalIgnoreCase) is true);
return !notInitialized;
}

ColdCard mock tests