Пример #1
0
        private async Task <PSBT> SignTxImplAsync(HardwareWalletModels?deviceType, string devicePath, HDFingerprint?fingerprint, PSBT psbt, CancellationToken cancel)
        {
            var psbtString = psbt.ToBase64();

            var response = await SendCommandAsync(
                options : BuildOptions(deviceType, devicePath, fingerprint),
                command : HwiCommands.SignTx,
                commandArguments : psbtString,
                openConsole : false,
                cancel).ConfigureAwait(false);

            PSBT signedPsbt = HwiParser.ParsePsbt(response, Network);

            if (!signedPsbt.IsAllFinalized())
            {
                signedPsbt.Finalize();
            }

            return(signedPsbt);
        }
Пример #2
0
        public async Task <IActionResult> WalletPSBTReady(
            [ModelBinder(typeof(WalletIdModelBinder))]
            WalletId walletId, WalletPSBTViewModel vm, string command, CancellationToken cancellationToken = default)
        {
            var  network = NetworkProvider.GetNetwork <BTCPayNetwork>(walletId.CryptoCode);
            PSBT psbt    = await vm.GetPSBT(network.NBitcoinNetwork);

            if (vm.InvalidPSBT || psbt is null)
            {
                if (vm.InvalidPSBT)
                {
                    vm.GlobalError = "Invalid PSBT";
                }
                return(View(nameof(WalletPSBT), vm));
            }
            DerivationSchemeSettings derivationSchemeSettings = GetDerivationSchemeSettings(walletId);

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

            await FetchTransactionDetails(derivationSchemeSettings, vm, network);

            switch (command)
            {
            case "payjoin":
                string error;
                try
                {
                    var proposedPayjoin = await GetPayjoinProposedTX(new BitcoinUrlBuilder(vm.SigningContext.PayJoinBIP21, network.NBitcoinNetwork), psbt,
                                                                     derivationSchemeSettings, network, cancellationToken);

                    try
                    {
                        proposedPayjoin.Settings.SigningOptions = new SigningOptions
                        {
                            EnforceLowR = !(vm.SigningContext?.EnforceLowR is false)
                        };
                        var extKey = ExtKey.Parse(vm.SigningKey, network.NBitcoinNetwork);
                        proposedPayjoin = proposedPayjoin.SignAll(derivationSchemeSettings.AccountDerivation,
                                                                  extKey,
                                                                  RootedKeyPath.Parse(vm.SigningKeyPath));
                        vm.SigningContext.PSBT         = proposedPayjoin.ToBase64();
                        vm.SigningContext.OriginalPSBT = psbt.ToBase64();
                        proposedPayjoin.Finalize();
                        var hash = proposedPayjoin.ExtractTransaction().GetHash();
                        _EventAggregator.Publish(new UpdateTransactionLabel(walletId, hash, UpdateTransactionLabel.PayjoinLabelTemplate()));
                        TempData.SetStatusMessageModel(new StatusMessageModel
                        {
                            Severity     = StatusMessageModel.StatusSeverity.Success,
                            AllowDismiss = false,
                            Html         = $"The payjoin transaction has been successfully broadcasted ({proposedPayjoin.ExtractTransaction().GetHash()})"
                        });
                        return(await WalletPSBTReady(walletId, vm, "broadcast"));
                    }
                    catch (Exception)
                    {
                        TempData.SetStatusMessageModel(new StatusMessageModel()
                        {
                            Severity     = StatusMessageModel.StatusSeverity.Warning,
                            AllowDismiss = false,
                            Html         =
                                "This transaction has been coordinated between the receiver and you to create a <a href='https://en.bitcoin.it/wiki/PayJoin' target='_blank'>payjoin transaction</a> by adding inputs from the receiver.<br/>" +
                                "The amount being sent may appear higher but is in fact almost same.<br/><br/>" +
                                "If you cancel or refuse to sign this transaction, the payment will proceed without payjoin"
                        });
                        vm.SigningContext.PSBT         = proposedPayjoin.ToBase64();
                        vm.SigningContext.OriginalPSBT = psbt.ToBase64();
                        return(ViewVault(walletId, vm.SigningContext));
                    }
                }
                catch (PayjoinReceiverException ex)
                {
                    error = $"The payjoin receiver could not complete the payjoin: {ex.Message}";
                }
                catch (PayjoinSenderException ex)
                {
                    error = $"We rejected the receiver's payjoin proposal: {ex.Message}";
                }
                catch (Exception ex)
                {
                    error = $"Unexpected payjoin error: {ex.Message}";
                }

                //we possibly exposed the tx to the receiver, so we need to broadcast straight away
                psbt.Finalize();
                TempData.SetStatusMessageModel(new StatusMessageModel
                {
                    Severity     = StatusMessageModel.StatusSeverity.Warning,
                    AllowDismiss = false,
                    Html         = $"The payjoin transaction could not be created.<br/>" +
                                   $"The original transaction was broadcasted instead. ({psbt.ExtractTransaction().GetHash()})<br/><br/>" +
                                   $"{error}"
                });
                return(await WalletPSBTReady(walletId, vm, "broadcast"));

            case "broadcast" when !psbt.IsAllFinalized() && !psbt.TryFinalize(out var errors):
                vm.SetErrors(errors);
                return(View(nameof(WalletPSBT), vm));

            case "broadcast":
            {
                var transaction = psbt.ExtractTransaction();
                try
                {
                    var broadcastResult = await ExplorerClientProvider.GetExplorerClient(network).BroadcastAsync(transaction);

                    if (!broadcastResult.Success)
                    {
                        if (!string.IsNullOrEmpty(vm.SigningContext.OriginalPSBT))
                        {
                            TempData.SetStatusMessageModel(new StatusMessageModel
                                {
                                    Severity     = StatusMessageModel.StatusSeverity.Warning,
                                    AllowDismiss = false,
                                    Html         = $"The payjoin transaction could not be broadcasted.<br/>({broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}).<br/>The transaction has been reverted back to its original format and has been broadcast."
                                });
                            vm.SigningContext.PSBT         = vm.SigningContext.OriginalPSBT;
                            vm.SigningContext.OriginalPSBT = null;
                            return(await WalletPSBTReady(walletId, vm, "broadcast"));
                        }

                        vm.GlobalError = $"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}";
                        return(View(nameof(WalletPSBT), vm));
                    }
                    else
                    {
                        var wallet             = _walletProvider.GetWallet(network);
                        var derivationSettings = GetDerivationSchemeSettings(walletId);
                        wallet.InvalidateCache(derivationSettings.AccountDerivation);
                    }
                }
                catch (Exception ex)
                {
                    vm.GlobalError = "Error while broadcasting: " + ex.Message;
                    return(View(nameof(WalletPSBT), vm));
                }

                if (!TempData.HasStatusMessage())
                {
                    TempData[WellKnownTempData.SuccessMessage] = $"Transaction broadcasted successfully ({transaction.GetHash()})";
                }
                var returnUrl = this.HttpContext.Request.Query["returnUrl"].FirstOrDefault();
                if (returnUrl is not null)
                {
                    return(Redirect(returnUrl));
                }
                return(RedirectToAction(nameof(WalletTransactions), new { walletId = walletId.ToString() }));
            }

            case "analyze-psbt":
                return(RedirectToWalletPSBT(new WalletPSBTViewModel()
                {
                    PSBT = psbt.ToBase64()
                }));

            case "decode":
                await FetchTransactionDetails(derivationSchemeSettings, vm, network);

                return(View("WalletPSBTDecoded", vm));

            default:
                vm.GlobalError = "Unknown command";
                return(View(nameof(WalletPSBT), vm));
            }
        }
        public TransactionBroadcasterViewModel() : base("Transaction Broadcaster")
        {
            Global = Locator.Current.GetService <Global>();

            ButtonText = "Broadcast Transaction";

            PasteCommand = ReactiveCommand.CreateFromTask(async() =>
            {
                if (!string.IsNullOrEmpty(TransactionString))
                {
                    return;
                }

                var textToPaste   = await Application.Current.Clipboard.GetTextAsync();
                TransactionString = textToPaste;
            });

            IObservable <bool> broadcastTransactionCanExecute = this
                                                                .WhenAny(x => x.TransactionString, (transactionString) => !string.IsNullOrWhiteSpace(transactionString.Value))
                                                                .ObserveOn(RxApp.MainThreadScheduler);

            BroadcastTransactionCommand = ReactiveCommand.CreateFromTask(
                async() => await OnDoTransactionBroadcastAsync(),
                broadcastTransactionCanExecute);

            ImportTransactionCommand = ReactiveCommand.CreateFromTask(
                async() =>
            {
                try
                {
                    var ofd = new OpenFileDialog
                    {
                        AllowMultiple = false,
                        Title         = "Import Transaction"
                    };

                    if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
                    {
                        var initialDirectory = Path.Combine("/media", Environment.UserName);
                        if (!Directory.Exists(initialDirectory))
                        {
                            initialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
                        }
                        ofd.Directory = initialDirectory;
                    }
                    else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
                    {
                        ofd.Directory = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
                    }

                    var window   = (Application.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).MainWindow;
                    var selected = await ofd.ShowAsync(window, fallBack: true);

                    if (selected != null && selected.Any())
                    {
                        var path                = selected.First();
                        var psbtBytes           = await File.ReadAllBytesAsync(path);
                        PSBT psbt               = null;
                        Transaction transaction = null;
                        try
                        {
                            psbt = PSBT.Load(psbtBytes, Global.Network);
                        }
                        catch
                        {
                            var text = await File.ReadAllTextAsync(path);
                            text     = text.Trim();
                            try
                            {
                                psbt = PSBT.Parse(text, Global.Network);
                            }
                            catch
                            {
                                transaction = Transaction.Parse(text, Global.Network);
                            }
                        }

                        if (psbt != null)
                        {
                            if (!psbt.IsAllFinalized())
                            {
                                psbt.Finalize();
                            }

                            TransactionString = psbt.ToBase64();
                        }
                        else
                        {
                            TransactionString = transaction.ToHex();
                        }
                    }
                }
                catch (Exception ex)
                {
                    Logger.LogError(ex);
                    NotificationHelpers.Error(ex.ToUserFriendlyString());
                }
            },
                outputScheduler: RxApp.MainThreadScheduler);

            Observable
            .Merge(PasteCommand.ThrownExceptions)
            .Merge(BroadcastTransactionCommand.ThrownExceptions)
            .Merge(ImportTransactionCommand.ThrownExceptions)
            .ObserveOn(RxApp.TaskpoolScheduler)
            .Subscribe(ex =>
            {
                NotificationHelpers.Error(ex.ToUserFriendlyString());
                Logger.LogError(ex);
            });
        }
Пример #4
0
        public async Task <IActionResult> WalletPSBTReady(
            [ModelBinder(typeof(WalletIdModelBinder))]
            WalletId walletId, WalletPSBTReadyViewModel vm, string command = null, CancellationToken cancellationToken = default)
        {
            if (command == null)
            {
                return(await WalletPSBTReady(walletId, vm.PSBT, vm.SigningKey, vm.SigningKeyPath, vm.OriginalPSBT, vm.PayJoinEndpointUrl));
            }
            PSBT psbt    = null;
            var  network = NetworkProvider.GetNetwork <BTCPayNetwork>(walletId.CryptoCode);
            DerivationSchemeSettings derivationSchemeSettings = null;

            try
            {
                psbt = PSBT.Parse(vm.PSBT, network.NBitcoinNetwork);
                derivationSchemeSettings = GetDerivationSchemeSettings(walletId);
                if (derivationSchemeSettings == null)
                {
                    return(NotFound());
                }
                await FetchTransactionDetails(derivationSchemeSettings, vm, network);
            }
            catch
            {
                vm.GlobalError = "Invalid PSBT";
                return(View(nameof(WalletPSBTReady), vm));
            }

            switch (command)
            {
            case "payjoin":
                string error = null;
                try
                {
                    var proposedPayjoin = await GetPayjoinProposedTX(vm.PayJoinEndpointUrl, psbt,
                                                                     derivationSchemeSettings, network, cancellationToken);

                    try
                    {
                        var extKey = ExtKey.Parse(vm.SigningKey, network.NBitcoinNetwork);
                        proposedPayjoin = proposedPayjoin.SignAll(derivationSchemeSettings.AccountDerivation,
                                                                  extKey,
                                                                  RootedKeyPath.Parse(vm.SigningKeyPath));
                        vm.PSBT         = proposedPayjoin.ToBase64();
                        vm.OriginalPSBT = psbt.ToBase64();
                        proposedPayjoin.Finalize();
                        TempData.SetStatusMessageModel(new StatusMessageModel()
                        {
                            Severity     = StatusMessageModel.StatusSeverity.Success,
                            AllowDismiss = false,
                            Html         = $"The payjoin transaction has been successfully broadcasted ({proposedPayjoin.ExtractTransaction().GetHash()})"
                        });
                        return(await WalletPSBTReady(walletId, vm, "broadcast"));
                    }
                    catch (Exception)
                    {
                        TempData.SetStatusMessageModel(new StatusMessageModel()
                        {
                            Severity     = StatusMessageModel.StatusSeverity.Warning,
                            AllowDismiss = false,
                            Html         =
                                $"This transaction has been coordinated between the receiver and you to create a <a href='https://en.bitcoin.it/wiki/PayJoin' target='_blank'>payjoin transaction</a> by adding inputs from the receiver.<br/>" +
                                $"The amount being sent may appear higher but is in fact almost same.<br/><br/>" +
                                $"If you cancel refuse to sign this transaction, the payment will proceed without payjoin"
                        });
                        return(ViewVault(walletId, proposedPayjoin, vm.PayJoinEndpointUrl, psbt));
                    }
                }
                catch (PayjoinReceiverException ex)
                {
                    error = $"The payjoin receiver could not complete the payjoin: {ex.Message}";
                }
                catch (PayjoinSenderException ex)
                {
                    error = $"We rejected the receiver's payjoin proposal: {ex.Message}";
                }
                catch (Exception ex)
                {
                    error = $"Unexpected payjoin error: {ex.Message}";
                }

                //we possibly exposed the tx to the receiver, so we need to broadcast straight away
                psbt.Finalize();
                TempData.SetStatusMessageModel(new StatusMessageModel()
                {
                    Severity     = StatusMessageModel.StatusSeverity.Warning,
                    AllowDismiss = false,
                    Html         = $"The payjoin transaction could not be created.<br/>" +
                                   $"The original transaction was broadcasted instead. ({psbt.ExtractTransaction().GetHash()})<br/><br/>" +
                                   $"{error}"
                });
                return(await WalletPSBTReady(walletId, vm, "broadcast"));

            case "broadcast" when !psbt.IsAllFinalized() && !psbt.TryFinalize(out var errors):
                vm.SetErrors(errors);
                return(View(nameof(WalletPSBTReady), vm));

            case "broadcast":
            {
                var transaction = psbt.ExtractTransaction();
                try
                {
                    var broadcastResult = await ExplorerClientProvider.GetExplorerClient(network).BroadcastAsync(transaction);

                    if (!broadcastResult.Success)
                    {
                        if (!string.IsNullOrEmpty(vm.OriginalPSBT))
                        {
                            TempData.SetStatusMessageModel(new StatusMessageModel()
                                {
                                    Severity     = StatusMessageModel.StatusSeverity.Warning,
                                    AllowDismiss = false,
                                    Html         = $"The payjoin transaction could not be broadcasted.<br/>({broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}).<br/>The transaction has been reverted back to its original format and has been broadcast."
                                });
                            vm.PSBT         = vm.OriginalPSBT;
                            vm.OriginalPSBT = null;
                            return(await WalletPSBTReady(walletId, vm, "broadcast"));
                        }

                        vm.GlobalError = $"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}";
                        return(View(nameof(WalletPSBTReady), vm));
                    }
                }
                catch (Exception ex)
                {
                    vm.GlobalError = "Error while broadcasting: " + ex.Message;
                    return(View(nameof(WalletPSBTReady), vm));
                }

                if (!TempData.HasStatusMessage())
                {
                    TempData[WellKnownTempData.SuccessMessage] = $"Transaction broadcasted successfully ({transaction.GetHash()})";
                }
                return(RedirectToWalletTransaction(walletId, transaction));
            }

            case "analyze-psbt":
                return(RedirectToWalletPSBT(psbt));

            default:
                vm.GlobalError = "Unknown command";
                return(View(nameof(WalletPSBTReady), vm));
            }
        }