Wallet file parsing: Add Wasabi test case and re-add Electrum distinction (#5694)

* Extend tests, add Wasabi file

* Re-add Electrum distinction

* Specter: Fix indentation

* Cleanups
This commit is contained in:
d11n 2024-01-24 01:28:22 +01:00 committed by GitHub
parent b03f8db06b
commit f31aa43c6a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 33 additions and 27 deletions

View file

@ -936,9 +936,10 @@ namespace BTCPayServer.Tests
// xpub
var tpub = "tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS";
Assert.True(parsers.TryParseWalletFile(tpub, testnet, out var settings, out var error));
Assert.Null(error);
Assert.True(settings.AccountDerivation is DirectDerivationStrategy { Segwit: false });
Assert.Equal($"{tpub}-[legacy]", ((DirectDerivationStrategy)settings.AccountDerivation).ToString());
Assert.Equal("Generic", settings.Source);
Assert.Null(error);
// xpub with fingerprint and account
tpub = "tpubDCXK98mNrPWuoWweaoUkqwxQF5NMWpQLy7n7XJgDCpwYfoZRXGafPaVM7mYqD7UKhsbMxkN864JY2PniMkt1Uk4dNuAMnWFVqdquyvZNyca";
@ -953,6 +954,8 @@ 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.Null(error);
// ColdCard
Assert.True(parsers.TryParseWalletFile(
@ -969,12 +972,15 @@ namespace BTCPayServer.Tests
settings.AccountOriginal);
Assert.Equal(root.Derive(new KeyPath("m/49'/0'/0'")).Neuter().PubKey.WitHash.ScriptPubKey.Hash.ScriptPubKey,
settings.AccountDerivation.GetDerivation().ScriptPubKey);
Assert.Equal("ElectrumFile", settings.Source);
Assert.Null(error);
// Should be legacy
Assert.True(parsers.TryParseWalletFile(
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"tpubDDWYqT3P24znfsaGX7kZcQhNc5LAjnQiKQvUCHF2jS6dsgJBRtymopEU5uGpMaR5YChjuiExZG1X2aTbqXkp82KqH5qnqwWHp6EWis9ZvKr\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/44'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}",
testnet, out settings, out error));
Assert.True(settings.AccountDerivation is DirectDerivationStrategy { Segwit: false });
Assert.Equal("ElectrumFile", settings.Source);
Assert.Null(error);
// Should be segwit p2sh
@ -982,6 +988,7 @@ namespace BTCPayServer.Tests
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DSddA9NoRUyJrQ4p86nsCiTSY7kLHrSxx3joEJXjHd4HPARhdXUATuk585FdWPVC2GdjsMePHb6BMDmf7c6KG4K4RPX6LVqBLtDcWpQJmh\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}",
testnet, out settings, out error));
Assert.True(settings.AccountDerivation is P2SHDerivationStrategy { Inner: DirectDerivationStrategy { Segwit: true } });
Assert.Equal("ElectrumFile", settings.Source);
Assert.Null(error);
// Should be segwit
@ -989,6 +996,7 @@ namespace BTCPayServer.Tests
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"vpub5YjYxTemJ39tFRnuAhwduyxG2tKGjoEpmvqVQRPqdYrqa6YGoeSzBtHXaJUYB19zDbXs3JjbEcVWERjQBPf9bEfUUMZNMv1QnMyHV8JPqyf\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/84'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}",
testnet, out settings, out error));
Assert.True(settings.AccountDerivation is DirectDerivationStrategy { Segwit: true });
Assert.Equal("ElectrumFile", settings.Source);
Assert.Null(error);
// Specter
@ -998,11 +1006,21 @@ namespace BTCPayServer.Tests
Assert.Equal(root.GetPublicKey().GetHDFingerPrint(), specter.AccountKeySettings[0].RootFingerprint);
Assert.Equal(specter.AccountKeySettings[0].RootFingerprint, hd);
Assert.Equal("49'/0'/0'", specter.AccountKeySettings[0].AccountKeyPath.ToString());
Assert.True(specter.AccountDerivation is DirectDerivationStrategy { Segwit: true });
Assert.Equal("Specter", specter.Label);
Assert.Null(error);
//BSMS BIP129, Nunchuk
// Wasabi
var wasabiJson = @"{""EncryptedSecret"": ""6PYNUAZZLS1ShkhHhm9ayiNwXPAPLN669fN5mY2WbGm1Hqc88tomqWXabU"",""ChainCode"": ""UoHIB+2mDbZSowo11TfDQbsYK6q1DrZ2H2yqQBxu6m8="",""MasterFingerprint"": ""0f215605"",""ExtPubKey"": ""xpub6DUXFa6fMrFpg7x4nEd8jBU6xDN3vkSXsVUrSbUB2dadbYaPE31czwVdv146JRStGsc2U6TywdKnGoVcP8Rtp2AZQyzXxQb7HrgmR9LrqLA"",""TaprootExtPubKey"": ""xpub6D2thLU5KwUk3axkJu1UT3yKFshCGU7TMuxhPgZMd91VvrcDwHdRwdzLk61cSHtZC6BkaipPgfFwjoDBY4m1WxyznxZLukYgM4dC6iRJVf8"",""SkipSynchronization"": true,""UseTurboSync"": true,""MinGapLimit"": 21,""AccountKeyPath"": ""84'/0'/0'"",""TaprootAccountKeyPath"": ""86'/0'/0'"",""BlockchainState"": {""Network"": ""Main"",""Height"": ""503723"",""TurboSyncHeight"": ""503723""},""PreferPsbtWorkflow"": false,""AutoCoinJoin"": true,""PlebStopThreshold"": ""0.01"",""AnonScoreTarget"": 5,""FeeRateMedianTimeFrameHours"": 0,""IsCoinjoinProfileSelected"": true,""RedCoinIsolation"": false,""ExcludedCoinsFromCoinJoin"": [],""HdPubKeys"": [{""PubKey"": ""03f88b9c3e16e40a5a9eaf8b36b9bcee7bbc93fd9eea640b541efb931ac55f7ff5"",""FullKeyPath"": ""84'/0'/0'/1/0"",""Label"": """",""KeyState"": 0},{""PubKey"": ""03e5241fc28aa556d7cb826b9a9f5ecee85287e7476746126263574a5e27fbf569"",""FullKeyPath"": ""84'/0'/0'/0/0"",""Label"": """",""KeyState"": 0}]}";
Assert.True(parsers.TryParseWalletFile(wasabiJson, mainnet, out var wasabi, out error));
Assert.Null(error);
Assert.Equal("WasabiFile", wasabi.Source);
Assert.Single(wasabi.AccountKeySettings);
Assert.Equal("84'/0'/0'", wasabi.AccountKeySettings[0].AccountKeyPath.ToString());
Assert.Equal("0f215605", wasabi.AccountKeySettings[0].RootFingerprint.ToString());
Assert.True(wasabi.AccountDerivation is DirectDerivationStrategy { Segwit: true });
// BSMS BIP129, Nunchuk
var bsms = @"BSMS 1.0
wsh(sortedmulti(1,[5c9e228d/48'/0'/0'/2']xpub6EgGHjcvovyN3nK921zAGPfuB41cJXkYRdt3tLGmiMyvbgHpss4X1eRZwShbEBb1znz2e2bCkCED87QZpin3sSYKbmCzQ9Sc7LaV98ngdeX/**,[2b0e251e/48'/0'/0'/2']xpub6DrimHB8KUSkPvmJ8Pk8RE769EdDm2VEoZ8MBz76w9QupP8Py4wexs4Pa3aRB1LUEhc9GyY6ypDWEFFRCgqeDQePcyWQfjtmintrehq3JCL/**))
/0/*,/1/*

View file

@ -96,7 +96,7 @@ namespace BTCPayServer
throw new ArgumentOutOfRangeException(nameof(outputDescriptor));
}
}
public DerivationStrategyBase Parse(string str, bool ignorePrefix = false, bool ignoreBasePrefix = false, bool enforceNetworkPrefix = true)
public DerivationStrategyBase Parse(string str, bool ignorePrefix = false, bool ignoreBasePrefix = false, bool enforceNetworkPrefix = true, bool electrum = true)
{
ArgumentNullException.ThrowIfNull(str);
str = str.Trim();
@ -143,7 +143,7 @@ namespace BTCPayServer
throw new FormatException(
$"Invalid xpub. Is this really for {BtcPayNetwork.CryptoCode} {Network.ChainName}?");
if (!ignorePrefix && !hasLabel && BtcPayNetwork.ElectrumMapping.TryGetValue(prefix, out var type))
if (!ignorePrefix && !hasLabel && electrum && BtcPayNetwork.ElectrumMapping.TryGetValue(prefix, out var type))
{
switch (type)
{

View file

@ -1,16 +1,11 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using BTCPayServer.Payments;
using NBitcoin;
using NBitcoin.DataEncoders;
using NBXplorer.Client;
using NBXplorer.DerivationStrategy;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer
{
@ -21,8 +16,7 @@ namespace BTCPayServer
string error = null;
ArgumentNullException.ThrowIfNull(network);
ArgumentNullException.ThrowIfNull(derivationStrategy);
var result = new DerivationSchemeSettings();
result.Network = network;
var result = new DerivationSchemeSettings { Network = network };
var parser = network.GetDerivationSchemeParser();
if (parser.TryParseXpub(derivationStrategy, ref result, out error))
{

View file

@ -32,9 +32,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using NBitcoin;
using NBitcoin.DataEncoders;
using NBitcoin.Payment;
using NBitcoin.Scripting;
using NBXplorer.DerivationStrategy;
using NBXplorer.Models;
using Newtonsoft.Json;
@ -50,7 +48,7 @@ namespace BTCPayServer
}
public static bool TryParseXpub(this DerivationSchemeParser derivationSchemeParser, string xpub,
ref DerivationSchemeSettings derivationSchemeSettings, out string error)
ref DerivationSchemeSettings derivationSchemeSettings, out string error, bool electrum = true)
{
try
{
@ -72,7 +70,7 @@ namespace BTCPayServer
derivationSchemeSettings.AccountOriginal = xpub.Trim();
derivationSchemeSettings.AccountDerivation =
derivationSchemeParser.Parse(derivationSchemeSettings.AccountOriginal, false, false, false);
derivationSchemeParser.Parse(derivationSchemeSettings.AccountOriginal, false, false, false, electrum);
derivationSchemeSettings.AccountKeySettings = derivationSchemeSettings.AccountDerivation.GetExtPubKeys()
.Select(key => new AccountKeySettings {AccountKey = key.GetWif(derivationSchemeParser.Network)})
.ToArray();

View file

@ -1,10 +1,7 @@
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
using BTCPayServer;
using NBitcoin;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Services.WalletFileParsing;
public class ElectrumWalletFileParser : IWalletFileParser
{
@ -28,7 +25,7 @@ public class ElectrumWalletFileParser : IWalletFileParser
if (jobj?.keystore is null)
return false;
var result = new BTCPayServer.DerivationSchemeSettings() { Network = network };
var result = new DerivationSchemeSettings { Network = network };
var derivationSchemeParser = network.GetDerivationSchemeParser();
result.Source = "ElectrumFile";
@ -46,7 +43,6 @@ public class ElectrumWalletFileParser : IWalletFileParser
result.AccountKeySettings[0].RootFingerprint = new HDFingerprint(jobj.keystore.ckcc_xfp.Value);
result.AccountKeySettings[0].AccountKeyPath = new KeyPath(jobj.keystore.derivation);
if (jobj.keystore.ColdCardFirmwareVersion is not null)
{
result.Source = "ColdCard";

View file

@ -1,13 +1,11 @@
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
using BTCPayServer;
namespace BTCPayServer.Services.WalletFileParsing;
public class NBXDerivGenericWalletFileParser : IWalletFileParser
{
public bool TryParse(BTCPayNetwork network, string data, [MaybeNullWhen(false)] out DerivationSchemeSettings derivationSchemeSettings)
{
derivationSchemeSettings = BTCPayServer.DerivationSchemeSettings.Parse(data, network);
derivationSchemeSettings = DerivationSchemeSettings.Parse(data, network);
derivationSchemeSettings.Source = "Generic";
return true;
}

View file

@ -27,9 +27,11 @@ public class SpecterWalletFileParser : IWalletFileParser
return false;
if (!_outputDescriptorOnChainWalletParser.TryParse(network, jobj.descriptor, out derivationSchemeSettings))
return false;
derivationSchemeSettings.Source = "Specter";
if (jobj.label is not null)
derivationSchemeSettings.Label = jobj.label;
derivationSchemeSettings.Label = jobj.label;
return true;
}
}

View file

@ -30,7 +30,7 @@ public class WasabiWalletFileParser : IWalletFileParser
Network = network
};
if (jobj is null || !derivationSchemeParser.TryParseXpub(jobj.ExtPubKey, ref result, out var error))
if (jobj is null || !derivationSchemeParser.TryParseXpub(jobj.ExtPubKey, ref result, out var error, false))
return false;
if (jobj.MasterFingerprint is not null)