mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-01-18 05:12:51 +01:00
Fix wallet import (#5695)
* Fix wallet import * Improve error message for import of wallet file
This commit is contained in:
parent
f31aa43c6a
commit
35b3fef7c5
@ -47,6 +47,7 @@ using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenQA.Selenium.DevTools.V100.DOMSnapshot;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
@ -855,8 +856,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// xpub
|
||||
var xpub = "xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw";
|
||||
Assert.Throws<FormatException>(() => parser.Parse(xpub, false, false, true));
|
||||
DerivationStrategyBase strategyBase = parser.Parse(xpub, false, false, false);
|
||||
DerivationStrategyBase strategyBase = parser.Parse(xpub);
|
||||
Assert.IsType<DirectDerivationStrategy>(strategyBase);
|
||||
Assert.True(((DirectDerivationStrategy)strategyBase).Segwit);
|
||||
Assert.Equal("tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS", strategyBase.ToString());
|
||||
@ -938,7 +938,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(parsers.TryParseWalletFile(tpub, testnet, out var settings, out var error));
|
||||
Assert.True(settings.AccountDerivation is DirectDerivationStrategy { Segwit: false });
|
||||
Assert.Equal($"{tpub}-[legacy]", ((DirectDerivationStrategy)settings.AccountDerivation).ToString());
|
||||
Assert.Equal("Generic", settings.Source);
|
||||
Assert.Equal("GenericFile", settings.Source);
|
||||
Assert.Null(error);
|
||||
|
||||
// xpub with fingerprint and account
|
||||
@ -954,7 +954,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(tpub, ((DirectDerivationStrategy)settings.AccountDerivation).ToString());
|
||||
Assert.Equal(HDFingerprint.TryParse(fingerprint, out var hd) ? hd : default, settings.AccountKeySettings[0].RootFingerprint);
|
||||
Assert.Equal(account, settings.AccountKeySettings[0].AccountKeyPath.ToString());
|
||||
Assert.Equal("Generic", settings.Source);
|
||||
Assert.Equal("GenericFile", settings.Source);
|
||||
Assert.Null(error);
|
||||
|
||||
// ColdCard
|
||||
@ -1069,6 +1069,50 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
||||
Assert.True(passport.AccountDerivation is TaprootDerivationStrategy);
|
||||
Assert.Equal("5c9e228d", passport.AccountKeySettings[0].RootFingerprint.ToString());
|
||||
Assert.Equal("86'/0'/0'", passport.AccountKeySettings[0].AccountKeyPath.ToString());
|
||||
|
||||
//electrum
|
||||
var electrumText =
|
||||
"""
|
||||
{
|
||||
"keystore": {
|
||||
"xpub": "vpub5Z14bnDNoEQeFdwZYSpVHcpzRpH99CnvSemzqTAvhjcgBTzPUVnaA5GhjgZc9J46duUprxQRUVUuqchazanXD6bLuVyarviNHBFUu6fBZNj",
|
||||
"xprv": "vprv9ENJcv8RKwqMTqyhLSuBz5bEV7hpdZjisjUBuV9K8azz1vpop6xJFEDRdfDwgWBpYgUUhEVxdvpxgV3f8NircysfebnBaPu5y2dcnSDAEEw",
|
||||
"type": "bip32",
|
||||
"pw_hash_version": 1
|
||||
},
|
||||
"wallet_type": "standard",
|
||||
"use_encryption": false,
|
||||
"seed_type": "bip39"
|
||||
}
|
||||
""";
|
||||
Assert.True(parsers.TryParseWalletFile(electrumText, testnet, out var electrum, out _));
|
||||
Assert.Equal("ElectrumFile", electrum.Source);
|
||||
|
||||
electrumText =
|
||||
"""
|
||||
{
|
||||
"keystore": {
|
||||
"derivation": "m/0h",
|
||||
"pw_hash_version": 1,
|
||||
"root_fingerprint": "fbb5b37d",
|
||||
"seed": "tiger room acoustic bracket thing film umbrella rather pepper tired vault remain",
|
||||
"seed_type": "segwit",
|
||||
"type": "bip32",
|
||||
"xprv": "zprvAaQyp6mTAX53zY4j2BbecRNtmTq2kSEKgy2y4yK3bFPKgPJLxrMmPxzZdRkWq5XvmtH2R4ko5YmJYH2MgnVkWr32pHi4Dc5627WyML32KTW",
|
||||
"xpub": "zpub6oQLDcJLztdMD29C8D8eyZKdKVfX9txB4BxZsMif9avJZBdVWPg1wmK3Uh3VxU7KXon1wm1xzvjyqmKWguYMqyjKP5f5Cho9f7uLfmRt2Br"
|
||||
},
|
||||
"wallet_type": "standard",
|
||||
"use_encryption": false,
|
||||
"seed_type": "bip39"
|
||||
}
|
||||
""";
|
||||
Assert.True(parsers.TryParseWalletFile(electrumText, mainnet, out electrum, out _));
|
||||
Assert.Equal("ElectrumFile", electrum.Source);
|
||||
Assert.Equal("0'", electrum.GetSigningAccountKeySettings().AccountKeyPath.ToString());
|
||||
Assert.True(electrum.AccountDerivation is DirectDerivationStrategy { Segwit: true });
|
||||
Assert.Equal("fbb5b37d", electrum.GetSigningAccountKeySettings().RootFingerprint.ToString());
|
||||
Assert.Equal("zpub6oQLDcJLztdMD29C8D8eyZKdKVfX9txB4BxZsMif9avJZBdVWPg1wmK3Uh3VxU7KXon1wm1xzvjyqmKWguYMqyjKP5f5Cho9f7uLfmRt2Br", electrum.AccountOriginal);
|
||||
Assert.Equal(((DirectDerivationStrategy)electrum.AccountDerivation).GetExtPubKeys().First().ParentFingerprint.ToString(), electrum.GetSigningAccountKeySettings().RootFingerprint.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -1949,7 +1993,7 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
||||
// Passing electrum stuff
|
||||
// Passing a native segwit from mainnet to a testnet parser, means the testnet parser will try to convert it into segwit
|
||||
result = testnetParser.Parse(
|
||||
"zpub6nL6PUGurpU3DfPDSZaRS6WshpbNc9ctCFFzrCn54cssnheM31SZJZUcFHKtjJJNhAueMbh6ptFMfy1aeiMQJr3RJ4DDt1hAPx7sMTKV48t", false, false, false);
|
||||
"zpub6nL6PUGurpU3DfPDSZaRS6WshpbNc9ctCFFzrCn54cssnheM31SZJZUcFHKtjJJNhAueMbh6ptFMfy1aeiMQJr3RJ4DDt1hAPx7sMTKV48t");
|
||||
Assert.Equal(
|
||||
"tpubD93CJNkmGjLXnsBqE2zGDqfEh1Q8iJ8wueordy3SeWt1RngbbuxXCsqASuVWFywmfoCwUE1rSfNJbaH4cBNcbp8WcyZgPiiRSTazLGL8U9w",
|
||||
result.ToString());
|
||||
@ -1973,7 +2017,7 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
||||
|
||||
// if prefix not recognize, assume it is segwit
|
||||
result = testnetParser.Parse(
|
||||
"xpub661MyMwAqRbcGeVGU5e5KBcau1HHEUGf9Wr7k4FyLa8yRPNQrrVa7Ndrgg8Afbe2UYXMSL6tJBFd2JewwWASsePPLjkcJFL1tTVEs3UQ23X", false, false, false);
|
||||
"xpub661MyMwAqRbcGeVGU5e5KBcau1HHEUGf9Wr7k4FyLa8yRPNQrrVa7Ndrgg8Afbe2UYXMSL6tJBFd2JewwWASsePPLjkcJFL1tTVEs3UQ23X");
|
||||
Assert.Equal(
|
||||
"tpubD6NzVbkrYhZ4YSg7vGdAX6wxE8NwDrmih9SR6cK7gUtsAg37w5LfFpJgviCxC6bGGT4G3uckqH5fiV9ZLN1gm5qgQLVuymzFUR5ed7U7ksu",
|
||||
result.ToString());
|
||||
@ -1982,13 +2026,13 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
||||
var tpub =
|
||||
"tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o";
|
||||
|
||||
result = testnetParser.Parse(tpub, false, true);
|
||||
result = testnetParser.Parse(tpub);
|
||||
Assert.Equal(tpub, result.ToString());
|
||||
|
||||
var regtestParser = new DerivationSchemeParser(regtestNetworkProvider.GetNetwork<BTCPayNetwork>("BTC"));
|
||||
var parsed =
|
||||
regtestParser.Parse(
|
||||
"xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]", false, false, false);
|
||||
"xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]");
|
||||
Assert.Equal(
|
||||
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[p2sh]",
|
||||
parsed.ToString());
|
||||
@ -1996,14 +2040,14 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
||||
// Let's make sure we can't generate segwit with dogecoin
|
||||
regtestParser = new DerivationSchemeParser(regtestNetworkProvider.GetNetwork<BTCPayNetwork>("DOGE"));
|
||||
parsed = regtestParser.Parse(
|
||||
"xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]", false, false, false);
|
||||
"xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]");
|
||||
Assert.Equal(
|
||||
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[legacy]",
|
||||
parsed.ToString());
|
||||
|
||||
regtestParser = new DerivationSchemeParser(regtestNetworkProvider.GetNetwork<BTCPayNetwork>("DOGE"));
|
||||
parsed = regtestParser.Parse(
|
||||
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[p2sh]", false, false, false);
|
||||
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[p2sh]");
|
||||
Assert.Equal(
|
||||
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[legacy]",
|
||||
parsed.ToString());
|
||||
@ -2227,7 +2271,7 @@ bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
|
||||
["derivationStrategy"] = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf"
|
||||
};
|
||||
var scheme = DerivationSchemeSettings.Parse("tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf", CreateNetworkProvider(ChainName.Regtest).BTC);
|
||||
|
||||
Assert.True(scheme.AccountDerivation is DirectDerivationStrategy { Segwit: true });
|
||||
scheme.Source = "ManualDerivationScheme";
|
||||
scheme.AccountOriginal = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf";
|
||||
var legacy2 = new JObject()
|
||||
|
@ -123,7 +123,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
try
|
||||
{
|
||||
|
||||
var strategy = network.GetDerivationSchemeParser().Parse(paymentMethod.DerivationScheme, false, true);
|
||||
var strategy = network.GetDerivationSchemeParser().Parse(paymentMethod.DerivationScheme);
|
||||
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
|
||||
|
||||
var line = strategy.GetLineFor(deposit);
|
||||
@ -173,7 +173,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
DerivationStrategyBase strategy;
|
||||
try
|
||||
{
|
||||
strategy = network.GetDerivationSchemeParser().Parse(paymentMethodData.DerivationScheme, false, true);
|
||||
strategy = network.GetDerivationSchemeParser().Parse(paymentMethodData.DerivationScheme);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -246,7 +246,7 @@ namespace BTCPayServer.Controllers.Greenfield
|
||||
{
|
||||
var store = Store;
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var strategy = network.GetDerivationSchemeParser().Parse(request.DerivationScheme, false, true);
|
||||
var strategy = network.GetDerivationSchemeParser().Parse(request.DerivationScheme);
|
||||
if (strategy != null)
|
||||
await wallet.TrackAsync(strategy);
|
||||
|
||||
|
@ -100,7 +100,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
if (fileContent is null || !_onChainWalletParsers.TryParseWalletFile(fileContent, network, out strategy, out _))
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.WalletFile), $"Importing wallet failed");
|
||||
ModelState.AddModelError(nameof(vm.WalletFile), $"Import failed, make sure you import a compatible wallet format");
|
||||
return View(vm.ViewName, vm);
|
||||
}
|
||||
}
|
||||
|
@ -919,7 +919,7 @@ namespace BTCPayServer.Controllers
|
||||
return derivationSchemeSettings;
|
||||
}
|
||||
|
||||
var strategy = parser.Parse(derivationScheme, false, true);
|
||||
var strategy = parser.Parse(derivationScheme);
|
||||
return new DerivationSchemeSettings(strategy, network);
|
||||
}
|
||||
|
||||
|
@ -34,7 +34,7 @@ namespace BTCPayServer
|
||||
{
|
||||
throw new FormatException("Custom change paths are not supported.");
|
||||
}
|
||||
return (Parse($"{hd.Extkey}{suffix}", true, false, false), null);
|
||||
return (Parse($"{hd.Extkey}{suffix}"), null);
|
||||
case PubKeyProvider.Origin origin:
|
||||
var innerResult = ExtractFromPkProvider(origin.Inner, suffix);
|
||||
return (innerResult.Item1, new[] { origin.KeyOriginInfo });
|
||||
@ -48,14 +48,14 @@ namespace BTCPayServer
|
||||
var xpubs = multi.PkProviders.Select(provider => ExtractFromPkProvider(provider));
|
||||
return (
|
||||
Parse(
|
||||
$"{multi.Threshold}-of-{(string.Join('-', xpubs.Select(tuple => tuple.Item1.ToString())))}{(multi.IsSorted ? "" : "-[keeporder]")}", true ,false, false),
|
||||
$"{multi.Threshold}-of-{(string.Join('-', xpubs.Select(tuple => tuple.Item1.ToString())))}{(multi.IsSorted ? "" : "-[keeporder]")}"),
|
||||
xpubs.SelectMany(tuple => tuple.Item2).ToArray());
|
||||
}
|
||||
|
||||
ArgumentNullException.ThrowIfNull(str);
|
||||
str = str.Trim();
|
||||
//nbitcoin output descriptor does not support taproot, so let's check if it is a taproot descriptor and fake until it is supported
|
||||
|
||||
|
||||
var outputDescriptor = OutputDescriptor.Parse(str, Network);
|
||||
switch (outputDescriptor)
|
||||
{
|
||||
@ -81,7 +81,7 @@ namespace BTCPayServer
|
||||
sh.Inner is OutputDescriptor.WSH)
|
||||
{
|
||||
var ds = ParseOutputDescriptor(sh.Inner.ToString());
|
||||
return (Parse(ds.Item1 + suffix, true, false, false), ds.Item2);
|
||||
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.Tr tr:
|
||||
@ -96,19 +96,25 @@ namespace BTCPayServer
|
||||
throw new ArgumentOutOfRangeException(nameof(outputDescriptor));
|
||||
}
|
||||
}
|
||||
public DerivationStrategyBase Parse(string str, bool ignorePrefix = false, bool ignoreBasePrefix = false, bool enforceNetworkPrefix = true, bool electrum = true)
|
||||
public DerivationStrategyBase Parse(string str)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(str);
|
||||
str = str.Trim();
|
||||
|
||||
HashSet<string> hintedLabels = new HashSet<string>();
|
||||
|
||||
if (!Network.Consensus.SupportSegwit)
|
||||
{
|
||||
hintedLabels.Add("legacy");
|
||||
str = str.Replace("-[p2sh]", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return BtcPayNetwork.NBXplorerNetwork.DerivationStrategyFactory.Parse(str);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
var parts = str.Split('-');
|
||||
bool hasLabel = false;
|
||||
for (int i = 0; i < parts.Length; i++)
|
||||
@ -125,29 +131,23 @@ namespace BTCPayServer
|
||||
hintedLabels.Add(parts[i].Substring(1, parts[i].Length - 2).ToLowerInvariant());
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var data = Network.GetBase58CheckEncoder().DecodeData(parts[i]);
|
||||
if (data.Length < 4)
|
||||
continue;
|
||||
var prefix = Utils.ToUInt32(data, false);
|
||||
|
||||
var standardPrefix = Utils.ToBytes(0x0488b21eU, false);
|
||||
for (int ii = 0; ii < 4; ii++)
|
||||
data[ii] = standardPrefix[ii];
|
||||
|
||||
var derivationScheme = GetBitcoinExtPubKeyByNetwork(Network, data).ToString();
|
||||
|
||||
if (enforceNetworkPrefix && !BtcPayNetwork.ElectrumMapping.ContainsKey(prefix))
|
||||
throw new FormatException(
|
||||
$"Invalid xpub. Is this really for {BtcPayNetwork.CryptoCode} {Network.ChainName}?");
|
||||
|
||||
if (!ignorePrefix && !hasLabel && electrum && BtcPayNetwork.ElectrumMapping.TryGetValue(prefix, out var type))
|
||||
if (BtcPayNetwork.ElectrumMapping.TryGetValue(prefix, out var type))
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case DerivationType.Legacy when !ignoreBasePrefix:
|
||||
case DerivationType.Legacy:
|
||||
hintedLabels.Add("legacy");
|
||||
break;
|
||||
case DerivationType.SegwitP2SH:
|
||||
@ -155,13 +155,8 @@ namespace BTCPayServer
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
parts[i] = derivationScheme;
|
||||
}
|
||||
catch (FormatException e) when (e.Message.StartsWith("Invalid xpub"))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch { continue; }
|
||||
}
|
||||
|
||||
@ -170,10 +165,35 @@ namespace BTCPayServer
|
||||
{
|
||||
str = $"{str}-[{label}]";
|
||||
}
|
||||
|
||||
return BtcPayNetwork.NBXplorerNetwork.DerivationStrategyFactory.Parse(str);
|
||||
}
|
||||
|
||||
internal DerivationStrategyBase ParseElectrum(string str)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(str);
|
||||
str = str.Trim();
|
||||
var data = Network.GetBase58CheckEncoder().DecodeData(str);
|
||||
if (data.Length < 4)
|
||||
throw new FormatException();
|
||||
var prefix = Utils.ToUInt32(data, false);
|
||||
|
||||
var standardPrefix = Utils.ToBytes(0x0488b21eU, false);
|
||||
for (int ii = 0; ii < 4; ii++)
|
||||
data[ii] = standardPrefix[ii];
|
||||
var extPubKey = GetBitcoinExtPubKeyByNetwork(Network, data);
|
||||
if (!BtcPayNetwork.ElectrumMapping.TryGetValue(prefix, out var type))
|
||||
{
|
||||
throw new FormatException();
|
||||
}
|
||||
if (type == DerivationType.Segwit)
|
||||
return new DirectDerivationStrategy(extPubKey, true);
|
||||
if (type == DerivationType.Legacy)
|
||||
return new DirectDerivationStrategy(extPubKey, false);
|
||||
if (type == DerivationType.SegwitP2SH)
|
||||
return BtcPayNetwork.NBXplorerNetwork.DerivationStrategyFactory.Parse(extPubKey.ToString() + "-[p2sh]");
|
||||
throw new FormatException();
|
||||
}
|
||||
|
||||
public static BitcoinExtPubKey GetBitcoinExtPubKeyByNetwork(Network network, byte[] data)
|
||||
{
|
||||
try
|
||||
|
@ -13,17 +13,17 @@ namespace BTCPayServer
|
||||
{
|
||||
public static DerivationSchemeSettings Parse(string derivationStrategy, BTCPayNetwork network)
|
||||
{
|
||||
string error = null;
|
||||
ArgumentNullException.ThrowIfNull(network);
|
||||
ArgumentNullException.ThrowIfNull(derivationStrategy);
|
||||
var result = new DerivationSchemeSettings { Network = network };
|
||||
var parser = network.GetDerivationSchemeParser();
|
||||
if (parser.TryParseXpub(derivationStrategy, ref result, out error))
|
||||
if (parser.TryParseXpub(derivationStrategy, ref result) ||
|
||||
parser.TryParseXpub(derivationStrategy, ref result, electrum: true))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new FormatException($"Invalid Derivation Scheme: {error}");
|
||||
throw new FormatException($"Invalid Derivation Scheme");
|
||||
}
|
||||
|
||||
public static bool TryParseFromJson(string config, BTCPayNetwork network, out DerivationSchemeSettings strategy)
|
||||
|
@ -48,8 +48,32 @@ namespace BTCPayServer
|
||||
}
|
||||
|
||||
public static bool TryParseXpub(this DerivationSchemeParser derivationSchemeParser, string xpub,
|
||||
ref DerivationSchemeSettings derivationSchemeSettings, out string error, bool electrum = true)
|
||||
ref DerivationSchemeSettings derivationSchemeSettings, bool electrum = false)
|
||||
{
|
||||
if (!electrum)
|
||||
{
|
||||
var isOD = Regex.Match(xpub, @"\(.*?\)").Success;
|
||||
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)
|
||||
{
|
||||
if (isOD)
|
||||
{
|
||||
return false;
|
||||
} // otherwise continue and try to parse input as xpub
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
// Extract fingerprint and account key path from export formats that contain them.
|
||||
@ -67,37 +91,32 @@ namespace BTCPayServer
|
||||
if (!string.IsNullOrEmpty(match.Groups[3].Value))
|
||||
xpub = match.Groups[3].Value;
|
||||
}
|
||||
|
||||
derivationSchemeSettings.AccountOriginal = xpub.Trim();
|
||||
derivationSchemeSettings.AccountDerivation =
|
||||
derivationSchemeParser.Parse(derivationSchemeSettings.AccountOriginal, false, false, false, electrum);
|
||||
derivationSchemeSettings.AccountDerivation = electrum ? derivationSchemeParser.ParseElectrum(derivationSchemeSettings.AccountOriginal) : derivationSchemeParser.Parse(derivationSchemeSettings.AccountOriginal);
|
||||
derivationSchemeSettings.AccountKeySettings = derivationSchemeSettings.AccountDerivation.GetExtPubKeys()
|
||||
.Select(key => new AccountKeySettings {AccountKey = key.GetWif(derivationSchemeParser.Network)})
|
||||
.ToArray();
|
||||
.Select(key => new AccountKeySettings
|
||||
{
|
||||
AccountKey = key.GetWif(derivationSchemeParser.Network)
|
||||
}).ToArray();
|
||||
if (derivationSchemeSettings.AccountDerivation is DirectDerivationStrategy direct && !direct.Segwit)
|
||||
derivationSchemeSettings.AccountOriginal =
|
||||
null; // Saving this would be confusing for user, as xpub of electrum is legacy derivation, but for btcpay, it is segwit derivation
|
||||
derivationSchemeSettings.AccountOriginal = null; // Saving this would be confusing for user, as xpub of electrum is legacy derivation, but for btcpay, it is segwit derivation
|
||||
// apply initial matches if there were no results from parsing
|
||||
if (rootFingerprint != null && derivationSchemeSettings.AccountKeySettings[0].RootFingerprint == null)
|
||||
{
|
||||
derivationSchemeSettings.AccountKeySettings[0].RootFingerprint = rootFingerprint;
|
||||
}
|
||||
|
||||
if (accountKeyPath != null && derivationSchemeSettings.AccountKeySettings[0].AccountKeyPath == null)
|
||||
{
|
||||
derivationSchemeSettings.AccountKeySettings[0].AccountKeyPath = accountKeyPath;
|
||||
}
|
||||
|
||||
error = null;
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
catch (Exception)
|
||||
{
|
||||
error = exception.Message;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static CardKey CreatePullPaymentCardKey(this IssuerKey issuerKey, byte[] uid, int version, string pullPaymentId)
|
||||
{
|
||||
var data = Encoding.UTF8.GetBytes(pullPaymentId);
|
||||
|
@ -11,6 +11,7 @@ public class ElectrumWalletFileParser : IWalletFileParser
|
||||
{
|
||||
public string? xpub { get; set; }
|
||||
public string? label { get; set; }
|
||||
public string? root_fingerprint { get; set; }
|
||||
public uint? ckcc_xfp { get; set; }
|
||||
public string? derivation { get; set; }
|
||||
public string? ColdCardFirmwareVersion { get; set; }
|
||||
@ -29,28 +30,25 @@ public class ElectrumWalletFileParser : IWalletFileParser
|
||||
var derivationSchemeParser = network.GetDerivationSchemeParser();
|
||||
result.Source = "ElectrumFile";
|
||||
|
||||
if (jobj.keystore.xpub is null || jobj.keystore.ckcc_xfp is null || jobj.keystore.derivation is null)
|
||||
if (jobj.keystore.xpub is null)
|
||||
return false;
|
||||
|
||||
var strategy = derivationSchemeParser.Parse(jobj.keystore.xpub, false, false, true);
|
||||
result.AccountDerivation = strategy;
|
||||
result.AccountOriginal = jobj.keystore.xpub;
|
||||
result.GetSigningAccountKeySettings();
|
||||
|
||||
if (!derivationSchemeParser.TryParseXpub(jobj.keystore.xpub, ref result, true))
|
||||
return false;
|
||||
|
||||
if (jobj.keystore.label is not null)
|
||||
result.Label = jobj.keystore.label;
|
||||
|
||||
result.AccountKeySettings[0].RootFingerprint = new HDFingerprint(jobj.keystore.ckcc_xfp.Value);
|
||||
result.AccountKeySettings[0].AccountKeyPath = new KeyPath(jobj.keystore.derivation);
|
||||
|
||||
if (jobj.keystore.ckcc_xfp is not null)
|
||||
result.AccountKeySettings[0].RootFingerprint = new HDFingerprint(jobj.keystore.ckcc_xfp.Value);
|
||||
if (jobj.keystore.root_fingerprint is not null)
|
||||
result.AccountKeySettings[0].RootFingerprint = HDFingerprint.Parse(jobj.keystore.root_fingerprint);
|
||||
if (jobj.keystore.derivation is not null)
|
||||
result.AccountKeySettings[0].AccountKeyPath = new KeyPath(jobj.keystore.derivation);
|
||||
if (jobj.keystore.ColdCardFirmwareVersion is not null)
|
||||
{
|
||||
result.Source = "ColdCard";
|
||||
}
|
||||
else if (jobj.keystore.CoboVaultFirmwareVersion is not null)
|
||||
{
|
||||
result.Source = "CoboVault";
|
||||
}
|
||||
derivationSchemeSettings = result;
|
||||
return true;
|
||||
}
|
||||
|
@ -5,8 +5,16 @@ public class NBXDerivGenericWalletFileParser : IWalletFileParser
|
||||
{
|
||||
public bool TryParse(BTCPayNetwork network, string data, [MaybeNullWhen(false)] out DerivationSchemeSettings derivationSchemeSettings)
|
||||
{
|
||||
derivationSchemeSettings = DerivationSchemeSettings.Parse(data, network);
|
||||
derivationSchemeSettings.Source = "Generic";
|
||||
return true;
|
||||
var result = new DerivationSchemeSettings { Network = network };
|
||||
var parser = network.GetDerivationSchemeParser();
|
||||
if (parser.TryParseXpub(data, ref result, electrum: true) ||
|
||||
parser.TryParseXpub(data, ref result))
|
||||
{
|
||||
derivationSchemeSettings = result;
|
||||
derivationSchemeSettings.Source = "GenericFile";
|
||||
return true;
|
||||
}
|
||||
derivationSchemeSettings = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ public class WasabiWalletFileParser : IWalletFileParser
|
||||
Network = network
|
||||
};
|
||||
|
||||
if (jobj is null || !derivationSchemeParser.TryParseXpub(jobj.ExtPubKey, ref result, out var error, false))
|
||||
if (jobj is null || !derivationSchemeParser.TryParseXpub(jobj.ExtPubKey, ref result))
|
||||
return false;
|
||||
|
||||
if (jobj.MasterFingerprint is not null)
|
||||
|
Loading…
Reference in New Issue
Block a user