mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-03 09:29:10 +01:00
Add Bitcoin Output descriptor support (#2169)
* Add Output descriptor support * Fix exception message and fix Parse Deriv Scheme Settings when electrum * fix test
This commit is contained in:
parent
b8da6847b9
commit
01be74b219
8 changed files with 267 additions and 43 deletions
|
@ -27,7 +27,7 @@
|
|||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NBitcoin" Version="5.0.60" />
|
||||
<PackageReference Include="NBitcoin" Version="5.0.68" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.6.0" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
|
||||
<PackageReference Include="NBitcoin" Version="5.0.60" />
|
||||
<PackageReference Include="NBitcoin" Version="5.0.68" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.6.3" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -44,6 +44,7 @@ using Microsoft.EntityFrameworkCore;
|
|||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitcoin.Payment;
|
||||
using NBitcoin.Scripting.Parser;
|
||||
using NBitpayClient;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
|
@ -1040,6 +1041,99 @@ normal:
|
|||
Assert.Equal(
|
||||
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[legacy]",
|
||||
parsed.ToString());
|
||||
|
||||
//let's test output descriptor parsing support
|
||||
|
||||
|
||||
//we don't support every descriptor, only the ones which represent an HD wallet with stndard derivation paths
|
||||
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)"));
|
||||
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)"));
|
||||
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)"));
|
||||
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))"));
|
||||
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)"));
|
||||
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))"));
|
||||
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)"));
|
||||
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))"));
|
||||
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("sh(sortedmulti(2,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01))"));
|
||||
|
||||
//let's see what we actually support now
|
||||
|
||||
//standard legacy hd wallet
|
||||
var parsedDescriptor = mainnetParser.ParseOutputDescriptor(
|
||||
"pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
|
||||
Assert.Equal(KeyPath.Parse("44'/0'/0'"),Assert.Single(parsedDescriptor.Item2).KeyPath);
|
||||
Assert.Equal( HDFingerprint.Parse("d34db33f"),Assert.Single(parsedDescriptor.Item2).MasterFingerprint);
|
||||
Assert.Equal("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]",parsedDescriptor.Item1.ToString() );
|
||||
|
||||
//masterfingerprint and key path are optional
|
||||
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
|
||||
"pkh([d34db33f]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
|
||||
Assert.Equal("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]",parsedDescriptor.Item1.ToString() );
|
||||
//a master fingerprint must always be present if youre providing rooted path
|
||||
Assert.Throws<ParsingException>(() => mainnetParser.ParseOutputDescriptor("pkh([44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)"));
|
||||
|
||||
|
||||
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
|
||||
"pkh(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
|
||||
Assert.Equal("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]",parsedDescriptor.Item1.ToString() );
|
||||
|
||||
//but a different deriv path from standard (0/*) is not supported
|
||||
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("pkh(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)"));
|
||||
|
||||
//p2sh-segwit hd wallet
|
||||
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
|
||||
"sh(wpkh([d34db33f/49'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))");
|
||||
Assert.Equal(KeyPath.Parse("49'/0'/0'"),Assert.Single(parsedDescriptor.Item2).KeyPath);
|
||||
Assert.Equal( HDFingerprint.Parse("d34db33f"),Assert.Single(parsedDescriptor.Item2).MasterFingerprint);
|
||||
Assert.Equal("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[p2sh]",parsedDescriptor.Item1.ToString() );
|
||||
|
||||
//segwit hd wallet
|
||||
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
|
||||
"wpkh([d34db33f/84'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
|
||||
Assert.Equal(KeyPath.Parse("84'/0'/0'"),Assert.Single(parsedDescriptor.Item2).KeyPath);
|
||||
Assert.Equal( HDFingerprint.Parse("d34db33f"),Assert.Single(parsedDescriptor.Item2).MasterFingerprint);
|
||||
Assert.Equal("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL",parsedDescriptor.Item1.ToString() );
|
||||
|
||||
//multisig tests
|
||||
|
||||
//legacy
|
||||
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
|
||||
"sh(multi(1,[d34db33f/45'/0]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/45'/0]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))");
|
||||
Assert.Equal(2, parsedDescriptor.Item2.Length);
|
||||
var strat = Assert.IsType<MultisigDerivationStrategy>(Assert.IsType<P2SHDerivationStrategy>(parsedDescriptor.Item1).Inner);
|
||||
Assert.True(strat.IsLegacy);
|
||||
Assert.Equal(1,strat.RequiredSignatures);
|
||||
Assert.Equal(2,strat.Keys.Count());
|
||||
Assert.False(strat.LexicographicOrder);
|
||||
Assert.Equal("1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]-[keeporder]",parsedDescriptor.Item1.ToString() );
|
||||
|
||||
//segwit
|
||||
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
|
||||
"wsh(multi(1,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))");
|
||||
Assert.Equal(2, parsedDescriptor.Item2.Length);
|
||||
strat = Assert.IsType<MultisigDerivationStrategy>(Assert.IsType<P2WSHDerivationStrategy>(parsedDescriptor.Item1).Inner);
|
||||
Assert.False(strat.IsLegacy);
|
||||
Assert.Equal(1,strat.RequiredSignatures);
|
||||
Assert.Equal(2,strat.Keys.Count());
|
||||
Assert.False(strat.LexicographicOrder);
|
||||
Assert.Equal("1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[keeporder]",parsedDescriptor.Item1.ToString() );
|
||||
|
||||
|
||||
//segwit-p2sh
|
||||
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
|
||||
"sh(wsh(multi(1,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)))");
|
||||
Assert.Equal(2, parsedDescriptor.Item2.Length);
|
||||
strat = Assert.IsType<MultisigDerivationStrategy>(Assert.IsType<P2WSHDerivationStrategy>(Assert.IsType<P2SHDerivationStrategy>(parsedDescriptor.Item1).Inner).Inner);
|
||||
Assert.False(strat.IsLegacy);
|
||||
Assert.Equal(1,strat.RequiredSignatures);
|
||||
Assert.Equal(2,strat.Keys.Count());
|
||||
Assert.False(strat.LexicographicOrder);
|
||||
Assert.Equal("1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[keeporder]-[p2sh]",parsedDescriptor.Item1.ToString() );
|
||||
|
||||
//sorted
|
||||
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
|
||||
"sh(sortedmulti(1,[d34db33f/48'/0'/0'/1']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/48'/0'/0'/1']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))");
|
||||
Assert.Equal("1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]",parsedDescriptor.Item1.ToString() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -72,7 +73,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/preview")]
|
||||
public ActionResult<OnChainPaymentMethodPreviewResultData> GetOnChainPaymentMethodPreview(
|
||||
public IActionResult GetOnChainPaymentMethodPreview(
|
||||
string cryptoCode,
|
||||
int offset = 0, int amount = 10)
|
||||
{
|
||||
|
@ -86,25 +87,34 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var strategy = DerivationSchemeSettings.Parse(paymentMethod.DerivationScheme, network);
|
||||
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
|
||||
|
||||
var line = strategy.AccountDerivation.GetLineFor(deposit);
|
||||
var result = new OnChainPaymentMethodPreviewResultData();
|
||||
for (var i = offset; i < amount; i++)
|
||||
try
|
||||
{
|
||||
var address = line.Derive((uint)i);
|
||||
result.Addresses.Add(
|
||||
new OnChainPaymentMethodPreviewResultData.OnChainPaymentMethodPreviewResultAddressItem()
|
||||
{
|
||||
KeyPath = deposit.GetKeyPath((uint)i).ToString(),
|
||||
Address = address.ScriptPubKey.GetDestinationAddress(strategy.Network.NBitcoinNetwork)
|
||||
.ToString()
|
||||
});
|
||||
var strategy = DerivationSchemeSettings.Parse(paymentMethod.DerivationScheme, network);
|
||||
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
|
||||
|
||||
var line = strategy.AccountDerivation.GetLineFor(deposit);
|
||||
var result = new OnChainPaymentMethodPreviewResultData();
|
||||
for (var i = offset; i < amount; i++)
|
||||
{
|
||||
var address = line.Derive((uint)i);
|
||||
result.Addresses.Add(
|
||||
new OnChainPaymentMethodPreviewResultData.OnChainPaymentMethodPreviewResultAddressItem()
|
||||
{
|
||||
KeyPath = deposit.GetKeyPath((uint)i).ToString(),
|
||||
Address = address.ScriptPubKey.GetDestinationAddress(strategy.Network.NBitcoinNetwork)
|
||||
.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(OnChainPaymentMethodData.DerivationScheme),
|
||||
"Invalid Derivation Scheme");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
|
||||
|
@ -125,36 +135,37 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
}
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
DerivationSchemeSettings strategy;
|
||||
try
|
||||
{
|
||||
var strategy = DerivationSchemeSettings.Parse(paymentMethodData.DerivationScheme, network);
|
||||
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
|
||||
var line = strategy.AccountDerivation.GetLineFor(deposit);
|
||||
var result = new OnChainPaymentMethodPreviewResultData();
|
||||
for (var i = offset; i < amount; i++)
|
||||
{
|
||||
var derivation = line.Derive((uint)i);
|
||||
result.Addresses.Add(
|
||||
new
|
||||
OnChainPaymentMethodPreviewResultData.
|
||||
OnChainPaymentMethodPreviewResultAddressItem()
|
||||
{
|
||||
KeyPath = deposit.GetKeyPath((uint)i).ToString(),
|
||||
Address = strategy.Network.NBXplorerNetwork.CreateAddress(strategy.AccountDerivation,
|
||||
line.KeyPathTemplate.GetKeyPath((uint)i),
|
||||
derivation.ScriptPubKey).ToString()
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
strategy = DerivationSchemeSettings.Parse(paymentMethodData.DerivationScheme, network);
|
||||
}
|
||||
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(OnChainPaymentMethodData.DerivationScheme),
|
||||
"Invalid Derivation Scheme");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
|
||||
var line = strategy.AccountDerivation.GetLineFor(deposit);
|
||||
var result = new OnChainPaymentMethodPreviewResultData();
|
||||
for (var i = offset; i < amount; i++)
|
||||
{
|
||||
var derivation = line.Derive((uint)i);
|
||||
result.Addresses.Add(
|
||||
new
|
||||
OnChainPaymentMethodPreviewResultData.
|
||||
OnChainPaymentMethodPreviewResultAddressItem()
|
||||
{
|
||||
KeyPath = deposit.GetKeyPath((uint)i).ToString(),
|
||||
Address = strategy.Network.NBXplorerNetwork.CreateAddress(strategy.AccountDerivation,
|
||||
line.KeyPathTemplate.GetKeyPath((uint)i),
|
||||
derivation.ScriptPubKey).ToString()
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
|
|
|
@ -7,7 +7,6 @@ using BTCPayServer.Abstractions.Models;
|
|||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services;
|
||||
|
|
|
@ -36,6 +36,7 @@ using Microsoft.Extensions.Options;
|
|||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBXplorer;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
|
@ -699,6 +700,26 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
var parser = new DerivationSchemeParser(network);
|
||||
parser.HintScriptPubKey = hint;
|
||||
try
|
||||
{
|
||||
var derivationSchemeSettings = new DerivationSchemeSettings();
|
||||
derivationSchemeSettings.Network = network;
|
||||
var result = parser.ParseOutputDescriptor(derivationScheme);
|
||||
derivationSchemeSettings.AccountOriginal = derivationScheme.Trim();
|
||||
derivationSchemeSettings.AccountDerivation = result.Item1;
|
||||
derivationSchemeSettings.AccountKeySettings = result.Item2?.Select((path, i) => new AccountKeySettings()
|
||||
{
|
||||
RootFingerprint = path?.MasterFingerprint,
|
||||
AccountKeyPath = path?.KeyPath,
|
||||
AccountKey = result.Item1.GetExtPubKeys().ElementAt(i).GetWif(parser.Network)
|
||||
}).ToArray() ?? new AccountKeySettings[result.Item1.GetExtPubKeys().Count()];
|
||||
return derivationSchemeSettings;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return new DerivationSchemeSettings(parser.Parse(derivationScheme), network);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Scripting;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
|
||||
namespace BTCPayServer
|
||||
|
@ -21,6 +22,77 @@ namespace BTCPayServer
|
|||
BtcPayNetwork = expectedNetwork;
|
||||
}
|
||||
|
||||
public (DerivationStrategyBase, RootedKeyPath[]) ParseOutputDescriptor(string str)
|
||||
{
|
||||
(DerivationStrategyBase, RootedKeyPath[]) ExtractFromPkProvider(PubKeyProvider pubKeyProvider,
|
||||
string suffix = "")
|
||||
{
|
||||
switch (pubKeyProvider)
|
||||
{
|
||||
case PubKeyProvider.Const _:
|
||||
throw new FormatException("Only HD output descriptors are supported.");
|
||||
case PubKeyProvider.HD hd:
|
||||
if (hd.Path != null && hd.Path.ToString() != "0")
|
||||
{
|
||||
throw new FormatException("Custom change paths are not supported.");
|
||||
}
|
||||
|
||||
return (Parse($"{hd.Extkey}{suffix}"), null);
|
||||
case PubKeyProvider.Origin origin:
|
||||
var innerResult = ExtractFromPkProvider(origin.Inner, suffix);
|
||||
return (innerResult.Item1, new[] {origin.KeyOriginInfo});
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
if (str == null)
|
||||
throw new ArgumentNullException(nameof(str));
|
||||
str = str.Trim();
|
||||
var outputDescriptor = OutputDescriptor.Parse(str);
|
||||
switch(outputDescriptor)
|
||||
{
|
||||
case OutputDescriptor.PK _:
|
||||
case OutputDescriptor.Raw _:
|
||||
case OutputDescriptor.Addr _:
|
||||
throw new FormatException("Only HD output descriptors are supported.");
|
||||
case OutputDescriptor.Combo _:
|
||||
throw new FormatException("Only output descriptors of one format are supported.");
|
||||
case OutputDescriptor.Multi multi:
|
||||
var xpubs = multi.PkProviders.Select(provider => ExtractFromPkProvider(provider));
|
||||
return (
|
||||
Parse(
|
||||
$"{multi.Threshold}-of-{(string.Join('-', xpubs.Select(tuple => tuple.Item1.ToString())))}{(multi.IsSorted?"":"-[keeporder]")}"),
|
||||
xpubs.SelectMany(tuple => tuple.Item2).ToArray());
|
||||
case OutputDescriptor.PKH pkh:
|
||||
return ExtractFromPkProvider(pkh.PkProvider, "-[legacy]");
|
||||
case OutputDescriptor.SH sh:
|
||||
var suffix = "-[p2sh]";
|
||||
if (sh.Inner is OutputDescriptor.Multi)
|
||||
{
|
||||
//non segwit
|
||||
suffix = "-[legacy]";
|
||||
}
|
||||
|
||||
if (sh.Inner is OutputDescriptor.Multi || sh.Inner is OutputDescriptor.WPKH ||
|
||||
sh.Inner is OutputDescriptor.WSH)
|
||||
{
|
||||
var ds = ParseOutputDescriptor(sh.Inner.ToString());
|
||||
return (Parse(ds.Item1 + suffix), ds.Item2);
|
||||
};
|
||||
throw new FormatException("sh descriptors are only supported with multsig(legacy or p2wsh) and segwit(p2wpkh)");
|
||||
case OutputDescriptor.WPKH wpkh:
|
||||
return ExtractFromPkProvider(wpkh.PkProvider, "");
|
||||
case OutputDescriptor.WSH wsh:
|
||||
if (wsh.Inner is OutputDescriptor.Multi)
|
||||
{
|
||||
return ParseOutputDescriptor(wsh.Inner.ToString());
|
||||
}
|
||||
throw new FormatException("wsh descriptors are only supported with multisig");
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(outputDescriptor));
|
||||
}
|
||||
}
|
||||
|
||||
public DerivationStrategyBase ParseElectrum(string str)
|
||||
{
|
||||
|
|
|
@ -18,8 +18,15 @@ namespace BTCPayServer
|
|||
throw new ArgumentNullException(nameof(network));
|
||||
if (derivationStrategy == null)
|
||||
throw new ArgumentNullException(nameof(derivationStrategy));
|
||||
var result = network.NBXplorerNetwork.DerivationStrategyFactory.Parse(derivationStrategy);
|
||||
return new DerivationSchemeSettings(result, network) { AccountOriginal = derivationStrategy.Trim() };
|
||||
var result = new DerivationSchemeSettings();
|
||||
result.Network = network;
|
||||
var parser = new DerivationSchemeParser(network);
|
||||
if (TryParseXpub(derivationStrategy, parser, ref result, false) || TryParseXpub(derivationStrategy, parser, ref result, true))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new FormatException("Invalid Derivation Scheme");
|
||||
}
|
||||
|
||||
public static bool TryParseFromJson(string config, BTCPayNetwork network, out DerivationSchemeSettings strategy)
|
||||
|
@ -40,6 +47,26 @@ namespace BTCPayServer
|
|||
|
||||
private static bool TryParseXpub(string xpub, DerivationSchemeParser derivationSchemeParser, ref DerivationSchemeSettings derivationSchemeSettings, bool electrum = true)
|
||||
{
|
||||
if (!electrum)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = derivationSchemeParser.ParseOutputDescriptor(xpub);
|
||||
derivationSchemeSettings.AccountOriginal = xpub.Trim();
|
||||
derivationSchemeSettings.AccountDerivation = result.Item1;
|
||||
derivationSchemeSettings.AccountKeySettings = result.Item2.Select((path, i) => new AccountKeySettings()
|
||||
{
|
||||
RootFingerprint = path?.MasterFingerprint,
|
||||
AccountKeyPath = path?.KeyPath,
|
||||
AccountKey = result.Item1.GetExtPubKeys().ElementAt(i).GetWif(derivationSchemeParser.Network)
|
||||
}).ToArray();
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
derivationSchemeSettings.AccountOriginal = xpub.Trim();
|
||||
|
|
Loading…
Add table
Reference in a new issue