Include XPubs in PSBTs for multi sig (#6696)

This commit is contained in:
Nicolas Dorier
2025-04-24 21:36:09 +09:00
committed by GitHub
parent 818d65a4f9
commit dd6c4c771e
7 changed files with 25 additions and 19 deletions

View File

@@ -1,5 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -19,7 +18,7 @@ namespace BTCPayServer.Blazor.VaultBridge;
public abstract class HWIController : VaultController public abstract class HWIController : VaultController
{ {
override protected string VaultUri => "http://127.0.0.1:65092/hwi-bridge/v1"; protected override string VaultUri => "http://127.0.0.1:65092/hwi-bridge/v1";
public string CryptoCode { get; set; } public string CryptoCode { get; set; }
private static bool IsTrezorT(HwiEnumerateEntry deviceEntry) private static bool IsTrezorT(HwiEnumerateEntry deviceEntry)
@@ -41,7 +40,7 @@ public abstract class HWIController : VaultController
try try
{ {
var network = networkProviders.GetNetwork<BTCPayNetwork>(CryptoCode); var network = networkProviders.GetNetwork<BTCPayNetwork>(CryptoCode);
var hwi = new Hwi.HwiClient(network.NBitcoinNetwork) var hwi = new HwiClient(network.NBitcoinNetwork)
{ {
Transport = new VaultHWITransport(vaultClient), IgnoreInvalidNetwork = network.NBitcoinNetwork.ChainName != ChainName.Mainnet Transport = new VaultHWITransport(vaultClient), IgnoreInvalidNetwork = network.NBitcoinNetwork.ChainName != ChainName.Mainnet
}; };
@@ -206,7 +205,6 @@ public class SignHWIController : HWIController
ui.ShowRetry(); ui.ShowRetry();
return; return;
} }
derivationSettings.RebaseKeyPaths(psbt); derivationSettings.RebaseKeyPaths(psbt);
// otherwise, let the device check if it can sign anything // otherwise, let the device check if it can sign anything
var signableInputs = psbt.Inputs var signableInputs = psbt.Inputs
@@ -227,7 +225,7 @@ public class SignHWIController : HWIController
if (derivationSettings.IsMultiSigOnServer) if (derivationSettings.IsMultiSigOnServer)
{ {
var alreadySigned = psbt.Inputs.Any(a => var alreadySigned = psbt.Inputs.Any(a =>
a.PartialSigs.Any(a => a.Key == actualPubKey)); a.PartialSigs.Any(o => o.Key == actualPubKey));
if (alreadySigned) if (alreadySigned)
{ {
ui.ShowFeedback(FeedbackType.Failed, ui.StringLocalizer["This device already signed PSBT."]); ui.ShowFeedback(FeedbackType.Failed, ui.StringLocalizer["This device already signed PSBT."]);

View File

@@ -540,7 +540,7 @@ namespace BTCPayServer.Controllers.Greenfield
{ {
SelectedInputs = request.SelectedInputs?.Select(point => point.ToString()), SelectedInputs = request.SelectedInputs?.Select(point => point.ToString()),
Outputs = outputs, Outputs = outputs,
AlwaysIncludeNonWitnessUTXO = true, AlwaysIncludeNonWitnessUTXO = derivationScheme.DefaultIncludeNonWitnessUtxo,
InputSelection = request.SelectedInputs?.Any() is true, InputSelection = request.SelectedInputs?.Any() is true,
FeeSatoshiPerByte = request.FeeRate?.SatoshiPerByte, FeeSatoshiPerByte = request.FeeRate?.SatoshiPerByte,
NoChange = request.NoChange NoChange = request.NoChange

View File

@@ -27,7 +27,12 @@ namespace BTCPayServer.Controllers
public async Task<CreatePSBTResponse> CreatePSBT(BTCPayNetwork network, DerivationSchemeSettings derivationSettings, WalletSendModel sendModel, CancellationToken cancellationToken) public async Task<CreatePSBTResponse> CreatePSBT(BTCPayNetwork network, DerivationSchemeSettings derivationSettings, WalletSendModel sendModel, CancellationToken cancellationToken)
{ {
var nbx = ExplorerClientProvider.GetExplorerClient(network); var nbx = ExplorerClientProvider.GetExplorerClient(network);
CreatePSBTRequest psbtRequest = new(); var psbtRequest = new CreatePSBTRequest()
{
RBF = network.SupportRBF ? true : null,
AlwaysIncludeNonWitnessUTXO = sendModel.AlwaysIncludeNonWitnessUTXO,
IncludeGlobalXPub = derivationSettings.IsMultiSigOnServer,
};
if (sendModel.InputSelection) if (sendModel.InputSelection)
{ {
psbtRequest.IncludeOnlyOutpoints = sendModel.SelectedInputs?.Select(OutPoint.Parse).ToList() ?? new List<OutPoint>(); psbtRequest.IncludeOnlyOutpoints = sendModel.SelectedInputs?.Select(OutPoint.Parse).ToList() ?? new List<OutPoint>();
@@ -40,8 +45,6 @@ namespace BTCPayServer.Controllers
psbtDestination.Amount = Money.Coins(transactionOutput.Amount ?? 0.0m); psbtDestination.Amount = Money.Coins(transactionOutput.Amount ?? 0.0m);
psbtDestination.SubstractFees = transactionOutput.SubtractFeesFromOutput; psbtDestination.SubstractFees = transactionOutput.SubtractFeesFromOutput;
} }
psbtRequest.RBF = network.SupportRBF ? true : null;
psbtRequest.AlwaysIncludeNonWitnessUTXO = sendModel.AlwaysIncludeNonWitnessUTXO;
psbtRequest.FeePreference = new FeePreference(); psbtRequest.FeePreference = new FeePreference();
if (sendModel.FeeSatoshiPerByte is decimal v and > decimal.Zero) if (sendModel.FeeSatoshiPerByte is decimal v and > decimal.Zero)
@@ -56,8 +59,7 @@ namespace BTCPayServer.Controllers
var psbt = (await nbx.CreatePSBTAsync(derivationSettings.AccountDerivation, psbtRequest, cancellationToken)); var psbt = (await nbx.CreatePSBTAsync(derivationSettings.AccountDerivation, psbtRequest, cancellationToken));
if (psbt == null) if (psbt == null)
throw new NotSupportedException(StringLocalizer["You need to update your version of NBXplorer"]); throw new NotSupportedException(StringLocalizer["You need to update your version of NBXplorer"]);
// Not supported by coldcard, remove when they do support it
psbt.PSBT.GlobalXPubs.Clear();
return psbt; return psbt;
} }

View File

@@ -316,6 +316,7 @@ namespace BTCPayServer.Controllers
{ {
RBF = true, RBF = true,
AlwaysIncludeNonWitnessUTXO = paymentMethod.DefaultIncludeNonWitnessUtxo, AlwaysIncludeNonWitnessUTXO = paymentMethod.DefaultIncludeNonWitnessUtxo,
IncludeGlobalXPub = paymentMethod.IsMultiSigOnServer,
IncludeOnlyOutpoints = bumpableUTXOs, IncludeOnlyOutpoints = bumpableUTXOs,
SpendAllMatchingOutpoints = true, SpendAllMatchingOutpoints = true,
FeePreference = new FeePreference() FeePreference = new FeePreference()
@@ -369,6 +370,7 @@ namespace BTCPayServer.Controllers
{ {
RBF = true, RBF = true,
AlwaysIncludeNonWitnessUTXO = paymentMethod.DefaultIncludeNonWitnessUtxo, AlwaysIncludeNonWitnessUTXO = paymentMethod.DefaultIncludeNonWitnessUtxo,
IncludeGlobalXPub = paymentMethod.IsMultiSigOnServer,
IncludeOnlyOutpoints = tx.Transaction.Inputs.Select(i => i.PrevOut).ToList(), IncludeOnlyOutpoints = tx.Transaction.Inputs.Select(i => i.PrevOut).ToList(),
SpendAllMatchingOutpoints = true, SpendAllMatchingOutpoints = true,
DisableFingerprintRandomization = true, DisableFingerprintRandomization = true,
@@ -1329,6 +1331,7 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> WalletSendVault([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, public async Task<IActionResult> WalletSendVault([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId,
WalletSendVaultModel model) WalletSendVaultModel model)
{ {
TempData.SetStatusSuccess(StringLocalizer["Transaction successfully signed"].Value);
return await RedirectToWalletPSBTReady(walletId, new WalletPSBTReadyViewModel return await RedirectToWalletPSBTReady(walletId, new WalletPSBTReadyViewModel
{ {
SigningContext = model.SigningContext, SigningContext = model.SigningContext,

View File

@@ -554,7 +554,8 @@ namespace BTCPayServer
{ {
PSBT = psbt, PSBT = psbt,
DerivationScheme = derivationSchemeSettings.AccountDerivation, DerivationScheme = derivationSchemeSettings.AccountDerivation,
AlwaysIncludeNonWitnessUTXO = true AlwaysIncludeNonWitnessUTXO = true,
IncludeGlobalXPub = derivationSchemeSettings.IsMultiSigOnServer
}); });
if (result == null) if (result == null)
return null; return null;

View File

@@ -1610,6 +1610,7 @@ namespace BTCPayServer.Services
"Transaction fee rate:": "", "Transaction fee rate:": "",
"Transaction Id": "", "Transaction Id": "",
"Transaction signed successfully, proceeding to review...": "", "Transaction signed successfully, proceeding to review...": "",
"Transaction successfully signed": "",
"transactions": "", "transactions": "",
"Translations": "", "Translations": "",
"Translations are formatted as JSON; for example, <b>{0}</b> translates <b>{1}</b> to <b>{2}</b>.": "", "Translations are formatted as JSON; for example, <b>{0}</b> translates <b>{1}</b> to <b>{2}</b>.": "",

View File

@@ -1,4 +1,5 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HWI/@EntryIndexedValue">HWI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LNURL/@EntryIndexedValue">LNURL</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LNURL/@EntryIndexedValue">LNURL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NB/@EntryIndexedValue">NBX</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NB/@EntryIndexedValue">NBX</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NBXplorer/@EntryIndexedValue">NBXplorer</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=NBXplorer/@EntryIndexedValue">NBXplorer</s:String>