mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-03 17:36:59 +01:00
Taproot support for wallets (#2830)
* Support taproot for HotWallet * Support taproot for hardware wallets * Fix NBX version * Undo formatting * Do not show Taproot when not supported * Create taproot wallet from xpub * Bug Fix
This commit is contained in:
parent
203db44b4e
commit
e5699f674b
11 changed files with 70 additions and 12 deletions
|
@ -84,7 +84,7 @@ services:
|
||||||
- customer_lnd
|
- customer_lnd
|
||||||
- merchant_lnd
|
- merchant_lnd
|
||||||
nbxplorer:
|
nbxplorer:
|
||||||
image: nicolasdorier/nbxplorer:2.2.0
|
image: nicolasdorier/nbxplorer:2.2.1
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "32838:32838"
|
- "32838:32838"
|
||||||
|
|
|
@ -81,7 +81,7 @@ services:
|
||||||
- customer_lnd
|
- customer_lnd
|
||||||
- merchant_lnd
|
- merchant_lnd
|
||||||
nbxplorer:
|
nbxplorer:
|
||||||
image: nicolasdorier/nbxplorer:2.2.0
|
image: nicolasdorier/nbxplorer:2.2.1
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "32838:32838"
|
- "32838:32838"
|
||||||
|
|
|
@ -55,6 +55,7 @@ namespace BTCPayServer.Controllers
|
||||||
vm.RootKeyPath = network.GetRootKeyPath();
|
vm.RootKeyPath = network.GetRootKeyPath();
|
||||||
vm.CanUseHotWallet = hotWallet;
|
vm.CanUseHotWallet = hotWallet;
|
||||||
vm.CanUseRPCImport = rpcImport;
|
vm.CanUseRPCImport = rpcImport;
|
||||||
|
vm.CanUseTaproot = TaprootSupported(vm.CryptoCode);
|
||||||
|
|
||||||
if (vm.Method == null)
|
if (vm.Method == null)
|
||||||
{
|
{
|
||||||
|
@ -109,6 +110,11 @@ namespace BTCPayServer.Controllers
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
strategy = ParseDerivationStrategy(vm.DerivationScheme, network);
|
strategy = ParseDerivationStrategy(vm.DerivationScheme, network);
|
||||||
|
if(strategy.AccountDerivation is TaprootDerivationStrategy && !TaprootSupported(vm.CryptoCode))
|
||||||
|
{
|
||||||
|
ModelState.AddModelError(nameof(vm.DerivationScheme), "Taproot is not supported");
|
||||||
|
return View(vm.ViewName, vm);
|
||||||
|
}
|
||||||
strategy.Source = "ManualDerivationScheme";
|
strategy.Source = "ManualDerivationScheme";
|
||||||
if (!string.IsNullOrEmpty(vm.AccountKey))
|
if (!string.IsNullOrEmpty(vm.AccountKey))
|
||||||
{
|
{
|
||||||
|
@ -208,6 +214,7 @@ namespace BTCPayServer.Controllers
|
||||||
|
|
||||||
vm.CanUseHotWallet = hotWallet;
|
vm.CanUseHotWallet = hotWallet;
|
||||||
vm.CanUseRPCImport = rpcImport;
|
vm.CanUseRPCImport = rpcImport;
|
||||||
|
vm.CanUseTaproot = TaprootSupported(vm.CryptoCode);
|
||||||
vm.RootKeyPath = network.GetRootKeyPath();
|
vm.RootKeyPath = network.GetRootKeyPath();
|
||||||
vm.Network = network;
|
vm.Network = network;
|
||||||
|
|
||||||
|
@ -262,6 +269,12 @@ namespace BTCPayServer.Controllers
|
||||||
CanUseRPCImport = rpcImport
|
CanUseRPCImport = rpcImport
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (request.ScriptPubKeyType == ScriptPubKeyType.TaprootBIP86 && !TaprootSupported(cryptoCode) )
|
||||||
|
{
|
||||||
|
ModelState.AddModelError(nameof(request.ScriptPubKeyType), $"Taproot not supported");
|
||||||
|
return View(vm.ViewName, vm);
|
||||||
|
}
|
||||||
|
|
||||||
if (isImport && string.IsNullOrEmpty(request.ExistingMnemonic))
|
if (isImport && string.IsNullOrEmpty(request.ExistingMnemonic))
|
||||||
{
|
{
|
||||||
ModelState.AddModelError(nameof(request.ExistingMnemonic), "Please provide your existing seed");
|
ModelState.AddModelError(nameof(request.ExistingMnemonic), "Please provide your existing seed");
|
||||||
|
|
|
@ -68,7 +68,8 @@ namespace BTCPayServer.Controllers
|
||||||
EventAggregator eventAggregator,
|
EventAggregator eventAggregator,
|
||||||
AppService appService,
|
AppService appService,
|
||||||
WebhookNotificationManager webhookNotificationManager,
|
WebhookNotificationManager webhookNotificationManager,
|
||||||
IDataProtectionProvider dataProtector)
|
IDataProtectionProvider dataProtector,
|
||||||
|
NBXplorerDashboard Dashboard)
|
||||||
{
|
{
|
||||||
_RateFactory = rateFactory;
|
_RateFactory = rateFactory;
|
||||||
_Repo = repo;
|
_Repo = repo;
|
||||||
|
@ -89,6 +90,7 @@ namespace BTCPayServer.Controllers
|
||||||
_ServiceProvider = serviceProvider;
|
_ServiceProvider = serviceProvider;
|
||||||
_BtcpayServerOptions = btcpayServerOptions;
|
_BtcpayServerOptions = btcpayServerOptions;
|
||||||
_BTCPayEnv = btcpayEnv;
|
_BTCPayEnv = btcpayEnv;
|
||||||
|
_Dashboard = Dashboard;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly BTCPayServerOptions _BtcpayServerOptions;
|
readonly BTCPayServerOptions _BtcpayServerOptions;
|
||||||
|
@ -107,6 +109,7 @@ namespace BTCPayServer.Controllers
|
||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
private readonly AppService _appService;
|
private readonly AppService _appService;
|
||||||
private readonly EventAggregator _EventAggregator;
|
private readonly EventAggregator _EventAggregator;
|
||||||
|
private readonly NBXplorerDashboard _Dashboard;
|
||||||
|
|
||||||
[TempData]
|
[TempData]
|
||||||
public bool StoreNotConfigured
|
public bool StoreNotConfigured
|
||||||
|
@ -143,6 +146,13 @@ namespace BTCPayServer.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TaprootSupported(string crytoCode)
|
||||||
|
{
|
||||||
|
var networkSupport = ((BTCPayNetwork)_NetworkProvider.GetNetwork(crytoCode))?.NBitcoinNetwork?.Consensus?.SupportTaproot is true;
|
||||||
|
var status = _Dashboard.Get(crytoCode).Status;
|
||||||
|
return networkSupport && !(status.NetworkType == ChainName.Mainnet && status.ChainHeight < 709632);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("{storeId}/users")]
|
[Route("{storeId}/users")]
|
||||||
|
@ -444,7 +454,7 @@ namespace BTCPayServer.Controllers
|
||||||
}
|
}
|
||||||
SetCryptoCurrencies(model, CurrentStore);
|
SetCryptoCurrencies(model, CurrentStore);
|
||||||
model.SetLanguages(_LangService, model.DefaultLang);
|
model.SetLanguages(_LangService, model.DefaultLang);
|
||||||
model.PaymentMethodCriteria??= new List<PaymentMethodCriteriaViewModel>();
|
model.PaymentMethodCriteria ??= new List<PaymentMethodCriteriaViewModel>();
|
||||||
for (var index = 0; index < model.PaymentMethodCriteria.Count; index++)
|
for (var index = 0; index < model.PaymentMethodCriteria.Count; index++)
|
||||||
{
|
{
|
||||||
var methodCriterion = model.PaymentMethodCriteria[index];
|
var methodCriterion = model.PaymentMethodCriteria[index];
|
||||||
|
@ -457,7 +467,7 @@ namespace BTCPayServer.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
{
|
{
|
||||||
return View(model);
|
return View(model);
|
||||||
|
@ -550,7 +560,7 @@ namespace BTCPayServer.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("{storeId}")]
|
[HttpGet("{storeId}")]
|
||||||
public async Task<IActionResult> UpdateStore()
|
public async Task<IActionResult> UpdateStore()
|
||||||
{
|
{
|
||||||
|
@ -575,16 +585,16 @@ namespace BTCPayServer.Controllers
|
||||||
vm.PayJoinEnabled = storeBlob.PayJoinEnabled;
|
vm.PayJoinEnabled = storeBlob.PayJoinEnabled;
|
||||||
vm.HintWallet = storeBlob.Hints.Wallet;
|
vm.HintWallet = storeBlob.Hints.Wallet;
|
||||||
vm.HintLightning = storeBlob.Hints.Lightning;
|
vm.HintLightning = storeBlob.Hints.Lightning;
|
||||||
|
|
||||||
(bool canUseHotWallet, _) = await CanUseHotWallet();
|
(bool canUseHotWallet, _) = await CanUseHotWallet();
|
||||||
vm.CanUsePayJoin = canUseHotWallet && store
|
vm.CanUsePayJoin = canUseHotWallet && store
|
||||||
.GetSupportedPaymentMethods(_NetworkProvider)
|
.GetSupportedPaymentMethods(_NetworkProvider)
|
||||||
.OfType<DerivationSchemeSettings>()
|
.OfType<DerivationSchemeSettings>()
|
||||||
.Any(settings => settings.Network.SupportPayJoin && settings.IsHotWallet);
|
.Any(settings => settings.Network.SupportPayJoin && settings.IsHotWallet);
|
||||||
|
|
||||||
return View(vm);
|
return View(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{storeId}")]
|
[HttpPost("{storeId}")]
|
||||||
public async Task<IActionResult> UpdateStore(StoreViewModel model, string command = null)
|
public async Task<IActionResult> UpdateStore(StoreViewModel model, string command = null)
|
||||||
{
|
{
|
||||||
|
@ -704,7 +714,7 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
// ignored
|
// ignored
|
||||||
}
|
}
|
||||||
|
|
||||||
return new DerivationSchemeSettings(parser.Parse(derivationScheme), network);
|
return new DerivationSchemeSettings(parser.Parse(derivationScheme), network);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -222,7 +222,21 @@ namespace BTCPayServer.Controllers
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (addressType == "segwit")
|
if (!network.NBitcoinNetwork.Consensus.SupportTaproot && addressType == "taproot")
|
||||||
|
{
|
||||||
|
await websocketHelper.Send("{ \"error\": \"taproot-notsupported\"}", cancellationToken);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (addressType == "taproot")
|
||||||
|
{
|
||||||
|
keyPath = new KeyPath("86'").Derive(network.CoinType).Derive(accountNumber, true);
|
||||||
|
xpub = await device.GetXPubAsync(keyPath);
|
||||||
|
strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
|
||||||
|
{
|
||||||
|
ScriptPubKeyType = ScriptPubKeyType.TaprootBIP86
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (addressType == "segwit")
|
||||||
{
|
{
|
||||||
keyPath = new KeyPath("84'").Derive(network.CoinType).Derive(accountNumber, true);
|
keyPath = new KeyPath("84'").Derive(network.CoinType).Derive(accountNumber, true);
|
||||||
xpub = await device.GetXPubAsync(keyPath);
|
xpub = await device.GetXPubAsync(keyPath);
|
||||||
|
@ -337,6 +351,8 @@ askdevice:
|
||||||
private ScriptPubKeyType GetScriptPubKeyType(RootedKeyPath keyPath)
|
private ScriptPubKeyType GetScriptPubKeyType(RootedKeyPath keyPath)
|
||||||
{
|
{
|
||||||
var path = keyPath.KeyPath.ToString();
|
var path = keyPath.KeyPath.ToString();
|
||||||
|
if (path.StartsWith("86'", StringComparison.OrdinalIgnoreCase))
|
||||||
|
return ScriptPubKeyType.TaprootBIP86;
|
||||||
if (path.StartsWith("84'", StringComparison.OrdinalIgnoreCase))
|
if (path.StartsWith("84'", StringComparison.OrdinalIgnoreCase))
|
||||||
return ScriptPubKeyType.Segwit;
|
return ScriptPubKeyType.Segwit;
|
||||||
if (path.StartsWith("49'", StringComparison.OrdinalIgnoreCase))
|
if (path.StartsWith("49'", StringComparison.OrdinalIgnoreCase))
|
||||||
|
|
|
@ -39,7 +39,8 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||||
public bool CanUseHotWallet { get; set; }
|
public bool CanUseHotWallet { get; set; }
|
||||||
[Display(Name = "Can use RPC import")]
|
[Display(Name = "Can use RPC import")]
|
||||||
public bool CanUseRPCImport { get; set; }
|
public bool CanUseRPCImport { get; set; }
|
||||||
|
[Display(Name = "Can use Taproot")]
|
||||||
|
public bool CanUseTaproot { get; set; }
|
||||||
public RootedKeyPath GetAccountKeypath()
|
public RootedKeyPath GetAccountKeypath()
|
||||||
{
|
{
|
||||||
if (KeyPath != null && RootFingerprint != null &&
|
if (KeyPath != null && RootFingerprint != null &&
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
ViewData.SetActivePageAndTitle(StoreNavPages.Wallet, $"Create {Model.CryptoCode} {type} Wallet", Context.GetStoreData().StoreName);
|
ViewData.SetActivePageAndTitle(StoreNavPages.Wallet, $"Create {Model.CryptoCode} {type} Wallet", Context.GetStoreData().StoreName);
|
||||||
ViewData.Add(nameof(Model.CanUseHotWallet), Model.CanUseHotWallet);
|
ViewData.Add(nameof(Model.CanUseHotWallet), Model.CanUseHotWallet);
|
||||||
ViewData.Add(nameof(Model.CanUseRPCImport), Model.CanUseRPCImport);
|
ViewData.Add(nameof(Model.CanUseRPCImport), Model.CanUseRPCImport);
|
||||||
|
ViewData.Add(nameof(Model.CanUseTaproot), Model.CanUseTaproot);
|
||||||
ViewData.Add(nameof(Model.Method), Model.Method);
|
ViewData.Add(nameof(Model.Method), Model.Method);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,6 +31,10 @@
|
||||||
<option value="segwit">Segwit (Recommended, cheapest fee)</option>
|
<option value="segwit">Segwit (Recommended, cheapest fee)</option>
|
||||||
<option value="segwitWrapped">Segwit wrapped (Compatible with old wallets)</option>
|
<option value="segwitWrapped">Segwit wrapped (Compatible with old wallets)</option>
|
||||||
<option value="legacy">Legacy (Not recommended)</option>
|
<option value="legacy">Legacy (Not recommended)</option>
|
||||||
|
@if (ViewData["CanUseTaproot"] is true)
|
||||||
|
{
|
||||||
|
<option value="taproot">Taproot (ONLY FOR DEVELOPMENT)</option>
|
||||||
|
}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
{
|
{
|
||||||
ViewData.Add(nameof(Model.CanUseHotWallet), Model.CanUseHotWallet);
|
ViewData.Add(nameof(Model.CanUseHotWallet), Model.CanUseHotWallet);
|
||||||
ViewData.Add(nameof(Model.CanUseRPCImport), Model.CanUseRPCImport);
|
ViewData.Add(nameof(Model.CanUseRPCImport), Model.CanUseRPCImport);
|
||||||
|
ViewData.Add(nameof(Model.CanUseTaproot), Model.CanUseTaproot);
|
||||||
ViewData.Add(nameof(Model.Method), Model.Method);
|
ViewData.Add(nameof(Model.Method), Model.Method);
|
||||||
|
|
||||||
<partial name="_GenerateWalletForm" model="Model.SetupRequest" />
|
<partial name="_GenerateWalletForm" model="Model.SetupRequest" />
|
||||||
|
|
|
@ -83,6 +83,13 @@
|
||||||
<tr>
|
<tr>
|
||||||
<td class="font-monospace">pkh(xpub…/0/*)</td>
|
<td class="font-monospace">pkh(xpub…/0/*)</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
@if (Model.CanUseTaproot)
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td rowspan="1">P2TR</td>
|
||||||
|
<td class="font-monospace">xpub…-[taproot]</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
<tr class="additional">
|
<tr class="additional">
|
||||||
<td class="text-nowrap" rowspan="2">Multi-sig P2WSH</td>
|
<td class="text-nowrap" rowspan="2">Multi-sig P2WSH</td>
|
||||||
<td class="font-monospace">2-of-xpub1…-xpub2…</td>
|
<td class="font-monospace">2-of-xpub1…-xpub2…</td>
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
var isHotWallet = method is WalletSetupMethod.HotWallet;
|
var isHotWallet = method is WalletSetupMethod.HotWallet;
|
||||||
var canUseHotWallet = ViewData["CanUseHotWallet"] is true;
|
var canUseHotWallet = ViewData["CanUseHotWallet"] is true;
|
||||||
var canUseRpcImport = ViewData["CanUseRPCImport"] is true;
|
var canUseRpcImport = ViewData["CanUseRPCImport"] is true;
|
||||||
|
var canUseTaproot = ViewData["CanUseTaproot"] is true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (!User.IsInRole(Roles.ServerAdmin))
|
@if (!User.IsInRole(Roles.ServerAdmin))
|
||||||
|
@ -36,6 +37,10 @@
|
||||||
<option value="@ScriptPubKeyType.Segwit">Segwit (Recommended, cheapest transaction fee)</option>
|
<option value="@ScriptPubKeyType.Segwit">Segwit (Recommended, cheapest transaction fee)</option>
|
||||||
<option value="@ScriptPubKeyType.SegwitP2SH">Segwit wrapped (Compatible with old wallets)</option>
|
<option value="@ScriptPubKeyType.SegwitP2SH">Segwit wrapped (Compatible with old wallets)</option>
|
||||||
<option value="@ScriptPubKeyType.Legacy">Legacy (Not recommended)</option>
|
<option value="@ScriptPubKeyType.Legacy">Legacy (Not recommended)</option>
|
||||||
|
@if (canUseTaproot)
|
||||||
|
{
|
||||||
|
<option value="@ScriptPubKeyType.TaprootBIP86">Taproot (ONLY FOR DEVELOPMENT)</option>
|
||||||
|
}
|
||||||
</select>
|
</select>
|
||||||
<span asp-validation-for="ScriptPubKeyType" class="text-danger"></span>
|
<span asp-validation-for="ScriptPubKeyType" class="text-danger"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Reference in a new issue