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:
Manan Sharma 2021-09-03 12:07:12 +05:30 committed by GitHub
parent 203db44b4e
commit e5699f674b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 70 additions and 12 deletions

View file

@ -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"

View file

@ -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"

View file

@ -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");

View file

@ -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);
} }

View file

@ -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))

View file

@ -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 &&

View file

@ -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);
} }

View file

@ -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">

View file

@ -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" />

View file

@ -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>

View file

@ -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>