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(HardwareWalletModels.Trezor_1, entry.Model); Assert.True(entry.NeedsPinSent); Assert.Equal(HwiErrorCode.DeviceNotReady, entry.Code); Assert.Null(entry.Fingerprint); string devicePath = entry.Path; HardwareWalletModels deviceType = entry.Model; 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)); }
public async Task CanEnumerateAsync(HwiClient client) { using var cts = new CancellationTokenSource(ReasonableRequestTimeout); IEnumerable <HwiEnumerateEntry> enumerate = await client.EnumerateAsync(cts.Token); Assert.Empty(enumerate); }
public async Task CanCallAsynchronouslyAsync(HwiClient client) { using var cts = new CancellationTokenSource(); var tasks = new List <Task> { client.GetVersionAsync(cts.Token), client.GetVersionAsync(cts.Token), client.GetHelpAsync(cts.Token), client.GetHelpAsync(cts.Token), client.EnumerateAsync(cts.Token), client.EnumerateAsync(cts.Token) }; cts.CancelAfter(ReasonableRequestTimeout * tasks.Count); await Task.WhenAny(tasks); }
public async Task ThrowOperationCanceledExceptionsAsync(HwiClient client) { using var cts = new CancellationTokenSource(); cts.Cancel(); 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)); }
public static async Task <HwiEnumerateEntry[]> DetectAsync(Network network, CancellationToken cancelToken) { var client = new HwiClient(network); using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(3)); using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancelToken); var detectedHardwareWallets = (await client.EnumerateAsync(timeoutCts.Token).ConfigureAwait(false)).ToArray(); cancelToken.ThrowIfCancellationRequested(); return(detectedHardwareWallets); }
public async Task CanEnumerateAsync(HwiClient client) { #region Act IEnumerable <string> hwis = await client.EnumerateAsync(new CancellationTokenSource(1000).Token); #endregion Act #region Assert Assert.Empty(hwis); #endregion Assert }
public async Task TrezorTKataAsync() { // --- USER INTERACTIONS --- // // Connect and initialize your Trezor T with the following seed phrase: // more maid moon upgrade layer alter marine screen benefit way cover alcohol // Run this test. // displayaddress request: refuse 1 time // displayaddress request: confirm 2 times // displayaddress request: confirm 1 time // signtx request: confirm 23 times + Hold to confirm // // --- 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(HardwareWalletModels.Trezor_T, entry.Model); Assert.True(entry.Fingerprint.HasValue); string devicePath = entry.Path; HardwareWalletModels deviceType = entry.Model; HDFingerprint fingerprint = entry.Fingerprint.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)); // Trezor T doesn't support it. await Assert.ThrowsAsync <HwiException>(async() => await client.PromptPinAsync(deviceType, devicePath, cts.Token)); // Trezor T doesn't support it. await Assert.ThrowsAsync <HwiException>(async() => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token)); 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); Assert.NotNull(xpub1); Assert.NotNull(xpub2); Assert.NotEqual(xpub1, xpub2); // USER SHOULD REFUSE ACTION await Assert.ThrowsAsync <HwiException>(async() => await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token)); // USER: CONFIRM 2 TIMES BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token); // USER: CONFIRM 1 TIME BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(fingerprint, keyPath2, cts.Token); Assert.NotNull(address1); Assert.NotNull(address2); Assert.NotEqual(address1, address2); var expectedAddress1 = xpub1.PubKey.GetAddress(ScriptPubKeyType.Segwit, network); var expectedAddress2 = xpub2.PubKey.GetAddress(ScriptPubKeyType.Segwit, network); Assert.Equal(expectedAddress1, address1); Assert.Equal(expectedAddress2, address2); // USER: CONFIRM 23 TIMES + Hold to confirm // The user has to confirm multiple times because this transaction spends 22 inputs. // The transaction is similar to these transactions: // https://blockstream.info/testnet/tx/580d04a1891bf5b03a972eb63791e57ca39b85476d45f1d82a09732fe4c9214d // https://blockstream.info/testnet/tx/82cd8165a4fb3276354a817ad1b991a0c4af7d6d438f9052f34d58712f873457 PSBT psbt = PSBT.Parse("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", network); PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, psbt, cts.Token); Transaction signedTx = signedPsbt.GetOriginalTransaction(); Assert.Equal(psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); var checkResult = signedTx.Check(); Assert.Equal(TransactionCheckResult.Success, checkResult); }
public async Task LedgerNanoSKataAsync() { // --- USER INTERACTIONS --- // // Connect an already initialized device and unlock it and enter to Bitcoin App. // Run this test. // displayaddress request: refuse (accept Warning messages) // displayaddress request: confirm // displayaddress request: confirm // signtx request: refuse // signtx request: confirm // // --- USER INTERACTIONS --- var network = Network.Main; var client = new HwiClient(network); using var cts = new CancellationTokenSource(ReasonableRequestTimeout); var enumerate = await client.EnumerateAsync(cts.Token); HwiEnumerateEntry entry = Assert.Single(enumerate); Assert.NotNull(entry.Path); Assert.Equal(HardwareWalletModels.Ledger_Nano_S, entry.Model); Assert.True(entry.Fingerprint.HasValue); Assert.Null(entry.Code); Assert.Null(entry.Error); Assert.Null(entry.SerialNumber); Assert.False(entry.NeedsPassphraseSent); Assert.False(entry.NeedsPinSent); string devicePath = entry.Path; HardwareWalletModels deviceType = entry.Model; HDFingerprint fingerprint = entry.Fingerprint.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)); await Assert.ThrowsAsync <HwiException>(async() => await client.PromptPinAsync(deviceType, devicePath, cts.Token)); await Assert.ThrowsAsync <HwiException>(async() => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token)); 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); Assert.NotNull(xpub1); Assert.NotNull(xpub2); Assert.NotEqual(xpub1, xpub2); // USER SHOULD REFUSE ACTION await Assert.ThrowsAsync <HwiException>(async() => await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token)); // USER: CONFIRM BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token); // USER: CONFIRM BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(fingerprint, keyPath2, cts.Token); Assert.NotNull(address1); Assert.NotNull(address2); Assert.NotEqual(address1, address2); var expectedAddress1 = xpub1.PubKey.GetAddress(ScriptPubKeyType.Segwit, network); var expectedAddress2 = xpub2.PubKey.GetAddress(ScriptPubKeyType.Segwit, network); Assert.Equal(expectedAddress1, address1); Assert.Equal(expectedAddress2, address2); // USER: REFUSE PSBT psbt = BuildPsbt(network, fingerprint, xpub1, keyPath1); var ex = await Assert.ThrowsAsync <HwiException>(async() => await client.SignTxAsync(deviceType, devicePath, psbt, cts.Token)); Assert.Equal(HwiErrorCode.BadArgument, ex.ErrorCode); // USER: CONFIRM PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, psbt, cts.Token); Transaction signedTx = signedPsbt.GetOriginalTransaction(); Assert.Equal(psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); var checkResult = signedTx.Check(); Assert.Equal(TransactionCheckResult.Success, checkResult); }
public async Task ColdCardKataAsync() { // --- USER INTERACTIONS --- // // Connect an already initialized device and unlock it. // Run this test. // signtx request: refuse // signtx request: confirm // // --- 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(HardwareWalletModels.Coldcard, entry.Model); Assert.True(entry.Fingerprint.HasValue); string devicePath = entry.Path; HardwareWalletModels deviceType = entry.Model; 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)); // ColdCard doesn't support it. await Assert.ThrowsAsync <HwiException>(async() => await client.RestoreAsync(deviceType, devicePath, false, cts.Token)); // ColdCard doesn't support it. await Assert.ThrowsAsync <HwiException>(async() => await client.PromptPinAsync(deviceType, devicePath, cts.Token)); // ColdCard doesn't support it. await Assert.ThrowsAsync <HwiException>(async() => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token)); 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); Assert.NotNull(xpub1); Assert.NotNull(xpub2); Assert.NotEqual(xpub1, xpub2); PSBT psbt = BuildPsbt(network, fingerprint, xpub1, keyPath1); // USER: REFUSE var ex = await Assert.ThrowsAsync <HwiException>(async() => await client.SignTxAsync(deviceType, devicePath, psbt, cts.Token)); Assert.Equal(HwiErrorCode.ActionCanceled, ex.ErrorCode); // USER: CONFIRM PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, psbt, cts.Token); Transaction signedTx = signedPsbt.GetOriginalTransaction(); Assert.Equal(psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); var checkResult = signedTx.Check(); Assert.Equal(TransactionCheckResult.Success, checkResult); // ColdCard just display the address. There is no confirm/refuse action. BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token); BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(fingerprint, keyPath2, cts.Token); Assert.NotNull(address1); Assert.NotNull(address2); Assert.NotEqual(address1, address2); var expectedAddress1 = xpub1.PubKey.GetAddress(ScriptPubKeyType.Segwit, network); var expectedAddress2 = xpub2.PubKey.GetAddress(ScriptPubKeyType.Segwit, network); Assert.Equal(expectedAddress1, address1); Assert.Equal(expectedAddress2, address2); }
public async Task TrezorTMockTestsAsync(Network network) { var client = new HwiClient(network, new HwiProcessBridgeMock(HardwareWalletModels.Trezor_T)); 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_T, entry.Model); Assert.Equal("webusb: 001:4", entry.Path); Assert.False(entry.NeedsPassphraseSent); Assert.False(entry.NeedsPinSent); Assert.NotNull(entry.Error); Assert.NotEmpty(entry.Error); Assert.Equal(HwiErrorCode.DeviceNotInitialized, entry.Code); Assert.False(entry.IsInitialized()); Assert.Null(entry.Fingerprint); 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); // Trezor T doesn't support it. 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 async Task LedgerNanoSTestsAsync(Network network) { var client = new HwiClient(network, new HwiProcessBridgeMock(HardwareWalletModels.Ledger_Nano_S)); using var cts = new CancellationTokenSource(ReasonableRequestTimeout); IEnumerable <HwiEnumerateEntry> enumerate = await client.EnumerateAsync(cts.Token); Assert.Single(enumerate); HwiEnumerateEntry entry = enumerate.Single(); Assert.Equal(HardwareWalletModels.Ledger_Nano_S, entry.Model); 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.Model; 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 NotSupportedNetworkException(network); } Assert.Equal(expectedAddress1, address1); Assert.Equal(expectedAddress2, address2); }
public async Task LedgerNanoXKataAsync() { // --- USER INTERACTIONS --- // // Connect and initialize your Nano X with the following seed phrase: // more maid moon upgrade layer alter marine screen benefit way cover alcohol // Run this test. // displayaddress request(derivation path): approve // displayaddress request: reject // displayaddress request(derivation path): approve // displayaddress request: approve // displayaddress request(derivation path): approve // displayaddress request: approve // signtx request: reject // signtx request: accept // confirm transaction: accept and send // // --- USER INTERACTIONS --- var network = Network.Main; var client = new HwiClient(network); using var cts = new CancellationTokenSource(ReasonableRequestTimeout); var enumerate = await client.EnumerateAsync(cts.Token); HwiEnumerateEntry entry = Assert.Single(enumerate); Assert.NotNull(entry.Path); Assert.Equal(HardwareWalletModels.Ledger_Nano_X, entry.Model); Assert.NotNull(entry.Fingerprint); Assert.Null(entry.Code); Assert.Null(entry.Error); Assert.Null(entry.SerialNumber); Assert.False(entry.NeedsPassphraseSent); Assert.False(entry.NeedsPinSent); string devicePath = entry.Path; HardwareWalletModels deviceType = entry.Model; HDFingerprint fingerprint = entry.Fingerprint !.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)); await Assert.ThrowsAsync <HwiException>(async() => await client.PromptPinAsync(deviceType, devicePath, cts.Token)); await Assert.ThrowsAsync <HwiException>(async() => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token)); KeyPath keyPath1 = KeyManager.GetAccountKeyPath(network); KeyPath keyPath2 = KeyManager.GetAccountKeyPath(network).Derive(1); ExtPubKey xpub1 = await client.GetXpubAsync(deviceType, devicePath, keyPath1, cts.Token); ExtPubKey xpub2 = await client.GetXpubAsync(deviceType, devicePath, keyPath2, cts.Token); Assert.NotNull(xpub1); Assert.NotNull(xpub2); Assert.NotEqual(xpub1, xpub2); // USER SHOULD REFUSE ACTION await Assert.ThrowsAsync <HwiException>(async() => await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token)); // USER: CONFIRM BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token); // USER: CONFIRM BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(fingerprint, keyPath2, cts.Token); Assert.NotNull(address1); Assert.NotNull(address2); Assert.NotEqual(address1, address2); var expectedAddress1 = xpub1.PubKey.GetAddress(ScriptPubKeyType.Segwit, network); var expectedAddress2 = xpub2.PubKey.GetAddress(ScriptPubKeyType.Segwit, network); Assert.Equal(expectedAddress1, address1); Assert.Equal(expectedAddress2, address2); // USER: REFUSE var ex = await Assert.ThrowsAsync <HwiException>(async() => await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token)); Assert.Equal(HwiErrorCode.BadArgument, ex.ErrorCode); // USER: CONFIRM PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token); Transaction signedTx = signedPsbt.GetOriginalTransaction(); Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); var checkResult = signedTx.Check(); Assert.Equal(TransactionCheckResult.Success, checkResult); }
public async Task TrezorTKataAsync() { // --- USER INTERACTIONS --- // // Connect and initialize your Trezor T with the following seed phrase: // more maid moon upgrade layer alter marine screen benefit way cover alcohol // Run this test. // displayaddress request: confirm 1 time // displayaddress request: confirm 1 time // signtx request: refuse 1 time // signtx request: Hold to confirm // // --- 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(HardwareWalletModels.Trezor_T, entry.Model); Assert.NotNull(entry.Fingerprint); string devicePath = entry.Path; HardwareWalletModels deviceType = entry.Model; HDFingerprint fingerprint = entry.Fingerprint !.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)); // Trezor T doesn't support it. await Assert.ThrowsAsync <HwiException>(async() => await client.PromptPinAsync(deviceType, devicePath, cts.Token)); // Trezor T doesn't support it. await Assert.ThrowsAsync <HwiException>(async() => await client.SendPinAsync(deviceType, devicePath, 1111, cts.Token)); // Because of the Trezor T 2.3.5 firmware update, // we cannot use any longer the KeyManager.DefaultAccountKeyPath. KeyPath keyPath1 = new("m/84h/0h/0h/0/0"); KeyPath keyPath2 = new("m/84h/0h/0h/0/1"); ExtPubKey xpub1 = await client.GetXpubAsync(deviceType, devicePath, keyPath1, cts.Token); ExtPubKey xpub2 = await client.GetXpubAsync(deviceType, devicePath, keyPath2, cts.Token); Assert.NotNull(xpub1); Assert.NotNull(xpub2); Assert.NotEqual(xpub1, xpub2); // USER: CONFIRM BitcoinWitPubKeyAddress address1 = await client.DisplayAddressAsync(deviceType, devicePath, keyPath1, cts.Token); // USER: CONFIRM BitcoinWitPubKeyAddress address2 = await client.DisplayAddressAsync(fingerprint, keyPath2, cts.Token); Assert.NotNull(address1); Assert.NotNull(address2); Assert.NotEqual(address1, address2); var expectedAddress1 = xpub1.PubKey.GetAddress(ScriptPubKeyType.Segwit, network); var expectedAddress2 = xpub2.PubKey.GetAddress(ScriptPubKeyType.Segwit, network); Assert.Equal(expectedAddress1, address1); Assert.Equal(expectedAddress2, address2); // USER SHOULD REFUSE ACTION var result = await Assert.ThrowsAsync <HwiException>(async() => await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token)); Assert.Equal(HwiErrorCode.ActionCanceled, result.ErrorCode); // USER: Hold to confirm PSBT signedPsbt = await client.SignTxAsync(deviceType, devicePath, Psbt, cts.Token); Transaction signedTx = signedPsbt.GetOriginalTransaction(); Assert.Equal(Psbt.GetOriginalTransaction().GetHash(), signedTx.GetHash()); var checkResult = signedTx.Check(); Assert.Equal(TransactionCheckResult.Success, checkResult); }
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; 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.GetAccountKeyPath(network); KeyPath keyPath2 = KeyManager.GetAccountKeyPath(network).Derive(1); ExtPubKey xpub1 = await client.GetXpubAsync(deviceType, devicePath, keyPath1, cts.Token); ExtPubKey xpub2 = await client.GetXpubAsync(deviceType, devicePath, keyPath2, cts.Token); ExtPubKey expectedXpub1; ExtPubKey expectedXpub2; if (network == Network.TestNet) { expectedXpub1 = NBitcoinHelpers.BetterParseExtPubKey("xpub6CaGC5LjEw1YWw8br7AURnB6ioJY2bEVApXh8NMsPQ9mdDbzN51iwVrnmGSof3MfjjRrntnE8mbYeTW5ywgvCXdjqF8meQEwnhPDQV2TW7c"); expectedXpub2 = NBitcoinHelpers.BetterParseExtPubKey("xpub6E7pup6CRRS5jM1r3HVYQhHwQHpddJALjRDbsVDtsnQJozHrfE8Pua2X5JhtkWCxdcmGhPXWxV7DoJtSgZSUvUy6cvDchVQt2RGEd4mD4FA"); } else { expectedXpub1 = NBitcoinHelpers.BetterParseExtPubKey("xpub6DHjDx4gzLV37gJWMxYJAqyKRGN46MT61RHVizdU62cbVUYu9L95cXKzX62yJ2hPbN11EeprS8sSn8kj47skQBrmycCMzFEYBQSntVKFQ5M"); expectedXpub2 = NBitcoinHelpers.BetterParseExtPubKey("xpub6FJS1ne3STcKdQ9JLXNzZXidmCNZ9dxLiy7WVvsRkcmxjJsrDKJKEAXq4MGyEBM3vHEw2buqXezfNK5SNBrkwK7Fxjz1TW6xzRr2pUyMWFu"); } Assert.Equal(expectedXpub1, xpub1); Assert.Equal(expectedXpub2, 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); }