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; 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 NotSupportedNetworkException(network); } Assert.Equal(expectedAddress1, address1); Assert.Equal(expectedAddress2, address2); }
public Task <(string response, int exitCode)> SendCommandAsync(string arguments, bool openConsole, CancellationToken cancel, Action <StreamWriter>?standardInputWriter = null) { if (openConsole) { throw new NotImplementedException($"Cannot mock {nameof(openConsole)} mode."); } string model; string rawPath; model = Model switch { HardwareWalletModels.Trezor_T => "trezor_t", HardwareWalletModels.Trezor_1 => "trezor_1", HardwareWalletModels.Coldcard => "coldcard", HardwareWalletModels.Ledger_Nano_S => "ledger_nano_s", HardwareWalletModels.Ledger_Nano_X => "ledger_nano_x", _ => throw new NotImplementedException("Mock missing.") }; rawPath = Model switch { HardwareWalletModels.Trezor_T => "webusb: 001:4", HardwareWalletModels.Trezor_1 => "hid:\\\\\\\\?\\\\hid#vid_534c&pid_0001&mi_00#7&6f0b727&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}", HardwareWalletModels.Coldcard => @"\\\\?\\hid#vid_d13e&pid_cc10&mi_00#7&1b239988&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}", HardwareWalletModels.Ledger_Nano_S => "\\\\\\\\?\\\\hid#vid_2c97&pid_0001&mi_00#7&e45ae20&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}", HardwareWalletModels.Ledger_Nano_X => "\\\\\\\\?\\\\hid#vid_2c97&pid_0001&mi_00#7&e45ae20&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}", _ => throw new NotImplementedException("Mock missing.") }; string path = HwiParser.NormalizeRawDevicePath(rawPath); string devicePathAndTypeArgumentString = $"--device-path \"{path}\" --device-type \"{model}\""; const string SuccessTrueResponse = "{\"success\": true}\r\n"; string?response = null; int code = 0; if (CompareArguments(arguments, "enumerate")) { response = Model switch { HardwareWalletModels.Trezor_T => $"[{{\"model\": \"{model}\", \"path\": \"{rawPath}\", \"needs_pin_sent\": false, \"needs_passphrase_sent\": false, \"error\": \"Not initialized\"}}]", HardwareWalletModels.Trezor_1 => $"[{{\"model\": \"{model}\", \"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", HardwareWalletModels.Coldcard => $"[{{\"model\": \"{model}\", \"path\": \"{rawPath}\", \"needs_passphrase\": false, \"fingerprint\": \"a3d0d797\"}}]\r\n", HardwareWalletModels.Ledger_Nano_S => $"[{{\"model\": \"{model}\", \"path\": \"{rawPath}\", \"fingerprint\": \"4054d6f6\", \"needs_pin_sent\": false, \"needs_passphrase_sent\": false}}]\r\n", HardwareWalletModels.Ledger_Nano_X => $"[{{\"model\": \"{model}\", \"path\": \"{rawPath}\", \"fingerprint\": \"4054d6f6\", \"needs_pin_sent\": false, \"needs_passphrase_sent\": false}}]\r\n", _ => throw new NotImplementedException($"Mock missing for {model}") }; } else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} wipe")) { response = Model switch { HardwareWalletModels.Trezor_T or HardwareWalletModels.Trezor_1 => SuccessTrueResponse, HardwareWalletModels.Coldcard => "{\"error\": \"The Coldcard does not support wiping via software\", \"code\": -9}\r\n", HardwareWalletModels.Ledger_Nano_S => "{\"error\": \"The Ledger Nano S does not support wiping via software\", \"code\": -9}\r\n", HardwareWalletModels.Ledger_Nano_X => "{\"error\": \"The Ledger Nano X does not support wiping via software\", \"code\": -9}\r\n", _ => throw new NotImplementedException("Mock missing.") }; } else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} setup")) { response = Model switch { HardwareWalletModels.Trezor_T or HardwareWalletModels.Trezor_1 => "{\"error\": \"setup requires interactive mode\", \"code\": -9}", HardwareWalletModels.Coldcard => "{\"error\": \"The Coldcard does not support software setup\", \"code\": -9}\r\n", HardwareWalletModels.Ledger_Nano_S => "{\"error\": \"The Ledger Nano S does not support software setup\", \"code\": -9}\r\n", HardwareWalletModels.Ledger_Nano_X => "{\"error\": \"The Ledger Nano X does not support software setup\", \"code\": -9}\r\n", _ => throw new NotImplementedException("Mock missing.") }; } else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} --interactive setup")) { response = Model switch { HardwareWalletModels.Trezor_T or HardwareWalletModels.Trezor_1 => SuccessTrueResponse, HardwareWalletModels.Coldcard => "{\"error\": \"The Coldcard does not support software setup\", \"code\": -9}\r\n", HardwareWalletModels.Ledger_Nano_S => "{\"error\": \"The Ledger Nano S does not support software setup\", \"code\": -9}\r\n", HardwareWalletModels.Ledger_Nano_X => "{\"error\": \"The Ledger Nano X does not support software setup\", \"code\": -9}\r\n", _ => throw new NotImplementedException("Mock missing.") }; } else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} --interactive restore")) { response = Model switch { HardwareWalletModels.Trezor_T or HardwareWalletModels.Trezor_1 => SuccessTrueResponse, HardwareWalletModels.Coldcard => "{\"error\": \"The Coldcard does not support restoring via software\", \"code\": -9}\r\n", HardwareWalletModels.Ledger_Nano_S => "{\"error\": \"The Ledger Nano S does not support restoring via software\", \"code\": -9}\r\n", HardwareWalletModels.Ledger_Nano_X => "{\"error\": \"The Ledger Nano X does not support restoring via software\", \"code\": -9}\r\n", _ => throw new NotImplementedException("Mock missing.") }; } else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} promptpin")) { response = Model switch { HardwareWalletModels.Trezor_T or HardwareWalletModels.Trezor_1 => "{\"error\": \"The PIN has already been sent to this device\", \"code\": -11}", HardwareWalletModels.Coldcard => "{\"error\": \"The Coldcard does not need a PIN sent from the host\", \"code\": -9}\r\n", HardwareWalletModels.Ledger_Nano_S => "{\"error\": \"The Ledger Nano S does not need a PIN sent from the host\", \"code\": -9}\r\n", HardwareWalletModels.Ledger_Nano_X => "{\"error\": \"The Ledger Nano X does not need a PIN sent from the host\", \"code\": -9}\r\n", _ => throw new NotImplementedException("Mock missing.") }; } else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} sendpin", true)) { response = Model switch { HardwareWalletModels.Trezor_T or HardwareWalletModels.Trezor_1 => "{\"error\": \"The PIN has already been sent to this device\", \"code\": -11}", HardwareWalletModels.Coldcard => "{\"error\": \"The Coldcard does not need a PIN sent from the host\", \"code\": -9}\r\n", HardwareWalletModels.Ledger_Nano_S => "{\"error\": \"The Ledger Nano S does not need a PIN sent from the host\", \"code\": -9}\r\n", HardwareWalletModels.Ledger_Nano_X => "{\"error\": \"The Ledger Nano X does not need a PIN sent from the host\", \"code\": -9}\r\n", _ => throw new NotImplementedException("Mock missing.") }; } else if (CompareGetXbpubArguments(arguments, out string?xpub)) { if (Model is HardwareWalletModels.Trezor_T or HardwareWalletModels.Coldcard or HardwareWalletModels.Trezor_1 or HardwareWalletModels.Ledger_Nano_S or HardwareWalletModels.Ledger_Nano_X) { response = $"{{\"xpub\": \"{xpub}\"}}\r\n"; } } else if (CompareArguments(out bool t1, arguments, $"{devicePathAndTypeArgumentString} displayaddress --path m/84h/0h/0h --addr-type wit", false)) { if (Model is HardwareWalletModels.Trezor_T or HardwareWalletModels.Coldcard or HardwareWalletModels.Trezor_1 or HardwareWalletModels.Ledger_Nano_S or HardwareWalletModels.Ledger_Nano_X) { response = t1 ? "{\"address\": \"tb1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7rtzlxy\"}\r\n" : "{\"address\": \"bc1q7zqqsmqx5ymhd7qn73lm96w5yqdkrmx7fdevah\"}\r\n"; } }
public async Task TrezorOneMockTestsAsync(Network network) { var client = new HwiClient(network, new HwiProcessBridgeMock(HardwareWalletModels.Trezor_1)); 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_1, entry.Model); 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); Assert.True(entry.IsInitialized()); 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); 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 Task <(string response, int exitCode)> SendCommandAsync(string arguments, bool openConsole, CancellationToken cancel, Action <StreamWriter>?standardInputWriter = null) { if (openConsole) { throw new NotImplementedException($"Cannot mock {nameof(openConsole)} mode."); } string model; string rawPath; if (Model == HardwareWalletModels.Trezor_T) { model = "trezor_t"; rawPath = "webusb: 001:4"; } else if (Model == HardwareWalletModels.Trezor_1) { model = "trezor_1"; rawPath = "hid:\\\\\\\\?\\\\hid#vid_534c&pid_0001&mi_00#7&6f0b727&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}"; } else if (Model == HardwareWalletModels.Coldcard) { model = "coldcard"; rawPath = @"\\\\?\\hid#vid_d13e&pid_cc10&mi_00#7&1b239988&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}"; } else if (Model == HardwareWalletModels.Ledger_Nano_S) { model = "ledger_nano_s"; rawPath = "\\\\\\\\?\\\\hid#vid_2c97&pid_0001&mi_00#7&e45ae20&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 \"{model}\""; const string SuccessTrueResponse = "{\"success\": true}\r\n"; string?response = null; int code = 0; if (CompareArguments(arguments, "enumerate")) { if (Model == HardwareWalletModels.Trezor_T) { response = $"[{{\"model\": \"{model}\", \"path\": \"{rawPath}\", \"needs_pin_sent\": false, \"needs_passphrase_sent\": false, \"error\": \"Not initialized\"}}]"; } else if (Model == HardwareWalletModels.Trezor_1) { response = $"[{{\"model\": \"{model}\", \"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"; } else if (Model == HardwareWalletModels.Coldcard) { response = $"[{{\"model\": \"{model}\", \"path\": \"{rawPath}\", \"needs_passphrase\": false, \"fingerprint\": \"a3d0d797\"}}]\r\n"; } else if (Model == HardwareWalletModels.Ledger_Nano_S) { response = $"[{{\"model\": \"{model}\", \"path\": \"{rawPath}\", \"fingerprint\": \"4054d6f6\", \"needs_pin_sent\": false, \"needs_passphrase_sent\": false}}]\r\n"; } } else if (CompareArguments(arguments, $"{devicePathAndTypeArgumentString} wipe")) { if (Model is HardwareWalletModels.Trezor_T or HardwareWalletModels.Trezor_1) { response = SuccessTrueResponse; } else if (Model == HardwareWalletModels.Coldcard) { response = "{\"error\": \"The Coldcard does not support wiping via software\", \"code\": -9}\r\n"; } else if (Model == HardwareWalletModels.Ledger_Nano_S) { response = "{\"error\": \"The Ledger Nano S does not support wiping via software\", \"code\": -9}\r\n"; } }