Beispiel #1
0
		/// <summary>
		/// Create a P2PKH wallet with one key
		/// </summary>
		/// <param name="rootKey">The master key to use</param>
		public WalletCreation(BitcoinExtPubKey rootKey)
		{
			Network = rootKey.Network;
			RootKeys = new[] { rootKey.ExtPubKey };
			SignatureRequired = 1;
			UseP2SH = false;
		}
Beispiel #2
0
		/// <summary>
		/// Create a P2PKH wallet with one key
		/// </summary>
		/// <param name="rootKey">The master key to use</param>
		public WalletCreation(BitcoinExtPubKey rootKey)
		{
			Network = rootKey.Network;
			RootKeys = new[] { rootKey.ExtPubKey };
			SignatureRequired = 1;
			UseP2SH = false;
			Name = Guid.NewGuid().ToString();
		}
        public DerivationStrategyBase Parse(string str)
        {
            if (str == null)
            {
                throw new ArgumentNullException(nameof(str));
            }
            str = str.Trim();

            HashSet <string> hintedLabels = new HashSet <string>();

            var hintDestination = HintScriptPubKey?.GetDestination();

            if (hintDestination != null)
            {
                if (hintDestination is KeyId)
                {
                    hintedLabels.Add("legacy");
                }
                if (hintDestination is ScriptId)
                {
                    hintedLabels.Add("p2sh");
                }
            }

            try
            {
                var result = new DerivationStrategyFactory(Network).Parse(str);
                return(FindMatch(hintedLabels, result));
            }
            catch
            {
            }

            Dictionary <uint, string[]> electrumMapping = new Dictionary <uint, string[]>();
            //Source https://github.com/spesmilo/electrum/blob/9edffd17542de5773e7284a8c8a2673c766bb3c3/lib/bitcoin.py
            var standard = 0x0488b21eU;

            electrumMapping.Add(standard, new[] { "legacy" });
            var p2wpkh_p2sh = 0x049d7cb2U;

            electrumMapping.Add(p2wpkh_p2sh, new string[] { "p2sh" });
            var p2wpkh = 0x4b24746U;

            electrumMapping.Add(p2wpkh, Array.Empty <string>());

            var parts = str.Split('-');

            for (int i = 0; i < parts.Length; i++)
            {
                if (IsLabel(parts[i]))
                {
                    hintedLabels.Add(parts[i].Substring(1, parts[i].Length - 2).ToLowerInvariant());
                    continue;
                }
                try
                {
                    var data = Encoders.Base58Check.DecodeData(parts[i]);
                    if (data.Length < 4)
                    {
                        continue;
                    }
                    var prefix         = Utils.ToUInt32(data, false);
                    var standardPrefix = Utils.ToBytes(ChainType == NBXplorer.ChainType.Main ? 0x0488b21eU : 0x043587cf, false);
                    for (int ii = 0; ii < 4; ii++)
                    {
                        data[ii] = standardPrefix[ii];
                    }

                    var derivationScheme = new BitcoinExtPubKey(Encoders.Base58Check.EncodeData(data), Network).ToString();
                    electrumMapping.TryGetValue(prefix, out string[] labels);
                    if (labels != null)
                    {
                        foreach (var label in labels)
                        {
                            hintedLabels.Add(label.ToLowerInvariant());
                        }
                    }
                    parts[i] = derivationScheme;
                }
                catch { continue; }
            }

            if (hintDestination != null)
            {
                if (hintDestination is WitKeyId)
                {
                    hintedLabels.Remove("legacy");
                    hintedLabels.Remove("p2sh");
                }
            }

            str = string.Join('-', parts.Where(p => !IsLabel(p)));
            foreach (var label in hintedLabels)
            {
                str = $"{str}-[{label}]";
            }

            return(FindMatch(hintedLabels, new DerivationStrategyFactory(Network).Parse(str)));
        }
        /// <inheritdoc />
        public async Task TumbleAsync(string originWalletName, string destinationWalletName, string originWalletPassword)
        {
            // Make sure it won't start new tumbling round if already started
            if (this.State == TumbleState.Tumbling)
            {
                this.logger.LogDebug("Tumbler is already running");
                throw new Exception("Tumbling is already running");
            }

            this.TumblingState.TumblerUri = new Uri(this.TumblerAddress);

            // Check if in initial block download
            if (!this.chain.IsDownloaded())
            {
                this.logger.LogDebug("Chain is still being downloaded: " + this.chain.Tip);
                throw new Exception("Chain is still being downloaded");
            }

            Wallet originWallet      = this.walletManager.GetWallet(originWalletName);
            Wallet destinationWallet = this.walletManager.GetWallet(destinationWalletName);

            // Check if password is valid before starting any cycles
            try
            {
                HdAddress tempAddress = originWallet.GetAccountsByCoinType(this.TumblingState.CoinType).First()
                                        .GetFirstUnusedReceivingAddress();
                originWallet.GetExtendedPrivateKeyForAddress(originWalletPassword, tempAddress);
            }
            catch (Exception)
            {
                this.logger.LogDebug("Origin wallet password appears to be invalid");
                throw new Exception("Origin wallet password appears to be invalid");
            }

            // Update the state and save
            this.TumblingState.DestinationWallet     = destinationWallet ?? throw new Exception($"Destination wallet not found. Have you created a wallet with name {destinationWalletName}?");
            this.TumblingState.DestinationWalletName = destinationWalletName;
            this.TumblingState.OriginWallet          = originWallet ?? throw new Exception($"Origin wallet not found. Have you created a wallet with name {originWalletName}?");
            this.TumblingState.OriginWalletName      = originWalletName;
            this.TumblingState.OriginWalletPassword  = originWalletPassword;

            var accounts = this.TumblingState.DestinationWallet.GetAccountsByCoinType(this.TumblingState.CoinType);
            // TODO: Possibly need to preserve destination account name in tumbling state. Default to first account for now
            string    accountName = accounts.First().Name;
            HdAccount destAccount = this.TumblingState.DestinationWallet.GetAccountByCoinType(accountName, this.TumblingState.CoinType);
            string    key         = destAccount.ExtendedPubKey;
            KeyPath   keyPath     = new KeyPath("0");

            // Stop and dispose onlymonitor
            if (this.broadcasterJob != null && this.broadcasterJob.Started)
            {
                await this.broadcasterJob.Stop().ConfigureAwait(false);
            }
            this.runtime?.Dispose();

            // Bypass Tor for integration tests
            FullNodeTumblerClientConfiguration config;

            if (this.TumblerAddress.Contains("127.0.0.1"))
            {
                config = new FullNodeTumblerClientConfiguration(this.TumblingState, onlyMonitor: false,
                                                                connectionTest: false, useProxy: false);
            }
            else
            {
                config = new FullNodeTumblerClientConfiguration(this.TumblingState, onlyMonitor: false,
                                                                connectionTest: false, useProxy: true);
            }

            this.runtime = await TumblerClientRuntime.FromConfigurationAsync(config).ConfigureAwait(false);

            // Check if origin wallet has a sufficient balance to begin tumbling at least 1 cycle
            if (!runtime.HasEnoughFundsForCycle(true))
            {
                this.logger.LogDebug("Insufficient funds in origin wallet");
                throw new Exception("Insufficient funds in origin wallet");
            }

            BitcoinExtPubKey extPubKey = new BitcoinExtPubKey(key, this.runtime.Network);

            if (key != null)
            {
                this.runtime.DestinationWallet =
                    new ClientDestinationWallet(extPubKey, keyPath, this.runtime.Repository, this.runtime.Network);
            }
            this.TumblerParameters = this.runtime.TumblerParameters;

            // Run onlymonitor mode
            this.broadcasterJob = this.runtime.CreateBroadcasterJob();
            this.broadcasterJob.Start();

            // Run tumbling mode
            this.stateMachine = new StateMachinesExecutor(this.runtime);
            this.stateMachine.Start();
        }
Beispiel #5
0
        private async Task FetchTransactionDetails(WalletId walletId, WalletPSBTReadyViewModel vm, BTCPayNetwork network)
        {
            var           psbtObject     = PSBT.Parse(vm.PSBT, network.NBitcoinNetwork);
            IHDKey        signingKey     = null;
            RootedKeyPath signingKeyPath = null;

            try
            {
                signingKey = new BitcoinExtPubKey(vm.SigningKey, network.NBitcoinNetwork);
            }
            catch { }
            try
            {
                signingKey = signingKey ?? new BitcoinExtKey(vm.SigningKey, network.NBitcoinNetwork);
            }
            catch { }

            try
            {
                signingKeyPath = RootedKeyPath.Parse(vm.SigningKeyPath);
            }
            catch { }

            var derivationSchemeSettings = await GetDerivationSchemeSettings(walletId);

            if (signingKey == null || signingKeyPath == null)
            {
                var signingKeySettings = derivationSchemeSettings.GetSigningAccountKeySettings();
                if (signingKey == null)
                {
                    signingKey    = signingKeySettings.AccountKey;
                    vm.SigningKey = signingKey.ToString();
                }
                if (vm.SigningKeyPath == null)
                {
                    signingKeyPath    = signingKeySettings.GetRootedKeyPath();
                    vm.SigningKeyPath = signingKeyPath?.ToString();
                }
            }

            if (psbtObject.IsAllFinalized())
            {
                vm.CanCalculateBalance = false;
            }
            else
            {
                var balanceChange = psbtObject.GetBalance(derivationSchemeSettings.AccountDerivation, signingKey, signingKeyPath);
                vm.BalanceChange       = ValueToString(balanceChange, network);
                vm.CanCalculateBalance = true;
                vm.Positive            = balanceChange >= Money.Zero;
            }

            foreach (var output in psbtObject.Outputs)
            {
                var dest = new WalletPSBTReadyViewModel.DestinationViewModel();
                vm.Destinations.Add(dest);
                var mine           = output.HDKeysFor(derivationSchemeSettings.AccountDerivation, signingKey, signingKeyPath).Any();
                var balanceChange2 = output.Value;
                if (!mine)
                {
                    balanceChange2 = -balanceChange2;
                }
                dest.Balance     = ValueToString(balanceChange2, network);
                dest.Positive    = balanceChange2 >= Money.Zero;
                dest.Destination = output.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork)?.ToString() ?? output.ScriptPubKey.ToString();
            }

            if (psbtObject.TryGetFee(out var fee))
            {
                vm.Destinations.Add(new WalletPSBTReadyViewModel.DestinationViewModel()
                {
                    Positive    = false,
                    Balance     = ValueToString(-fee, network),
                    Destination = "Mining fees"
                });
            }
            if (psbtObject.TryGetEstimatedFeeRate(out var feeRate))
            {
                vm.FeeRate = feeRate.ToString();
            }

            if (!psbtObject.IsAllFinalized() && !psbtObject.TryFinalize(out var errors))
            {
                vm.SetErrors(errors);
            }
        }
Beispiel #6
0
        private void CreateCoin(TransactionBuilder builder, List <Transaction> knownTransactions, ScriptPubKeyType addressType, Money money, BitcoinExtPubKey xpub, string path)
        {
            var pubkey = xpub.Derive(new KeyPath(path)).ExtPubKey.PubKey;

            if (addressType == ScriptPubKeyType.Legacy)
            {
                var prevTx = xpub.Network.CreateTransaction();
                prevTx.Inputs.Add(RandomOutpoint(), new Key().ScriptPubKey);
                var txout = prevTx.Outputs.Add(money, pubkey.GetScriptPubKey(addressType));
                var coin  = new Coin(new OutPoint(prevTx, 0), txout);
                builder.AddCoins(coin);
                knownTransactions.Add(prevTx);
            }
            if (addressType == ScriptPubKeyType.Segwit)
            {
                var outpoint = RandomOutpoint();
                var txout    = xpub.Network.Consensus.ConsensusFactory.CreateTxOut();
                txout.Value        = money;
                txout.ScriptPubKey = pubkey.GetScriptPubKey(addressType);
                var coin = new Coin(outpoint, txout);
                builder.AddCoins(coin);
            }
            if (addressType == ScriptPubKeyType.SegwitP2SH)
            {
                var outpoint = RandomOutpoint();
                var txout    = xpub.Network.Consensus.ConsensusFactory.CreateTxOut();
                txout.Value        = money;
                txout.ScriptPubKey = pubkey.GetScriptPubKey(addressType);
                var coin = new Coin(outpoint, txout).ToScriptCoin(pubkey.GetScriptPubKey(ScriptPubKeyType.Segwit));
                builder.AddCoins(coin);
            }
        }
Beispiel #7
0
        private async Task FetchTransactionDetails(DerivationSchemeSettings derivationSchemeSettings, WalletPSBTReadyViewModel vm, BTCPayNetwork network)
        {
            var psbtObject = PSBT.Parse(vm.PSBT, network.NBitcoinNetwork);

            if (!psbtObject.IsAllFinalized())
            {
                psbtObject = await ExplorerClientProvider.UpdatePSBT(derivationSchemeSettings, psbtObject) ?? psbtObject;
            }
            IHDKey        signingKey     = null;
            RootedKeyPath signingKeyPath = null;

            try
            {
                signingKey = new BitcoinExtPubKey(vm.SigningKey, network.NBitcoinNetwork);
            }
            catch { }
            try
            {
                signingKey = signingKey ?? new BitcoinExtKey(vm.SigningKey, network.NBitcoinNetwork);
            }
            catch { }

            try
            {
                signingKeyPath = RootedKeyPath.Parse(vm.SigningKeyPath);
            }
            catch { }

            if (signingKey == null || signingKeyPath == null)
            {
                var signingKeySettings = derivationSchemeSettings.GetSigningAccountKeySettings();
                if (signingKey == null)
                {
                    signingKey    = signingKeySettings.AccountKey;
                    vm.SigningKey = signingKey.ToString();
                }
                if (vm.SigningKeyPath == null)
                {
                    signingKeyPath    = signingKeySettings.GetRootedKeyPath();
                    vm.SigningKeyPath = signingKeyPath?.ToString();
                }
            }

            if (psbtObject.IsAllFinalized())
            {
                vm.CanCalculateBalance = false;
            }
            else
            {
                var balanceChange = psbtObject.GetBalance(derivationSchemeSettings.AccountDerivation, signingKey, signingKeyPath);
                vm.BalanceChange       = ValueToString(balanceChange, network);
                vm.CanCalculateBalance = true;
                vm.Positive            = balanceChange >= Money.Zero;
            }
            vm.Inputs = new List <WalletPSBTReadyViewModel.InputViewModel>();
            foreach (var input in psbtObject.Inputs)
            {
                var inputVm = new WalletPSBTReadyViewModel.InputViewModel();
                vm.Inputs.Add(inputVm);
                var mine           = input.HDKeysFor(derivationSchemeSettings.AccountDerivation, signingKey, signingKeyPath).Any();
                var balanceChange2 = input.GetTxOut()?.Value ?? Money.Zero;
                if (mine)
                {
                    balanceChange2 = -balanceChange2;
                }
                inputVm.BalanceChange = ValueToString(balanceChange2, network);
                inputVm.Positive      = balanceChange2 >= Money.Zero;
                inputVm.Index         = (int)input.Index;
            }
            vm.Destinations = new List <WalletPSBTReadyViewModel.DestinationViewModel>();
            foreach (var output in psbtObject.Outputs)
            {
                var dest = new WalletPSBTReadyViewModel.DestinationViewModel();
                vm.Destinations.Add(dest);
                var mine           = output.HDKeysFor(derivationSchemeSettings.AccountDerivation, signingKey, signingKeyPath).Any();
                var balanceChange2 = output.Value;
                if (!mine)
                {
                    balanceChange2 = -balanceChange2;
                }
                dest.Balance     = ValueToString(balanceChange2, network);
                dest.Positive    = balanceChange2 >= Money.Zero;
                dest.Destination = output.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork)?.ToString() ?? output.ScriptPubKey.ToString();
            }

            if (psbtObject.TryGetFee(out var fee))
            {
                vm.Destinations.Add(new WalletPSBTReadyViewModel.DestinationViewModel()
                {
                    Positive    = false,
                    Balance     = ValueToString(-fee, network),
                    Destination = "Mining fees"
                });
            }
            if (psbtObject.TryGetEstimatedFeeRate(out var feeRate))
            {
                vm.FeeRate = feeRate.ToString();
            }

            var sanityErrors = psbtObject.CheckSanity();

            if (sanityErrors.Count != 0)
            {
                vm.SetErrors(sanityErrors);
            }
            else if (!psbtObject.IsAllFinalized() && !psbtObject.TryFinalize(out var errors))
            {
                vm.SetErrors(errors);
            }
        }
        /// <summary>
        /// WARNING: ONLY CHECKS CONFIRMED KEYPATHS
        /// </summary>
        public static IEnumerable <BitcoinWitPubKeyAddress> GetUnusedBech32Keys(int count, bool isInternal, BitcoinExtPubKey bitcoinExtPubKey, IEnumerable <FilterModel> filters)
        {
            var change      = isInternal ? 1 : 0;
            var filterArray = filters.ToArray();

            var startingKey = 0;             // Where to start getting keys one by one.
            int i           = 0;

            while (true)
            {
                if (Match(bitcoinExtPubKey, change, i, filterArray, out _))
                {
                    startingKey = i + 1;
                    if (i == 0)
                    {
                        i = 1;
                    }
                    else
                    {
                        i *= 2;
                    }
                }
                else
                {
                    break;
                }
            }

            i = startingKey;
            int returnedNum = 0;

            while (true)
            {
                if (!Match(bitcoinExtPubKey, change, i, filterArray, out PubKey pubKey))
                {
                    yield return(pubKey.GetSegwitAddress(bitcoinExtPubKey.Network));

                    returnedNum++;
                    if (returnedNum >= count)
                    {
                        yield break;
                    }
                }

                i++;
            }
        }
Beispiel #9
0
        /// <inheritdoc />
        public async Task TumbleAsync(string originWalletName, string destinationWalletName, string originWalletPassword)
        {
            // Make sure it won't start new tumbling round if already started
            if (this.State == TumbleState.Tumbling)
            {
                this.logger.LogDebug("Tumbler is already running");
                throw new Exception("Tumbling is already running");
            }

            this.tumblingState.TumblerUri = new Uri(this.TumblerAddress);

            // Check if in initial block download
            if (!this.chain.IsDownloaded())
            {
                this.logger.LogDebug("Chain is still being downloaded: " + this.chain.Tip);
                throw new Exception("Chain is still being downloaded");
            }

            Wallet destinationWallet = this.walletManager.GetWallet(destinationWalletName);
            Wallet originWallet      = this.walletManager.GetWallet(originWalletName);

            // Check if origin wallet has a balance
            WalletBalanceModel model = new WalletBalanceModel();

            var originWalletAccounts = this.walletManager.GetAccounts(originWallet.Name).ToList();
            var originConfirmed      = new Money(0);
            var originUnconfirmed    = new Money(0);

            foreach (var originAccount in originWallet.GetAccountsByCoinType(this.tumblingState.CoinType))
            {
                var result = originAccount.GetSpendableAmount();

                originConfirmed   += result.ConfirmedAmount;
                originUnconfirmed += result.UnConfirmedAmount;
            }

            // Should ideally take network transaction fee into account too, but that is dynamic
            if ((originConfirmed + originUnconfirmed) <= (this.TumblerParameters.Denomination + this.TumblerParameters.Fee))
            {
                this.logger.LogDebug("Insufficient funds in origin wallet");
                throw new Exception("Insufficient funds in origin wallet");
            }

            // TODO: Check if password is valid before starting any cycles

            // Update the state and save
            this.tumblingState.DestinationWallet     = destinationWallet ?? throw new Exception($"Destination wallet not found. Have you created a wallet with name {destinationWalletName}?");
            this.tumblingState.DestinationWalletName = destinationWalletName;
            this.tumblingState.OriginWallet          = originWallet ?? throw new Exception($"Origin wallet not found. Have you created a wallet with name {originWalletName}?");
            this.tumblingState.OriginWalletName      = originWalletName;
            this.tumblingState.OriginWalletPassword  = originWalletPassword;

            var accounts = this.tumblingState.DestinationWallet.GetAccountsByCoinType(this.tumblingState.CoinType);
            // TODO: Possibly need to preserve destination account name in tumbling state. Default to first account for now
            string accountName = null;

            foreach (var account in accounts)
            {
                if (account.Index == 0)
                {
                    accountName = account.Name;
                }
            }
            var destAccount = this.tumblingState.DestinationWallet.GetAccountByCoinType(accountName, this.tumblingState.CoinType);

            var key     = destAccount.ExtendedPubKey;
            var keyPath = new KeyPath("0");

            // Stop and dispose onlymonitor
            if (this.broadcasterJob != null && this.broadcasterJob.Started)
            {
                await this.broadcasterJob.Stop().ConfigureAwait(false);
            }
            this.runtime?.Dispose();

            var config = new FullNodeTumblerClientConfiguration(this.tumblingState, onlyMonitor: false);

            this.runtime = await TumblerClientRuntime.FromConfigurationAsync(config).ConfigureAwait(false);

            var extPubKey = new BitcoinExtPubKey(key, this.runtime.Network);

            if (key != null)
            {
                this.runtime.DestinationWallet =
                    new ClientDestinationWallet(extPubKey, keyPath, this.runtime.Repository, this.runtime.Network);
            }
            this.TumblerParameters = this.runtime.TumblerParameters;
            // Run onlymonitor mode
            this.broadcasterJob = this.runtime.CreateBroadcasterJob();
            this.broadcasterJob.Start();

            // Run tumbling mode
            this.stateMachine = new StateMachinesExecutor(this.runtime);
            this.stateMachine.Start();

            this.State = TumbleState.Tumbling;

            return;
        }
Beispiel #10
0
        public async Task <IActionResult> VaultBridgeConnection(string cryptoCode = null,
                                                                [ModelBinder(typeof(WalletIdModelBinder))]
                                                                WalletId walletId = null)
        {
            if (!HttpContext.WebSockets.IsWebSocketRequest)
            {
                return(NotFound());
            }
            cryptoCode = cryptoCode ?? walletId.CryptoCode;
            using (var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10)))
            {
                var cancellationToken = cts.Token;
                var network           = Networks.GetNetwork <BTCPayNetwork>(cryptoCode);
                if (network == null)
                {
                    return(NotFound());
                }
                var websocket = await HttpContext.WebSockets.AcceptWebSocketAsync();

                var hwi = new Hwi.HwiClient(network.NBitcoinNetwork)
                {
                    Transport = new HwiWebSocketTransport(websocket)
                };
                Hwi.HwiDeviceClient device      = null;
                HwiEnumerateEntry   deviceEntry = null;
                HDFingerprint?      fingerprint = null;
                string password        = null;
                int?   pin             = null;
                var    websocketHelper = new WebSocketHelper(websocket);

                async Task <bool> RequireDeviceUnlocking()
                {
                    if (deviceEntry == null)
                    {
                        await websocketHelper.Send("{ \"error\": \"need-device\"}", cancellationToken);

                        return(true);
                    }
                    if (deviceEntry.NeedsPinSent is true && pin is null)
                    {
                        await websocketHelper.Send("{ \"error\": \"need-pin\"}", cancellationToken);

                        return(true);
                    }
                    if (deviceEntry.NeedsPassphraseSent is true && password == null)
                    {
                        await websocketHelper.Send("{ \"error\": \"need-passphrase\"}", cancellationToken);

                        return(true);
                    }
                    return(false);
                }

                JObject o = null;
                try
                {
                    while (true)
                    {
                        var command = await websocketHelper.NextMessageAsync(cancellationToken);

                        switch (command)
                        {
                        case "set-passphrase":
                            device.Password = await websocketHelper.NextMessageAsync(cancellationToken);

                            password = device.Password;
                            break;

                        case "ask-sign":
                            if (await RequireDeviceUnlocking())
                            {
                                continue;
                            }
                            if (walletId == null)
                            {
                                await websocketHelper.Send("{ \"error\": \"invalid-walletId\"}", cancellationToken);

                                continue;
                            }
                            if (fingerprint is null)
                            {
                                fingerprint = (await device.GetXPubAsync(new KeyPath("44'"), cancellationToken)).ExtPubKey.ParentFingerprint;
                            }
                            await websocketHelper.Send("{ \"info\": \"ready\"}", cancellationToken);

                            o = JObject.Parse(await websocketHelper.NextMessageAsync(cancellationToken));
                            var authorization = await _authorizationService.AuthorizeAsync(User, Policies.CanModifyStoreSettings.Key);

                            if (!authorization.Succeeded)
                            {
                                await websocketHelper.Send("{ \"error\": \"not-authorized\"}", cancellationToken);

                                continue;
                            }
                            var psbt = PSBT.Parse(o["psbt"].Value <string>(), network.NBitcoinNetwork);
                            var derivationSettings = GetDerivationSchemeSettings(walletId);
                            derivationSettings.RebaseKeyPaths(psbt);
                            var signing = derivationSettings.GetSigningAccountKeySettings();
                            if (signing.GetRootedKeyPath()?.MasterFingerprint != fingerprint)
                            {
                                await websocketHelper.Send("{ \"error\": \"wrong-wallet\"}", cancellationToken);

                                continue;
                            }
                            try
                            {
                                psbt = await device.SignPSBTAsync(psbt, cancellationToken);
                            }
                            catch (Hwi.HwiException)
                            {
                                await websocketHelper.Send("{ \"error\": \"user-reject\"}", cancellationToken);

                                continue;
                            }
                            o = new JObject();
                            o.Add("psbt", psbt.ToBase64());
                            await websocketHelper.Send(o.ToString(), cancellationToken);

                            break;

                        case "ask-pin":
                            if (device == null)
                            {
                                await websocketHelper.Send("{ \"error\": \"need-device\"}", cancellationToken);

                                continue;
                            }
                            await device.PromptPinAsync(cancellationToken);

                            await websocketHelper.Send("{ \"info\": \"prompted, please input the pin\"}", cancellationToken);

                            pin = int.Parse(await websocketHelper.NextMessageAsync(cancellationToken), CultureInfo.InvariantCulture);
                            if (await device.SendPinAsync(pin.Value, cancellationToken))
                            {
                                await websocketHelper.Send("{ \"info\": \"the pin is correct\"}", cancellationToken);
                            }
                            else
                            {
                                await websocketHelper.Send("{ \"error\": \"incorrect-pin\"}", cancellationToken);

                                continue;
                            }
                            break;

                        case "ask-xpubs":
                            if (await RequireDeviceUnlocking())
                            {
                                continue;
                            }
                            JObject          result  = new JObject();
                            var              factory = network.NBXplorerNetwork.DerivationStrategyFactory;
                            var              keyPath = new KeyPath("84'").Derive(network.CoinType).Derive(0, true);
                            BitcoinExtPubKey xpub    = await device.GetXPubAsync(keyPath);

                            if (fingerprint is null)
                            {
                                fingerprint = (await device.GetXPubAsync(new KeyPath("44'"), cancellationToken)).ExtPubKey.ParentFingerprint;
                            }
                            result["fingerprint"] = fingerprint.Value.ToString();
                            var strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
                            {
                                ScriptPubKeyType = ScriptPubKeyType.Segwit
                            });
                            AddDerivationSchemeToJson("segwit", result, keyPath, xpub, strategy);
                            keyPath = new KeyPath("49'").Derive(network.CoinType).Derive(0, true);
                            xpub    = await device.GetXPubAsync(keyPath);

                            strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
                            {
                                ScriptPubKeyType = ScriptPubKeyType.SegwitP2SH
                            });
                            AddDerivationSchemeToJson("segwitWrapped", result, keyPath, xpub, strategy);
                            keyPath = new KeyPath("44'").Derive(network.CoinType).Derive(0, true);
                            xpub    = await device.GetXPubAsync(keyPath);

                            strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
                            {
                                ScriptPubKeyType = ScriptPubKeyType.Legacy
                            });
                            AddDerivationSchemeToJson("legacy", result, keyPath, xpub, strategy);
                            await websocketHelper.Send(result.ToString(), cancellationToken);

                            break;

                        case "ask-device":
                            password    = null;
                            pin         = null;
                            deviceEntry = null;
                            device      = null;
                            var entries = (await hwi.EnumerateEntriesAsync(cancellationToken)).ToList();
                            deviceEntry = entries.FirstOrDefault();
                            if (deviceEntry == null)
                            {
                                await websocketHelper.Send("{ \"error\": \"no-device\"}", cancellationToken);

                                continue;
                            }
                            device      = new HwiDeviceClient(hwi, deviceEntry.DeviceSelector, deviceEntry.Model, deviceEntry.Fingerprint);
                            fingerprint = device.Fingerprint;
                            JObject json = new JObject();
                            json.Add("model", device.Model.ToString());
                            json.Add("fingerprint", device.Fingerprint?.ToString());
                            await websocketHelper.Send(json.ToString(), cancellationToken);

                            break;
                        }
                    }
                }
                catch (Exception ex)
                {
                    JObject obj = new JObject();
                    obj.Add("error", "unknown-error");
                    obj.Add("details", ex.ToString());
                    try
                    {
                        await websocketHelper.Send(obj.ToString(), cancellationToken);
                    }
                    catch { }
                }
                finally
                {
                    await websocketHelper.DisposeAsync(cancellationToken);
                }
            }
            return(new EmptyResult());
        }
Beispiel #11
0
 private void AddDerivationSchemeToJson(string propertyName, JObject result, KeyPath keyPath, BitcoinExtPubKey xpub, DerivationStrategyBase strategy)
 {
     result.Add(new JProperty(propertyName, new JObject()
     {
         new JProperty("strategy", strategy.ToString()),
         new JProperty("accountKey", xpub.ToString()),
         new JProperty("keyPath", keyPath.ToString()),
     }));
 }
Beispiel #12
0
 /// <summary>
 /// Create a taproot signature derivation strategy from public key
 /// </summary>
 /// <param name="publicKey">The public key of the wallet</param>
 /// <param name="options">Derivation options</param>
 /// <returns></returns>
 public TaprootDerivationStrategy CreateTaprootDerivationStrategy(BitcoinExtPubKey publicKey, ReadOnlyDictionary <string, bool> options = null)
 {
     return(new TaprootDerivationStrategy(publicKey, options));
 }
Beispiel #13
0
        /// <summary>
        /// Create a new wallet P2PKH with one key
        /// </summary>
        /// <param name="rootKey"></param>
        /// <param name="keyPoolSize"></param>
        public Wallet(BitcoinExtPubKey rootKey, int keyPoolSize = 500)
#pragma warning disable CS0612 // Type or member is obsolete
            : this(new WalletCreation(rootKey), keyPoolSize)
#pragma warning restore CS0612 // Type or member is obsolete
        {
        }
        public DerivationStrategyBase Parse(string str)
        {
            if (str == null)
            {
                throw new ArgumentNullException(nameof(str));
            }
            str = str.Trim();

            HashSet <string> hintedLabels = new HashSet <string>();

            var hintDestination = HintScriptPubKey?.GetDestination();

            if (hintDestination != null)
            {
                if (hintDestination is KeyId)
                {
                    hintedLabels.Add("legacy");
                }
                if (hintDestination is ScriptId)
                {
                    hintedLabels.Add("p2sh");
                }
            }

            if (!Network.Consensus.SupportSegwit)
            {
                hintedLabels.Add("legacy");
            }

            try
            {
                var result = BtcPayNetwork.NBXplorerNetwork.DerivationStrategyFactory.Parse(str);
                return(FindMatch(hintedLabels, result));
            }
            catch
            {
            }

            var  parts    = str.Split('-');
            bool hasLabel = false;

            for (int i = 0; i < parts.Length; i++)
            {
                if (IsLabel(parts[i]))
                {
                    if (!hasLabel)
                    {
                        hintedLabels.Clear();
                        if (!Network.Consensus.SupportSegwit)
                        {
                            hintedLabels.Add("legacy");
                        }
                    }
                    hasLabel = true;
                    hintedLabels.Add(parts[i].Substring(1, parts[i].Length - 2).ToLowerInvariant());
                    continue;
                }
                try
                {
                    var data = Network.GetBase58CheckEncoder().DecodeData(parts[i]);
                    if (data.Length < 4)
                    {
                        continue;
                    }
                    var prefix = Utils.ToUInt32(data, false);

                    var standardPrefix = Utils.ToBytes(0x0488b21eU, false);
                    for (int ii = 0; ii < 4; ii++)
                    {
                        data[ii] = standardPrefix[ii];
                    }
                    var derivationScheme = new BitcoinExtPubKey(Network.GetBase58CheckEncoder().EncodeData(data), Network.Main).ToNetwork(Network).ToString();

                    if (BtcPayNetwork.ElectrumMapping.TryGetValue(prefix, out var type))
                    {
                        switch (type)
                        {
                        case DerivationType.Legacy:
                            hintedLabels.Add("legacy");
                            break;

                        case DerivationType.SegwitP2SH:
                            hintedLabels.Add("p2sh");
                            break;
                        }
                    }
                    parts[i] = derivationScheme;
                }
                catch { continue; }
            }

            if (hintDestination != null)
            {
                if (hintDestination is WitKeyId)
                {
                    hintedLabels.Remove("legacy");
                    hintedLabels.Remove("p2sh");
                }
            }

            str = string.Join('-', parts.Where(p => !IsLabel(p)));
            foreach (var label in hintedLabels)
            {
                str = $"{str}-[{label}]";
            }

            return(FindMatch(hintedLabels, BtcPayNetwork.NBXplorerNetwork.DerivationStrategyFactory.Parse(str)));
        }
Beispiel #15
0
		/// <summary>
		/// Create a new wallet P2PKH with one key
		/// </summary>
		/// <param name="rootKey"></param>
		/// <param name="keyPoolSize"></param>
		public Wallet(BitcoinExtPubKey rootKey, int keyPoolSize = 500)
			: this(new WalletCreation(rootKey), keyPoolSize)
		{
		}
Beispiel #16
0
 /// <summary>
 /// Create a new wallet P2PKH with one key
 /// </summary>
 /// <param name="rootKey"></param>
 /// <param name="keyPoolSize"></param>
 public Wallet(BitcoinExtPubKey rootKey, int keyPoolSize = 500)
     : this(new WalletCreation(rootKey), keyPoolSize)
 {
 }
Beispiel #17
0
 public KeyPair(ExtPubKey pubKey)
 {
     PubKey  = new BitcoinExtPubKey(pubKey, Network.TestNet);
     Address = PubKey.ScriptPubKey.GetDestinationAddress(Network.TestNet);
 }
        public async Task <IActionResult> VaultBridgeConnection(string cryptoCode = null,
                                                                [ModelBinder(typeof(WalletIdModelBinder))]
                                                                WalletId walletId = null)
        {
            if (!HttpContext.WebSockets.IsWebSocketRequest)
            {
                return(NotFound());
            }
            cryptoCode = cryptoCode ?? walletId.CryptoCode;
            bool versionChecked = false;

            using (var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10)))
            {
                var cancellationToken = cts.Token;
                var network           = Networks.GetNetwork <BTCPayNetwork>(cryptoCode);
                if (network == null)
                {
                    return(NotFound());
                }
                var websocket = await HttpContext.WebSockets.AcceptWebSocketAsync();

                var hwi = new Hwi.HwiClient(network.NBitcoinNetwork)
                {
                    Transport = new HwiWebSocketTransport(websocket)
                };
                Hwi.HwiDeviceClient device      = null;
                HwiEnumerateEntry   deviceEntry = null;
                HDFingerprint?      fingerprint = null;
                string password        = null;
                var    websocketHelper = new WebSocketHelper(websocket);

                async Task <bool> RequireDeviceUnlocking()
                {
                    if (deviceEntry == null)
                    {
                        await websocketHelper.Send("{ \"error\": \"need-device\"}", cancellationToken);

                        return(true);
                    }
                    if (deviceEntry.Code is HwiErrorCode.DeviceNotInitialized)
                    {
                        await websocketHelper.Send("{ \"error\": \"need-initialized\"}", cancellationToken);

                        return(true);
                    }
                    if (deviceEntry.Code is HwiErrorCode.DeviceNotReady)
                    {
                        if (IsTrezorT(deviceEntry))
                        {
                            await websocketHelper.Send("{ \"error\": \"need-passphrase-on-device\"}", cancellationToken);

                            return(true);
                        }
                        else if (deviceEntry.NeedsPinSent is true)
                        {
                            await websocketHelper.Send("{ \"error\": \"need-pin\"}", cancellationToken);

                            return(true);
                        }
                        else if (deviceEntry.NeedsPassphraseSent is true && password is null)
                        {
                            await websocketHelper.Send("{ \"error\": \"need-passphrase\"}", cancellationToken);

                            return(true);
                        }
                    }
                    return(false);
                }

                JObject o = null;
                try
                {
                    while (true)
                    {
                        var command = await websocketHelper.NextMessageAsync(cancellationToken);

                        switch (command)
                        {
                        case "set-passphrase":
                            device.Password = await websocketHelper.NextMessageAsync(cancellationToken);

                            password = device.Password;
                            break;

                        case "ask-sign":
                            if (await RequireDeviceUnlocking())
                            {
                                continue;
                            }
                            if (walletId == null)
                            {
                                await websocketHelper.Send("{ \"error\": \"invalid-walletId\"}", cancellationToken);

                                continue;
                            }
                            if (fingerprint is null)
                            {
                                fingerprint = (await device.GetXPubAsync(new KeyPath("44'"), cancellationToken)).ExtPubKey.ParentFingerprint;
                            }
                            await websocketHelper.Send("{ \"info\": \"ready\"}", cancellationToken);

                            o = JObject.Parse(await websocketHelper.NextMessageAsync(cancellationToken));
                            var authorization = await _authorizationService.AuthorizeAsync(User, Policies.CanModifyStoreSettings);

                            if (!authorization.Succeeded)
                            {
                                await websocketHelper.Send("{ \"error\": \"not-authorized\"}", cancellationToken);

                                continue;
                            }
                            var psbt = PSBT.Parse(o["psbt"].Value <string>(), network.NBitcoinNetwork);
                            var derivationSettings = GetDerivationSchemeSettings(walletId);
                            derivationSettings.RebaseKeyPaths(psbt);
                            var signing = derivationSettings.GetSigningAccountKeySettings();
                            if (signing.GetRootedKeyPath()?.MasterFingerprint != fingerprint)
                            {
                                await websocketHelper.Send("{ \"error\": \"wrong-wallet\"}", cancellationToken);

                                continue;
                            }
                            var signableInputs = psbt.Inputs
                                                 .SelectMany(i => i.HDKeyPaths)
                                                 .Where(i => i.Value.MasterFingerprint == fingerprint)
                                                 .ToArray();
                            if (signableInputs.Length > 0)
                            {
                                var actualPubKey = (await device.GetXPubAsync(signableInputs[0].Value.KeyPath)).GetPublicKey();
                                if (actualPubKey != signableInputs[0].Key)
                                {
                                    await websocketHelper.Send("{ \"error\": \"wrong-keypath\"}", cancellationToken);

                                    continue;
                                }
                            }
                            try
                            {
                                psbt = await device.SignPSBTAsync(psbt, cancellationToken);
                            }
                            catch (Hwi.HwiException)
                            {
                                await websocketHelper.Send("{ \"error\": \"user-reject\"}", cancellationToken);

                                continue;
                            }
                            o = new JObject();
                            o.Add("psbt", psbt.ToBase64());
                            await websocketHelper.Send(o.ToString(), cancellationToken);

                            break;

                        case "display-address":
                            if (await RequireDeviceUnlocking())
                            {
                                continue;
                            }
                            var k = RootedKeyPath.Parse(await websocketHelper.NextMessageAsync(cancellationToken));
                            await device.DisplayAddressAsync(GetScriptPubKeyType(k), k.KeyPath, cancellationToken);

                            await websocketHelper.Send("{ \"info\": \"ok\"}", cancellationToken);

                            break;

                        case "ask-pin":
                            if (device == null)
                            {
                                await websocketHelper.Send("{ \"error\": \"need-device\"}", cancellationToken);

                                continue;
                            }
                            try
                            {
                                await device.PromptPinAsync(cancellationToken);
                            }
                            catch (HwiException ex) when(ex.ErrorCode == HwiErrorCode.DeviceAlreadyUnlocked)
                            {
                                await websocketHelper.Send("{ \"error\": \"device-already-unlocked\"}", cancellationToken);

                                continue;
                            }
                            await websocketHelper.Send("{ \"info\": \"prompted, please input the pin\"}", cancellationToken);

                            var pin = int.Parse(await websocketHelper.NextMessageAsync(cancellationToken), CultureInfo.InvariantCulture);
                            if (await device.SendPinAsync(pin, cancellationToken))
                            {
                                goto askdevice;
                            }
                            else
                            {
                                await websocketHelper.Send("{ \"error\": \"incorrect-pin\"}", cancellationToken);

                                continue;
                            }

                        case "ask-xpub":
                            if (await RequireDeviceUnlocking())
                            {
                                continue;
                            }
                            await websocketHelper.Send("{ \"info\": \"ok\"}", cancellationToken);

                            var     askedXpub     = JObject.Parse(await websocketHelper.NextMessageAsync(cancellationToken));
                            var     addressType   = askedXpub["addressType"].Value <string>();
                            var     accountNumber = askedXpub["accountNumber"].Value <int>();
                            JObject result        = new JObject();
                            var     factory       = network.NBXplorerNetwork.DerivationStrategyFactory;
                            if (fingerprint is null)
                            {
                                fingerprint = (await device.GetXPubAsync(new KeyPath("44'"), cancellationToken)).ExtPubKey.ParentFingerprint;
                            }
                            result["fingerprint"] = fingerprint.Value.ToString();

                            DerivationStrategyBase strategy = null;
                            KeyPath          keyPath        = null;
                            BitcoinExtPubKey xpub           = null;

                            if (!network.NBitcoinNetwork.Consensus.SupportSegwit && addressType != "legacy")
                            {
                                await websocketHelper.Send("{ \"error\": \"segwit-notsupported\"}", cancellationToken);

                                continue;
                            }

                            if (addressType == "segwit")
                            {
                                keyPath = new KeyPath("84'").Derive(network.CoinType).Derive(accountNumber, true);
                                xpub    = await device.GetXPubAsync(keyPath);

                                strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
                                {
                                    ScriptPubKeyType = ScriptPubKeyType.Segwit
                                });
                            }
                            else if (addressType == "segwitWrapped")
                            {
                                keyPath = new KeyPath("49'").Derive(network.CoinType).Derive(accountNumber, true);
                                xpub    = await device.GetXPubAsync(keyPath);

                                strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
                                {
                                    ScriptPubKeyType = ScriptPubKeyType.SegwitP2SH
                                });
                            }
                            else if (addressType == "legacy")
                            {
                                keyPath = new KeyPath("44'").Derive(network.CoinType).Derive(accountNumber, true);
                                xpub    = await device.GetXPubAsync(keyPath);

                                strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
                                {
                                    ScriptPubKeyType = ScriptPubKeyType.Legacy
                                });
                            }
                            else
                            {
                                await websocketHelper.Send("{ \"error\": \"invalid-addresstype\"}", cancellationToken);

                                continue;
                            }
                            result.Add(new JProperty("strategy", strategy.ToString()));
                            result.Add(new JProperty("accountKey", xpub.ToString()));
                            result.Add(new JProperty("keyPath", keyPath.ToString()));
                            await websocketHelper.Send(result.ToString(), cancellationToken);

                            break;

                        case "ask-passphrase":
                            if (command == "ask-passphrase")
                            {
                                if (deviceEntry == null)
                                {
                                    await websocketHelper.Send("{ \"error\": \"need-device\"}", cancellationToken);

                                    continue;
                                }
                                // The make the trezor T ask for password
                                await device.GetXPubAsync(new KeyPath("44'"), cancellationToken);
                            }
                            goto askdevice;

                        case "ask-device":
askdevice:
                            if (!versionChecked)
                            {
                                var version = await hwi.GetVersionAsync(cancellationToken);

                                if (version.Major < 2)
                                {
                                    await websocketHelper.Send("{ \"error\": \"vault-outdated\"}", cancellationToken);

                                    continue;
                                }
                                versionChecked = true;
                            }
                            password    = null;
                            deviceEntry = null;
                            device      = null;
                            var entries = (await hwi.EnumerateEntriesAsync(cancellationToken)).ToList();
                            deviceEntry = entries.FirstOrDefault();
                            if (deviceEntry == null)
                            {
                                await websocketHelper.Send("{ \"error\": \"no-device\"}", cancellationToken);

                                continue;
                            }
                            device      = new HwiDeviceClient(hwi, deviceEntry.DeviceSelector, deviceEntry.Model, deviceEntry.Fingerprint);
                            fingerprint = device.Fingerprint;
                            JObject json = new JObject();
                            json.Add("model", device.Model.ToString());
                            json.Add("fingerprint", device.Fingerprint?.ToString());
                            await websocketHelper.Send(json.ToString(), cancellationToken);

                            break;
                        }
                    }
                }
                catch (FormatException ex)
                {
                    JObject obj = new JObject();
                    obj.Add("error", "invalid-network");
                    obj.Add("details", ex.ToString());
                    try
                    {
                        await websocketHelper.Send(obj.ToString(), cancellationToken);
                    }
                    catch { }
                }
                catch (Exception ex)
                {
                    JObject obj = new JObject();
                    obj.Add("error", "unknown-error");
                    obj.Add("message", ex.Message);
                    obj.Add("details", ex.ToString());
                    try
                    {
                        await websocketHelper.Send(obj.ToString(), cancellationToken);
                    }
                    catch { }
                }
                finally
                {
                    await websocketHelper.DisposeAsync(cancellationToken);
                }
            }
            return(new EmptyResult());
        }
Beispiel #19
0
 // Constructor
 public WalletBase(BitcoinExtPubKey rootKey) : base(rootKey, 1000)
 {
 }
Beispiel #20
0
 public BitcoinWallet(string extPubKeyString, bool isTestNetwork)
 {
     extPubKey = new BitcoinExtPubKey(extPubKeyString);
     network   = isTestNetwork ? Network.TestNet : Network.Main;
 }
Beispiel #21
0
 public abstract Task <PSBT> SignTransactionAsync(PSBT psbt, RootedKeyPath accountKeyPath, BitcoinExtPubKey accountKey, Script changeHint, CancellationToken cancellationToken);
        public async Task <IActionResult> UpdateWallet(WalletSetupViewModel vm)
        {
            var checkResult = IsAvailable(vm.CryptoCode, out var store, out var network);

            if (checkResult != null)
            {
                return(checkResult);
            }

            vm.Network = network;
            DerivationSchemeSettings strategy = null;

            var wallet = _WalletProvider.GetWallet(network);

            if (wallet == null)
            {
                return(NotFound());
            }

            if (vm.WalletFile != null)
            {
                if (!DerivationSchemeSettings.TryParseFromWalletFile(await ReadAllText(vm.WalletFile), network, out strategy))
                {
                    ModelState.AddModelError(nameof(vm.WalletFile), "Wallet file was not in the correct format");
                    return(View(vm.ViewName, vm));
                }
            }
            else if (!string.IsNullOrEmpty(vm.WalletFileContent))
            {
                if (!DerivationSchemeSettings.TryParseFromWalletFile(vm.WalletFileContent, network, out strategy))
                {
                    ModelState.AddModelError(nameof(vm.WalletFileContent), "QR import was not in the correct format");
                    return(View(vm.ViewName, vm));
                }
            }
            else if (!string.IsNullOrEmpty(vm.DerivationScheme))
            {
                try
                {
                    strategy        = ParseDerivationStrategy(vm.DerivationScheme, network);
                    strategy.Source = "ManualDerivationScheme";
                    if (!string.IsNullOrEmpty(vm.AccountKey))
                    {
                        var accountKey      = new BitcoinExtPubKey(vm.AccountKey, network.NBitcoinNetwork);
                        var accountSettings =
                            strategy.AccountKeySettings.FirstOrDefault(a => a.AccountKey == accountKey);
                        if (accountSettings != null)
                        {
                            accountSettings.AccountKeyPath =
                                vm.KeyPath == null ? null : KeyPath.Parse(vm.KeyPath);
                            accountSettings.RootFingerprint = string.IsNullOrEmpty(vm.RootFingerprint)
                                ? (HDFingerprint?)null
                                : new HDFingerprint(
                                NBitcoin.DataEncoders.Encoders.Hex.DecodeData(vm.RootFingerprint));
                        }
                    }
                    vm.DerivationScheme = strategy.AccountDerivation.ToString();
                    ModelState.Remove(nameof(vm.DerivationScheme));
                }
                catch
                {
                    ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid wallet format");
                    return(View(vm.ViewName, vm));
                }
            }
            else if (!string.IsNullOrEmpty(vm.Config))
            {
                if (!DerivationSchemeSettings.TryParseFromJson(UnprotectString(vm.Config), network, out strategy))
                {
                    ModelState.AddModelError(nameof(vm.Config), "Config file was not in the correct format");
                    return(View(vm.ViewName, vm));
                }
            }

            if (strategy is null)
            {
                ModelState.AddModelError(nameof(vm.DerivationScheme), "Please provide your extended public key");
                return(View(vm.ViewName, vm));
            }

            vm.Config = ProtectString(strategy.ToJson());
            ModelState.Remove(nameof(vm.Config));

            PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
            var             storeBlob       = store.GetStoreBlob();

            if (vm.Confirmation)
            {
                try
                {
                    await wallet.TrackAsync(strategy.AccountDerivation);

                    store.SetSupportedPaymentMethod(paymentMethodId, strategy);
                    storeBlob.SetExcluded(paymentMethodId, false);
                    storeBlob.PayJoinEnabled = strategy.IsHotWallet && !(vm.SetupRequest?.PayJoinEnabled is false);
                    store.SetStoreBlob(storeBlob);
                }
                catch
                {
                    ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid derivation scheme");
                    return(View(vm.ViewName, vm));
                }
                await _Repo.UpdateStore(store);

                _EventAggregator.Publish(new WalletChangedEvent {
                    WalletId = new WalletId(vm.StoreId, vm.CryptoCode)
                });

                TempData[WellKnownTempData.SuccessMessage] = $"Wallet settings for {network.CryptoCode} have been updated.";

                // This is success case when derivation scheme is added to the store
                return(RedirectToAction(nameof(WalletSettings), new { storeId = vm.StoreId, cryptoCode = vm.CryptoCode }));
            }
            return(ConfirmAddresses(vm, strategy));
        }
        public async Task <FileContentResult> Sync(
            [ModelBinder(BinderType = typeof(DestinationModelBinder))]
            BitcoinExtPubKey extPubKey,
            [ModelBinder(BinderType = typeof(UInt256ModelBinding))]
            uint256 lastBlockHash = null,
            [ModelBinder(BinderType = typeof(UInt256ModelBinding))]
            uint256 unconfirmedHash = null,
            bool noWait             = false)
        {
            lastBlockHash = lastBlockHash ?? uint256.Zero;
            var actualLastBlockHash = uint256.Zero;

            var waitingTransaction = noWait ? Task.FromResult(false) : WaitingTransaction(extPubKey);

            Runtime.Repository.MarkAsUsed(new KeyInformation(extPubKey));
            UTXOChanges changes                 = null;
            UTXOChanges previousChanges         = null;
            List <TrackedTransaction> cleanList = null;
            var getKeyPath = GetKeyPaths(extPubKey);

            while (true)
            {
                cleanList = new List <TrackedTransaction>();
                HashSet <uint256> conflictedUnconf = new HashSet <uint256>();
                changes = new UTXOChanges();
                List <AnnotatedTransaction> transactions = GetAnnotatedTransactions(extPubKey);

                var unconf = transactions.Where(tx => tx.Height == MempoolHeight);
                var conf   = transactions.Where(tx => tx.Height != MempoolHeight);

                conf   = conf.TopologicalSort(DependsOn(conf.ToList())).ToList();
                unconf = unconf.TopologicalSort(DependsOn(unconf.ToList())).ToList();

                foreach (var item in conf.Concat(unconf))
                {
                    var record = item.Record;
                    if (record.BlockHash == null)
                    {
                        if (                        //A parent conflicted with the current utxo
                            record.Transaction.Inputs.Any(i => conflictedUnconf.Contains(i.PrevOut.Hash))
                            ||
                            //Conflict with the confirmed utxo
                            changes.Confirmed.HasConflict(record.Transaction))
                        {
                            cleanList.Add(record);
                            conflictedUnconf.Add(record.Transaction.GetHash());
                            continue;
                        }
                        if (changes.Unconfirmed.HasConflict(record.Transaction))
                        {
                            Logs.Explorer.LogInformation($"Conflicts in the mempool. {record.Transaction.GetHash()} ignored");
                            continue;
                        }
                        changes.Unconfirmed.LoadChanges(record.Transaction, getKeyPath);
                    }
                    else
                    {
                        if (changes.Confirmed.HasConflict(record.Transaction))
                        {
                            Logs.Explorer.LogError("A conflict among confirmed transaction happened, this should be impossible");
                            throw new InvalidOperationException("The impossible happened");
                        }
                        changes.Unconfirmed.LoadChanges(record.Transaction, getKeyPath);
                        changes.Confirmed.LoadChanges(record.Transaction, getKeyPath);
                        changes.Confirmed.Hash = record.BlockHash;
                        actualLastBlockHash    = record.BlockHash;
                        if (record.BlockHash == lastBlockHash)
                        {
                            previousChanges = changes.Clone();
                        }
                    }
                }

                changes.Unconfirmed      = changes.Unconfirmed.Diff(changes.Confirmed);
                changes.Unconfirmed.Hash = changes.Unconfirmed.GetHash();
                if (changes.Unconfirmed.Hash == unconfirmedHash)
                {
                    changes.Unconfirmed.Clear();
                }
                else
                {
                    changes.Unconfirmed.Reset = true;
                }


                if (actualLastBlockHash == lastBlockHash)
                {
                    changes.Confirmed.Clear();
                }
                else if (previousChanges != null)
                {
                    changes.Confirmed.Reset = false;
                    changes.Confirmed       = changes.Confirmed.Diff(previousChanges.Confirmed);
                }
                else
                {
                    changes.Confirmed.Reset = true;
                    changes.Confirmed.SpentOutpoints.Clear();
                }

                if (changes.HasChanges || !(await waitingTransaction))
                {
                    break;
                }
                waitingTransaction = Task.FromResult(false);                 //next time, will not wait
            }

            Runtime.Repository.CleanTransactions(extPubKey.ExtPubKey, cleanList);

            return(new FileContentResult(changes.ToBytes(), "application/octet-stream"));
        }