Allow flexible derivation scheme for the store

This commit is contained in:
NicolasDorier
2017-10-05 00:05:38 +09:00
parent 51ef3ec656
commit 8b4e572e16
5 changed files with 139 additions and 47 deletions

View File

@@ -45,9 +45,9 @@ namespace BTCPayServer.Tests
await store.UpdateStore(StoreId, new StoreViewModel() await store.UpdateStore(StoreId, new StoreViewModel()
{ {
ExtPubKey = extKey.Neuter().ToString() + "-[legacy]", DerivationScheme = extKey.Neuter().ToString() + "-[legacy]",
SpeedPolicy = SpeedPolicy.MediumSpeed SpeedPolicy = SpeedPolicy.MediumSpeed
}); }, "Save");
Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString())); Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString()));
await store.Pair(pairingCode.ToString(), StoreId); await store.Pair(pairingCode.ToString(), StoreId);
} }

View File

@@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NBitcoin; using NBitcoin;
using NBitpayClient; using NBitpayClient;
using NBXplorer.DerivationStrategy;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -28,6 +29,7 @@ namespace BTCPayServer.Controllers
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
AccessTokenController tokenController, AccessTokenController tokenController,
BTCPayWallet wallet, BTCPayWallet wallet,
Network network,
IHostingEnvironment env) IHostingEnvironment env)
{ {
_Repo = repo; _Repo = repo;
@@ -36,7 +38,9 @@ namespace BTCPayServer.Controllers
_TokenController = tokenController; _TokenController = tokenController;
_Wallet = wallet; _Wallet = wallet;
_Env = env; _Env = env;
_Network = network;
} }
Network _Network;
BTCPayWallet _Wallet; BTCPayWallet _Wallet;
AccessTokenController _TokenController; AccessTokenController _TokenController;
StoreRepository _Repo; StoreRepository _Repo;
@@ -106,7 +110,7 @@ namespace BTCPayServer.Controllers
vm.StoreName = store.StoreName; vm.StoreName = store.StoreName;
vm.StoreWebsite = store.StoreWebsite; vm.StoreWebsite = store.StoreWebsite;
vm.SpeedPolicy = store.SpeedPolicy; vm.SpeedPolicy = store.SpeedPolicy;
vm.ExtPubKey = store.DerivationStrategy; vm.DerivationScheme = store.DerivationStrategy;
vm.StatusMessage = StatusMessage; vm.StatusMessage = StatusMessage;
return View(vm); return View(vm);
} }
@@ -114,7 +118,7 @@ namespace BTCPayServer.Controllers
[HttpPost] [HttpPost]
[ValidateAntiForgeryToken] [ValidateAntiForgeryToken]
[Route("{storeId}")] [Route("{storeId}")]
public async Task<IActionResult> UpdateStore(string storeId, StoreViewModel model) public async Task<IActionResult> UpdateStore(string storeId, StoreViewModel model, string command)
{ {
if(!ModelState.IsValid) if(!ModelState.IsValid)
{ {
@@ -124,6 +128,8 @@ namespace BTCPayServer.Controllers
if(store == null) if(store == null)
return NotFound(); return NotFound();
if(command == "Save")
{
bool needUpdate = false; bool needUpdate = false;
if(store.SpeedPolicy != model.SpeedPolicy) if(store.SpeedPolicy != model.SpeedPolicy)
{ {
@@ -141,17 +147,17 @@ namespace BTCPayServer.Controllers
store.StoreWebsite = model.StoreWebsite; store.StoreWebsite = model.StoreWebsite;
} }
if(store.DerivationStrategy != model.ExtPubKey) if(store.DerivationStrategy != model.DerivationScheme)
{ {
needUpdate = true; needUpdate = true;
try try
{ {
await _Wallet.TrackAsync(model.ExtPubKey); await _Wallet.TrackAsync(model.DerivationScheme);
store.DerivationStrategy = model.ExtPubKey; store.DerivationStrategy = model.DerivationScheme;
} }
catch catch
{ {
ModelState.AddModelError(nameof(model.ExtPubKey), "Invalid Derivation Scheme"); ModelState.AddModelError(nameof(model.DerivationScheme), "Invalid Derivation Scheme");
return View(model); return View(model);
} }
} }
@@ -167,6 +173,20 @@ namespace BTCPayServer.Controllers
storeId = storeId storeId = storeId
}); });
} }
else
{
var facto = new DerivationStrategyFactory(_Network);
var scheme = facto.Parse(model.DerivationScheme);
var line = scheme.GetLineFor(DerivationFeature.Deposit);
for(int i = 0; i < 10; i++)
{
var address = line.Derive((uint)i);
model.AddressSamples.Add((line.Path.Derive((uint)i).ToString(), address.ScriptPubKey.GetDestinationAddress(_Network).ToString()));
}
return View(model);
}
}
[HttpGet] [HttpGet]
[Route("{storeId}/Tokens")] [Route("{storeId}/Tokens")]

View File

@@ -27,8 +27,8 @@ namespace BTCPayServer.Models.StoreViewModels
set; set;
} }
[ExtPubKeyValidator] [DerivationStrategyValidator]
public string ExtPubKey public string DerivationScheme
{ {
get; set; get; set;
} }
@@ -39,6 +39,11 @@ namespace BTCPayServer.Models.StoreViewModels
get; set; get; set;
} }
public List<(string KeyPath, string Address)> AddressSamples
{
get; set;
} = new List<(string KeyPath, string Address)>();
public string StatusMessage public string StatusMessage
{ {
get; set; get; set;

View File

@@ -1,4 +1,5 @@
using NBitcoin; using NBitcoin;
using NBXplorer.DerivationStrategy;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
@@ -6,7 +7,7 @@ using System.Text;
namespace BTCPayServer.Validations namespace BTCPayServer.Validations
{ {
public class ExtPubKeyValidatorAttribute : ValidationAttribute public class DerivationStrategyValidatorAttribute : ValidationAttribute
{ {
protected override ValidationResult IsValid(object value, ValidationContext validationContext) protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{ {
@@ -19,7 +20,7 @@ namespace BTCPayServer.Validations
return new ValidationResult("No Network specified"); return new ValidationResult("No Network specified");
try try
{ {
new BitcoinExtPubKey((string)value, network); new DerivationStrategyFactory(network).Parse((string)value);
return ValidationResult.Success; return ValidationResult.Success;
} }
catch(Exception ex) catch(Exception ex)

View File

@@ -36,11 +36,77 @@
<span asp-validation-for="SpeedPolicy" class="text-danger"></span> <span asp-validation-for="SpeedPolicy" class="text-danger"></span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label asp-for="ExtPubKey"></label> <h5>Derivation Scheme</h5>
<input asp-for="ExtPubKey" class="form-control" /> @if(Model.AddressSamples.Count == 0)
<span asp-validation-for="ExtPubKey" class="text-danger"></span> {
<span>The DerivationScheme represents the destination of the funds received by your invoice. It is generated by your wallet software. Please, verify that you are generating the right addresses by clicking on 'Check ExtPubKey'</span>
}
</div> </div>
<button type="submit" class="btn btn-default">Save</button> <div class="form-group">
<input asp-for="DerivationScheme" class="form-control" />
<span asp-validation-for="DerivationScheme" class="text-danger"></span>
</div>
<div class="form-group">
@if(Model.AddressSamples.Count == 0)
{
<table class="table">
<thead class="thead-inverse">
<tr>
<th>Address type</th>
<th>Example</th>
</tr>
</thead>
<tbody>
<tr>
<td>P2WPKH</td>
<td>xpub</td>
</tr>
<tr>
<td>P2SH-P2WPKH</td>
<td>xpub-[p2sh]</td>
</tr>
<tr>
<td>P2SH</td>
<td>xpub-[legacy]</td>
</tr>
<tr>
<td>Multi-sig P2WSH</td>
<td>2-of-xpub1-xpub2</td>
</tr>
<tr>
<td>Multi-sig P2SH-P2WSH</td>
<td>2-of-xpub1-xpub2-[p2sh]</td>
</tr>
<tr>
<td>Multi-sig P2SH</td>
<td>2-of-xpub1-xpub2-[legacy]</td>
</tr>
</tbody>
</table>
}
else
{
<table class="table">
<thead class="thead-inverse">
<tr>
<th>Key path</th>
<th>Address</th>
</tr>
</thead>
<tbody>
@foreach(var sample in Model.AddressSamples)
{
<tr>
<td>@sample.KeyPath</td>
<td>@sample.Address</td>
</tr>
}
</tbody>
</table>
}
</div>
<button name="command" type="submit" class="btn btn-success" value="Save">Save</button>
<button name="command" type="submit" class="btn btn-default" value="Check">Check ExtPubKey</button>
</form> </form>
</div> </div>
</div> </div>