コード例 #1
0
        public static HDFingerprint BetterParseHDFingerprint(string hdFingerPrintString, bool reverseByteOrder = false)
        {
            hdFingerPrintString = Guard.NotNullOrEmptyOrWhitespace(nameof(hdFingerPrintString), hdFingerPrintString, trim: true);

            HDFingerprint hdfp;

            try
            {
                var hdfpu = uint.Parse(hdFingerPrintString);
                hdfp = new HDFingerprint(hdfpu);
            }
            catch
            {
                // Try hex, Old wallet format was like this.
                var bytes = ByteHelpers.FromHex(hdFingerPrintString);
                if (reverseByteOrder)
                {
                    hdfp = new HDFingerprint(bytes.Reverse().ToArray());
                }
                else
                {
                    hdfp = new HDFingerprint(bytes);
                }
            }
            return(hdfp);
        }
コード例 #2
0
        private static PSBT BuildPsbt(Network network, HDFingerprint fingerprint, ExtPubKey xpub, KeyPath xpubKeyPath)
        {
            var deriveSendFromKeyPath = new KeyPath("1/0");
            var deriveSendToKeyPath   = new KeyPath("0/0");

            KeyPath sendFromKeyPath = xpubKeyPath.Derive(deriveSendFromKeyPath);
            KeyPath sendToKeyPath   = xpubKeyPath.Derive(deriveSendToKeyPath);

            PubKey sendFromPubKey = xpub.Derive(deriveSendFromKeyPath).PubKey;
            PubKey sendToPubKey   = xpub.Derive(deriveSendToKeyPath).PubKey;

            BitcoinAddress sendFromAddress = sendFromPubKey.GetAddress(ScriptPubKeyType.Segwit, network);
            BitcoinAddress sendToAddress   = sendToPubKey.GetAddress(ScriptPubKeyType.Segwit, network);

            TransactionBuilder builder = network.CreateTransactionBuilder();

            builder = builder.AddCoins(new Coin(uint256.One, 0, Money.Coins(1), sendFromAddress.ScriptPubKey));
            builder.Send(sendToAddress.ScriptPubKey, Money.Coins(0.99999m));
            PSBT psbt = builder
                        .SendFees(Money.Coins(0.00001m))
                        .BuildPSBT(false);

            var rootKeyPath1 = new RootedKeyPath(fingerprint, sendFromKeyPath);
            var rootKeyPath2 = new RootedKeyPath(fingerprint, sendToKeyPath);

            psbt.AddKeyPath(sendFromPubKey, rootKeyPath1, sendFromAddress.ScriptPubKey);
            psbt.AddKeyPath(sendToPubKey, rootKeyPath2, sendToAddress.ScriptPubKey);
            return(psbt);
        }
コード例 #3
0
        public static bool TryGetMasterFingerprintFromFile(string filePath, out HDFingerprint masterFingerprint)
        {
            masterFingerprint = default;
            filePath          = Guard.NotNullOrEmptyOrWhitespace(nameof(filePath), filePath);

            if (!File.Exists(filePath))
            {
                throw new FileNotFoundException($"Wallet file not found at: `{filePath}`.");
            }

            // Example text to handle: "ExtPubKey": "03BF8271268000000013B9013C881FE456DDF524764F6322F611B03CF6".
            var masterfpline = File.ReadLines(filePath) // Enumerated read.
                               .Take(21)                // Limit reads to x lines.
                               .FirstOrDefault(line => line.Contains("\"MasterFingerprint\": \"", StringComparison.InvariantCulture));

            if (string.IsNullOrEmpty(masterfpline))
            {
                return(false);
            }

            var parts = masterfpline.Split("\"MasterFingerprint\": \"");

            if (parts.Length != 2)
            {
                throw new FormatException($"Could not split line: {masterfpline}");
            }

            var hex = parts[1].TrimEnd(',', '"');

            var mfp = new HDFingerprint(ByteHelpers.FromHex(hex));

            masterFingerprint = mfp;
            return(true);
        }
コード例 #4
0
        public HardwareWalletInfo(string masterFingerprint, string serialNumber, HardwareWalletType type, string path, string error)
        {
            try
            {
                Guard.NotNullOrEmptyOrWhitespace(nameof(masterFingerprint), masterFingerprint);
                var masterFingerPrintBytes = ByteHelpers.FromHex(masterFingerprint);
                MasterFingerprint = new HDFingerprint(masterFingerPrintBytes);
            }
            catch (ArgumentException)
            {
                MasterFingerprint = null;
            }

            SerialNumber = serialNumber;
            Type         = type;
            Path         = path;
            Error        = error;

            Ready       = true;
            Initialized = true;
            if (Error != null)
            {
                if (Error.Contains("Not initialized", StringComparison.OrdinalIgnoreCase))
                {
                    Initialized = false;
                }
                else if (Type == HardwareWalletType.Ledger &&
                         (Error.Contains("get_pubkey_at_path canceled", StringComparison.OrdinalIgnoreCase) ||
                          Error.Contains("Invalid status 6f04", StringComparison.OrdinalIgnoreCase) ||               // It comes when device asleep too.
                          Error.Contains("Device is asleep", StringComparison.OrdinalIgnoreCase)))
                {
                    Ready = false;
                }
            }
        }
コード例 #5
0
 public HardwareWalletInfo(string fingerprint, string serialNumber, HardwareWalletType type, string path, string error)
 {
     MasterFingerprint = new HDFingerprint(ByteHelpers.FromHex(fingerprint));
     SerialNumber      = serialNumber;
     Type  = type;
     Path  = path;
     Error = error;
 }
コード例 #6
0
 public FingerprintDeviceSelector(HDFingerprint fingerprint) : base(w =>
 {
     w.Add("--fingerprint");
     w.Add(fingerprint.ToString());
 })
 {
     Fingerprint = fingerprint;
 }
コード例 #7
0
        public static void ReadWrite(this CoinStream serializable, ref HDFingerprint lFingerprint)
        {
            var bytes = lFingerprint.ToBytes();

            serializable.ReadWrite(ref bytes);
            if (!serializable.Serializing)
            {
                lFingerprint = new HDFingerprint(bytes);
            }
        }
コード例 #8
0
        private async Task SaveKeyset(HDFingerprint fingerprint, Keyset v)
        {
            var dir = Path.Combine(DataDirectory, "keysets");

            if (!Directory.Exists(dir))
            {
                Directory.CreateDirectory(dir);
            }
            var keyset = Path.Combine(dir, $"{fingerprint}.json");
            await File.WriteAllTextAsync(keyset, JsonConvert.SerializeObject(v, JsonSettings));
        }
コード例 #9
0
        public HardwareWalletInfo(string masterFingerprint, string serialNumber, HardwareWalletType type, string path, string error)
        {
            Guard.NotNullOrEmptyOrWhitespace(nameof(masterFingerprint), masterFingerprint);
            var masterFingerPrintBytes = ByteHelpers.FromHex(masterFingerprint);

            MasterFingerprint = new HDFingerprint(masterFingerPrintBytes);
            SerialNumber      = serialNumber;
            Type  = type;
            Path  = path;
            Error = error;
        }
コード例 #10
0
        private bool KeySetExists(HDFingerprint fingerprint)
        {
            var dir = Path.Combine(DataDirectory, "keysets");

            if (!Directory.Exists(dir))
            {
                return(false);
            }
            var keyset = Path.Combine(dir, $"{fingerprint}.json");

            return(File.Exists(keyset));
        }
コード例 #11
0
        /// <inheritdoc />
        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            var s = (string)reader.Value;

            if (string.IsNullOrWhiteSpace(s))
            {
                return(null);
            }
            var fp = new HDFingerprint(ByteHelpers.FromHex(s));

            return(fp);
        }
コード例 #12
0
        private async Task <Keyset> OpenKeyset(HDFingerprint fingerprint)
        {
            var dir = Path.Combine(DataDirectory, "keysets");

            if (!Directory.Exists(dir))
            {
                throw new FormatException("Invalid keyset file");
            }
            var keyset = Path.Combine(dir, $"{fingerprint}.json");

            return(JsonConvert.DeserializeObject <Keyset>(await File.ReadAllTextAsync(keyset), JsonSettings) ??
                   throw new FormatException("Invalid keyset file"));
        }
コード例 #13
0
ファイル: KeyManager.cs プロジェクト: xmess7/WalletWasabi
        public static KeyManager CreateNew(out Mnemonic mnemonic, string password, string filePath = null)
        {
            password ??= "";

            mnemonic = new Mnemonic(Wordlist.English, WordCount.Twelve);
            ExtKey extKey          = mnemonic.DeriveExtKey(password);
            var    encryptedSecret = extKey.PrivateKey.GetEncryptedBitcoinSecret(password, Network.Main);

            HDFingerprint masterFingerprint = extKey.Neuter().PubKey.GetHDFingerPrint();
            KeyPath       keyPath           = DefaultAccountKeyPath;
            ExtPubKey     extPubKey         = extKey.Derive(keyPath).Neuter();

            return(new KeyManager(encryptedSecret, extKey.ChainCode, masterFingerprint, extPubKey, false, AbsoluteMinGapLimit, new BlockchainState(), filePath, keyPath));
        }
コード例 #14
0
ファイル: KeyManager.cs プロジェクト: xmess7/WalletWasabi
        public static KeyManager Recover(Mnemonic mnemonic, string password, string filePath = null, KeyPath accountKeyPath = null, int minGapLimit = AbsoluteMinGapLimit)
        {
            Guard.NotNull(nameof(mnemonic), mnemonic);
            password ??= "";

            ExtKey extKey          = mnemonic.DeriveExtKey(password);
            var    encryptedSecret = extKey.PrivateKey.GetEncryptedBitcoinSecret(password, Network.Main);

            HDFingerprint masterFingerprint = extKey.Neuter().PubKey.GetHDFingerPrint();

            KeyPath   keyPath   = accountKeyPath ?? DefaultAccountKeyPath;
            ExtPubKey extPubKey = extKey.Derive(keyPath).Neuter();

            return(new KeyManager(encryptedSecret, extKey.ChainCode, masterFingerprint, extPubKey, true, minGapLimit, new BlockchainState(), filePath, keyPath));
        }
コード例 #15
0
ファイル: KeyManager.cs プロジェクト: rude9/WalletWasabi
        public static KeyManager Recover(Mnemonic mnemonic, string password, string filePath = null)
        {
            Guard.NotNull(nameof(mnemonic), mnemonic);
            if (password is null)
            {
                password = "";
            }

            ExtKey extKey          = mnemonic.DeriveExtKey(password);
            var    encryptedSecret = extKey.PrivateKey.GetEncryptedBitcoinSecret(password, Network.Main);

            HDFingerprint masterFingerpring = extKey.Neuter().PubKey.GetHDFingerPrint();
            ExtPubKey     extPubKey         = extKey.Derive(AccountKeyPath).Neuter();

            return(new KeyManager(encryptedSecret, extKey.ChainCode, masterFingerpring, extPubKey, true, new BlockchainState(), filePath));
        }
コード例 #16
0
    private static KeyManager GetKeyManagerByColdcardJson(WalletManager manager, JObject jsonWallet, string walletFullPath)
    {
        var xpubString = jsonWallet["ExtPubKey"]?.ToString()
                         ?? throw new ArgumentNullException($"Can't get KeyManager, ExtPubKey was null.");

        var mfpString = jsonWallet["MasterFingerprint"]?.ToString()
                        ?? throw new ArgumentNullException($"Can't get KeyManager, MasterFingerprint was null.");

        // https://github.com/zkSNACKs/WalletWasabi/pull/1663#issuecomment-508073066
        // Coldcard 2.1.0 improperly implemented Wasabi skeleton fingerprint at first, so we must reverse byte order.
        // The solution was to add a ColdCardFirmwareVersion json field from 2.1.1 and correct the one generated by 2.1.0.
        var coldCardVersionString = jsonWallet["ColdCardFirmwareVersion"]?.ToString();
        var reverseByteOrder      = false;

        if (coldCardVersionString is null)
        {
            reverseByteOrder = true;
        }
        else
        {
            Version coldCardVersion = new(coldCardVersionString);

            if (coldCardVersion == new Version("2.1.0"))             // Should never happen though.
            {
                reverseByteOrder = true;
            }
        }

        var           bytes = ByteHelpers.FromHex(Guard.NotNullOrEmptyOrWhitespace(nameof(mfpString), mfpString, trim: true));
        HDFingerprint mfp   = reverseByteOrder ? new HDFingerprint(bytes.Reverse().ToArray()) : new HDFingerprint(bytes);

        if (manager.WalletExists(mfp))
        {
            throw new InvalidOperationException(WalletExistsErrorMessage);
        }

        ExtPubKey extPubKey = NBitcoinHelpers.BetterParseExtPubKey(xpubString);

        return(KeyManager.CreateNewHardwareWalletWatchOnly(mfp, extPubKey, manager.Network, walletFullPath));
    }
コード例 #17
0
    /// <seealso href="https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md#reference"/>
    public static WpkhDescriptors GetOutputDescriptors(Network network, HDFingerprint masterFingerprint, ExtKey notDerivedAccountKey, KeyPath accountKeyPath)
    {
        ExtKey accountKey = notDerivedAccountKey.Derive(accountKeyPath);

        // Optional part looks like this, for example: [2fc4a4f3/84'/0'/0']
        string optionalPart = $"[{masterFingerprint}/{accountKeyPath}]";

        // Optional part is followed by the actual private key: "tprv8ghYQhz7XQhoqDZG8SzbkqGCDTwAzyVVmUN3cUerPhUgK91Xvc4FaMJpYwrjuQ48WD7KdQ7Y6znKnaY9PXP8SiDLv1srjjs8NVYGuM7Hrrk"
        string privateDescriptor = $"{optionalPart}{accountKey.ToString(network)}";

        FlatSigningRepository internalRepo = new();
        FlatSigningRepository externalRepo = new();

        // Descriptor for all unhardened children (`/*`)
        OutputDescriptor internalPublicDescriptor = OutputDescriptor.Parse($"wpkh({privateDescriptor}/1/*)", network, requireCheckSum: false, internalRepo);
        OutputDescriptor externalPublicDescriptor = OutputDescriptor.Parse($"wpkh({privateDescriptor}/0/*)", network, requireCheckSum: false, externalRepo);

        externalPublicDescriptor.TryGetPrivateString(externalRepo, out string?privateExternalDescriptor);
        internalPublicDescriptor.TryGetPrivateString(internalRepo, out string?privateInternalDescriptor);

        return(new(internalPublicDescriptor, externalPublicDescriptor, privateInternalDescriptor, privateExternalDescriptor));
    }
コード例 #18
0
        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);
        }
コード例 #19
0
        public LoadWalletViewModel(WalletManagerViewModel owner, LoadWalletType loadWalletType)
            : base(loadWalletType == LoadWalletType.Password ? "Test Password" : (loadWalletType == LoadWalletType.Desktop ? "Load Wallet" : "Hardware Wallet"))
        {
            Owner          = owner;
            Password       = "";
            LoadWalletType = loadWalletType;
            Wallets        = new ObservableCollection <LoadWalletEntry>();
            WalletLock     = new object();

            this.WhenAnyValue(x => x.SelectedWallet)
            .Subscribe(selectedWallet => TrySetWalletStates());

            this.WhenAnyValue(x => x.IsWalletOpened)
            .Subscribe(isWalletOpened => TrySetWalletStates());

            this.WhenAnyValue(x => x.IsBusy)
            .Subscribe(x => TrySetWalletStates());

            this.WhenAnyValue(x => x.Password).Subscribe(async x =>
            {
                try
                {
                    if (x.NotNullAndNotEmpty())
                    {
                        char lastChar = x.Last();
                        if (lastChar == '\r' || lastChar == '\n')                         // If the last character is cr or lf then act like it'd be a sign to do the job.
                        {
                            Password = x.TrimEnd('\r', '\n');
                            await LoadKeyManagerAsync(requirePassword: true, isHardwareWallet: false);
                        }
                    }
                }
                catch (Exception ex)
                {
                    Logger.LogTrace(ex);
                }
            });

            LoadCommand           = ReactiveCommand.CreateFromTask(async() => await LoadWalletAsync(), this.WhenAnyValue(x => x.CanLoadWallet));
            TestPasswordCommand   = ReactiveCommand.CreateFromTask(async() => await LoadKeyManagerAsync(requirePassword: true, isHardwareWallet: false), this.WhenAnyValue(x => x.CanTestPassword));
            OpenFolderCommand     = ReactiveCommand.Create(OpenWalletsFolder);
            ImportColdcardCommand = ReactiveCommand.CreateFromTask(async() =>
            {
                try
                {
                    var ofd = new OpenFileDialog
                    {
                        AllowMultiple = false,
                        Title         = "Import Coldcard"
                    };

                    if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                    {
                        ofd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
                    }

                    var selected = await ofd.ShowAsync(Application.Current.MainWindow, fallBack: true);
                    if (selected != null && selected.Any())
                    {
                        var path       = selected.First();
                        var jsonString = await File.ReadAllTextAsync(path);
                        var json       = JObject.Parse(jsonString);
                        var xpubString = json["ExtPubKey"].ToString();
                        var mfpString  = json["MasterFingerprint"].ToString();

                        // https://github.com/zkSNACKs/WalletWasabi/pull/1663#issuecomment-508073066
                        // Coldcard 2.1.0 improperly implemented Wasabi skeleton fingerpring at first, so we must reverse byte order.
                        // The solution was to add a ColdCardFirmwareVersion json field from 2.1.1 and correct the one generated by 2.1.0.
                        var coldCardVersionString = json["ColdCardFirmwareVersion"]?.ToString();
                        var reverseByteOrder      = false;
                        if (coldCardVersionString is null)
                        {
                            reverseByteOrder = true;
                        }
                        else
                        {
                            Version coldCardVersion = new Version(coldCardVersionString);

                            if (coldCardVersion == new Version("2.1.0"))                             // Should never happen though.
                            {
                                reverseByteOrder = true;
                            }
                        }
                        HDFingerprint mfp   = NBitcoinHelpers.BetterParseHDFingerprint(mfpString, reverseByteOrder: reverseByteOrder);
                        ExtPubKey extPubKey = NBitcoinHelpers.BetterParseExtPubKey(xpubString);
                        Logger.LogInfo <LoadWalletViewModel>("Creating new wallet file.");
                        var walletName     = Global.GetNextHardwareWalletName(customPrefix: "Coldcard");
                        var walletFullPath = Global.GetWalletFullPath(walletName);
                        KeyManager.CreateNewHardwareWalletWatchOnly(mfp, extPubKey, walletFullPath);
                        owner.SelectLoadWallet();
                    }
                }
                catch (Exception ex)
                {
                    SetWarningMessage(ex.ToTypeMessageString());
                    Logger.LogError <LoadWalletViewModel>(ex);
                }
            }, outputScheduler: RxApp.MainThreadScheduler);

            OpenBrowserCommand = ReactiveCommand.Create <string>(x =>
            {
                IoHelpers.OpenBrowser(x);
            });

            OpenBrowserCommand.ThrownExceptions.Subscribe(Logger.LogWarning <LoadWalletViewModel>);
            LoadCommand.ThrownExceptions.Subscribe(Logger.LogWarning <LoadWalletViewModel>);
            TestPasswordCommand.ThrownExceptions.Subscribe(Logger.LogWarning <LoadWalletViewModel>);
            OpenFolderCommand.ThrownExceptions.Subscribe(Logger.LogWarning <LoadWalletViewModel>);
            ImportColdcardCommand.ThrownExceptions.Subscribe(Logger.LogWarning <LoadWalletViewModel>);

            SetLoadButtonText();

            IsHwWalletSearchTextVisible = LoadWalletType == LoadWalletType.Hardware;
        }
コード例 #20
0
        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);
        }
コード例 #21
0
 public static HwiOption Fingerprint(HDFingerprint fingerprint) => new HwiOption(HwiOptions.Fingerprint, fingerprint.ToString());
コード例 #22
0
    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);
    }
コード例 #23
0
    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);
    }
コード例 #24
0
ファイル: HwiClient.cs プロジェクト: carsenk/WalletWasabi
 public async Task PromptPinAsync(HDFingerprint fingerprint, CancellationToken cancel)
 => await PromptPinImplAsync(null, null, fingerprint, cancel);
コード例 #25
0
        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);
        }
コード例 #26
0
ファイル: HwiClient.cs プロジェクト: carsenk/WalletWasabi
 public async Task <ExtPubKey> GetXpubAsync(HDFingerprint fingerprint, KeyPath keyPath, CancellationToken cancel)
 => await GetXpubImplAsync(null, null, fingerprint, keyPath, cancel);
コード例 #27
0
        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);
        }
コード例 #28
0
ファイル: HwiClient.cs プロジェクト: carsenk/WalletWasabi
 public async Task <BitcoinWitPubKeyAddress> DisplayAddressAsync(HDFingerprint fingerprint, KeyPath keyPath, CancellationToken cancel)
 => await DisplayAddressImplAsync(null, null, fingerprint, keyPath, cancel);
コード例 #29
0
 public static KeyManager CreateNewHardwareWalletWatchOnly(HDFingerprint masterFingerpring, ExtPubKey extPubKey, string filePath = null)
 {
     return(new KeyManager(null, null, masterFingerpring, extPubKey, null, AbsoluteMinGapLimit, new BlockchainState(), filePath));
 }
コード例 #30
0
ファイル: HwiClient.cs プロジェクト: carsenk/WalletWasabi
 public async Task <PSBT> SignTxAsync(HDFingerprint fingerprint, PSBT psbt, CancellationToken cancel)
 => await SignTxImplAsync(null, null, fingerprint, psbt, cancel);