diff --git a/BTCPayServer/Controllers/UIStoresController.Onchain.cs b/BTCPayServer/Controllers/UIStoresController.Onchain.cs index fff198c72..17829b173 100644 --- a/BTCPayServer/Controllers/UIStoresController.Onchain.cs +++ b/BTCPayServer/Controllers/UIStoresController.Onchain.cs @@ -90,9 +90,17 @@ namespace BTCPayServer.Controllers if (vm.WalletFile != null) { - if (!_onChainWalletParsers.TryParseWalletFile(await ReadAllText(vm.WalletFile), network, out strategy, out var error)) + string fileContent = null; + try { - ModelState.AddModelError(nameof(vm.WalletFile), $"Importing wallet failed: {error}"); + fileContent = await ReadAllText(vm.WalletFile); + } + catch + { + } + if (fileContent is null || !_onChainWalletParsers.TryParseWalletFile(fileContent, network, out strategy, out _)) + { + ModelState.AddModelError(nameof(vm.WalletFile), $"Importing wallet failed"); return View(vm.ViewName, vm); } } diff --git a/BTCPayServer/Services/WalletFileParsers.cs b/BTCPayServer/Services/WalletFileParsers.cs index a5b09dc1d..36c7ec9a0 100644 --- a/BTCPayServer/Services/WalletFileParsers.cs +++ b/BTCPayServer/Services/WalletFileParsers.cs @@ -19,7 +19,7 @@ public class WalletFileParsers public bool TryParseWalletFile(string fileContents, BTCPayNetwork network, [MaybeNullWhen(false)] out DerivationSchemeSettings settings, [MaybeNullWhen(true)] out string error) { settings = null; - error = string.Empty; + error = null; ArgumentNullException.ThrowIfNull(fileContents); ArgumentNullException.ThrowIfNull(network); if (HexEncoder.IsWellFormed(fileContents)) @@ -29,19 +29,16 @@ public class WalletFileParsers foreach (IWalletFileParser onChainWalletParser in Parsers) { - var result = onChainWalletParser.TryParse(network, fileContents); - if (result.DerivationSchemeSettings is not null) + try { - settings = result.DerivationSchemeSettings; - error = null; - return true; + if (onChainWalletParser.TryParse(network, fileContents, out settings)) + return true; } - - if (result.Error is not null) + catch (Exception) { - error = result.Error; } } + error = "Unsupported file format"; return false; } } diff --git a/BTCPayServer/Services/WalletFileParsing/BSMSWalletFileParser.cs b/BTCPayServer/Services/WalletFileParsing/BSMSWalletFileParser.cs index 36803ce74..16510c703 100644 --- a/BTCPayServer/Services/WalletFileParsing/BSMSWalletFileParser.cs +++ b/BTCPayServer/Services/WalletFileParsing/BSMSWalletFileParser.cs @@ -1,5 +1,6 @@ -#nullable enable +#nullable enable using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using BTCPayServer; using NBitcoin; @@ -10,67 +11,51 @@ using BTCPayNetwork = BTCPayServer.BTCPayNetwork; namespace BTCPayServer.Services.WalletFileParsing; public class BSMSWalletFileParser : IWalletFileParser { - public (BTCPayServer.DerivationSchemeSettings? DerivationSchemeSettings, string? Error) TryParse( - BTCPayNetwork network, - string data) + public bool TryParse(BTCPayNetwork network, string data, [MaybeNullWhen(false)] out DerivationSchemeSettings derivationSchemeSettings) { - try + derivationSchemeSettings = null; + string[] lines = data.Split( + new[] { "\r\n", "\r", "\n" }, + StringSplitOptions.None + ); + + if (lines.Length < 4 || !lines[0].Trim().Equals("BSMS 1.0")) + return false; + + var descriptor = lines[1]; + var derivationPath = lines[2].Split(',', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault() ?? + "/0/*"; + if (derivationPath == "No path restrictions") + derivationPath = "/0/*"; + + if (derivationPath != "/0/*") + return false; + + + descriptor = descriptor.Replace("/**", derivationPath); + var testAddress = BitcoinAddress.Create(lines[3], network.NBitcoinNetwork); + + var result = network.GetDerivationSchemeParser().ParseOutputDescriptor(descriptor); + + var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit); + var line = result.Item1.GetLineFor(deposit).Derive(0); + + if (testAddress.ScriptPubKey != line.ScriptPubKey) + return false; + + derivationSchemeSettings = new BTCPayServer.DerivationSchemeSettings() { - string[] lines = data.Split( - new[] {"\r\n", "\r", "\n"}, - StringSplitOptions.None - ); - - if (!lines[0].Trim().Equals("BSMS 1.0")) + Network = network, + Source = "BSMS", + AccountDerivation = result.Item1, + AccountOriginal = descriptor.Trim(), + AccountKeySettings = result.Item2.Select((path, i) => new AccountKeySettings() { - return (null, null); - } - - var descriptor = lines[1]; - var derivationPath = lines[2].Split(',', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault() ?? - "/0/*"; - if (derivationPath == "No path restrictions") - { - derivationPath = "/0/*"; - } - - if (derivationPath != "/0/*") - { - return (null, "BTCPay Server can only derive address to the deposit and change paths"); - } - - - descriptor = descriptor.Replace("/**", derivationPath); - var testAddress = BitcoinAddress.Create(lines[3], network.NBitcoinNetwork); - - var result = network.GetDerivationSchemeParser().ParseOutputDescriptor(descriptor); - - var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit); - var line = result.Item1.GetLineFor(deposit).Derive(0); - - if (testAddress.ScriptPubKey != line.ScriptPubKey) - { - return (null, "BSMS test address did not match our generated address"); - } - - var derivationSchemeSettings = new BTCPayServer.DerivationSchemeSettings() - { - Network = network, - Source = "BSMS", - AccountDerivation = result.Item1, - AccountOriginal = descriptor.Trim(), - AccountKeySettings = result.Item2.Select((path, i) => new AccountKeySettings() - { - RootFingerprint = path?.MasterFingerprint, - AccountKeyPath = path?.KeyPath, - AccountKey = result.Item1.GetExtPubKeys().ElementAt(i).GetWif(network.NBitcoinNetwork) - }).ToArray() - }; - return (derivationSchemeSettings, null); - } - catch (Exception e) - { - return (null, $"BSMS parse error: {e.Message}"); - } + RootFingerprint = path?.MasterFingerprint, + AccountKeyPath = path?.KeyPath, + AccountKey = result.Item1.GetExtPubKeys().ElementAt(i).GetWif(network.NBitcoinNetwork) + }).ToArray() + }; + return true; } } diff --git a/BTCPayServer/Services/WalletFileParsing/ElectrumWalletFileParser.cs b/BTCPayServer/Services/WalletFileParsing/ElectrumWalletFileParser.cs index 65c53190b..7ff02d775 100644 --- a/BTCPayServer/Services/WalletFileParsing/ElectrumWalletFileParser.cs +++ b/BTCPayServer/Services/WalletFileParsing/ElectrumWalletFileParser.cs @@ -1,88 +1,61 @@ #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 { - public (BTCPayServer.DerivationSchemeSettings? DerivationSchemeSettings, string? Error) TryParse(BTCPayNetwork network, - string data) + class ElectrumFormat { - try + internal class KeyStoreFormat { - var derivationSchemeParser = network.GetDerivationSchemeParser(); - var jobj = JObject.Parse(data); - var result = new BTCPayServer.DerivationSchemeSettings() {Network = network}; - - if (jobj["keystore"] is JObject keyStore) - { - result.Source = "ElectrumFile"; - jobj = keyStore; - - if (!jobj.TryGetValue("xpub", StringComparison.InvariantCultureIgnoreCase, out var xpubToken)) - { - return (null, "no xpub"); - } - var strategy = derivationSchemeParser.Parse(xpubToken.Value(), false, false, true); - result.AccountDerivation = strategy; - result.AccountOriginal = xpubToken.Value(); - result.GetSigningAccountKeySettings(); - - if (jobj["label"]?.Value() is string label) - { - try - { - result.Label = label; - } - catch - { - return (null, "Label was not a string"); - } - } - - if (jobj["ckcc_xfp"]?.Value() is uint xfp) - { - try - { - result.AccountKeySettings[0].RootFingerprint = - new HDFingerprint(xfp); - } - catch - { - return (null, "fingerprint was not a uint"); - } - } - - if (jobj["derivation"]?.Value() is string derivation) - { - try - { - result.AccountKeySettings[0].AccountKeyPath = new KeyPath(derivation); - } - catch - { - return (null, "derivation keypath was not valid"); - } - } - - - if (jobj.ContainsKey("ColdCardFirmwareVersion")) - { - result.Source = "ColdCard"; - } - else if (jobj.ContainsKey("CoboVaultFirmwareVersion")) - { - result.Source = "CoboVault"; - } - return (result, null); - } - + public string? xpub { get; set; } + public string? label { get; set; } + public uint? ckcc_xfp { get; set; } + public string? derivation { get; set; } + public string? ColdCardFirmwareVersion { get; set; } + public string? CoboVaultFirmwareVersion { get; set; } } - catch (FormatException) + public KeyStoreFormat? keystore { get; set; } + } + public bool TryParse(BTCPayNetwork network, string data, [MaybeNullWhen(false)] out DerivationSchemeSettings derivationSchemeSettings) + { + derivationSchemeSettings = null; + var jobj = JsonConvert.DeserializeObject(data); + if (jobj?.keystore is null) + return false; + + var result = new BTCPayServer.DerivationSchemeSettings() { Network = network }; + var derivationSchemeParser = network.GetDerivationSchemeParser(); + result.Source = "ElectrumFile"; + + if (jobj.keystore.xpub is null || jobj.keystore.ckcc_xfp is null || jobj.keystore.derivation 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 (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.ColdCardFirmwareVersion is not null) { - return (null, "invalid xpub"); + result.Source = "ColdCard"; } - return (null, null); + else if (jobj.keystore.CoboVaultFirmwareVersion is not null) + { + result.Source = "CoboVault"; + } + derivationSchemeSettings = result; + return true; } } diff --git a/BTCPayServer/Services/WalletFileParsing/IWalletFileParser.cs b/BTCPayServer/Services/WalletFileParsing/IWalletFileParser.cs index 1276ddead..1b4538758 100644 --- a/BTCPayServer/Services/WalletFileParsing/IWalletFileParser.cs +++ b/BTCPayServer/Services/WalletFileParsing/IWalletFileParser.cs @@ -1,7 +1,8 @@ -#nullable enable +#nullable enable +using System.Diagnostics.CodeAnalysis; using BTCPayServer; namespace BTCPayServer.Services.WalletFileParsing; public interface IWalletFileParser { - (BTCPayServer.DerivationSchemeSettings? DerivationSchemeSettings, string? Error) TryParse(BTCPayNetwork network, string data); + bool TryParse(BTCPayNetwork network, string data, [MaybeNullWhen(false)] out DerivationSchemeSettings derivationSchemeSettings); } diff --git a/BTCPayServer/Services/WalletFileParsing/NBXDerivGenericWalletFileParser.cs b/BTCPayServer/Services/WalletFileParsing/NBXDerivGenericWalletFileParser.cs index 723daef54..80cb87e0b 100644 --- a/BTCPayServer/Services/WalletFileParsing/NBXDerivGenericWalletFileParser.cs +++ b/BTCPayServer/Services/WalletFileParsing/NBXDerivGenericWalletFileParser.cs @@ -1,21 +1,14 @@ #nullable enable using System; +using System.Diagnostics.CodeAnalysis; using BTCPayServer; namespace BTCPayServer.Services.WalletFileParsing; public class NBXDerivGenericWalletFileParser : IWalletFileParser { - public (BTCPayServer.DerivationSchemeSettings? DerivationSchemeSettings, string? Error) TryParse(BTCPayNetwork network, - string data) + public bool TryParse(BTCPayNetwork network, string data, [MaybeNullWhen(false)] out DerivationSchemeSettings derivationSchemeSettings) { - try - { - var result = BTCPayServer.DerivationSchemeSettings.Parse(data, network); - result.Source = "Generic"; - return (result, null); - } - catch (Exception) - { - return (null, null); - } + derivationSchemeSettings = BTCPayServer.DerivationSchemeSettings.Parse(data, network); + derivationSchemeSettings.Source = "Generic"; + return true; } } diff --git a/BTCPayServer/Services/WalletFileParsing/OutputDescriptorJsonWalletFileParser.cs b/BTCPayServer/Services/WalletFileParsing/OutputDescriptorJsonWalletFileParser.cs index 01471c456..1d337c327 100644 --- a/BTCPayServer/Services/WalletFileParsing/OutputDescriptorJsonWalletFileParser.cs +++ b/BTCPayServer/Services/WalletFileParsing/OutputDescriptorJsonWalletFileParser.cs @@ -1,36 +1,34 @@ #nullable enable using System; +using System.Diagnostics.CodeAnalysis; using BTCPayServer; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace BTCPayServer.Services.WalletFileParsing; public class OutputDescriptorJsonWalletFileParser : IWalletFileParser { private readonly OutputDescriptorWalletFileParser _outputDescriptorOnChainWalletParser; + class OutputDescriptorJsonWalletFileFormat + { + public string? Descriptor { get; set; } + public string? Source { get; set; } + } public OutputDescriptorJsonWalletFileParser(OutputDescriptorWalletFileParser outputDescriptorOnChainWalletParser) { _outputDescriptorOnChainWalletParser = outputDescriptorOnChainWalletParser; } - public (DerivationSchemeSettings? DerivationSchemeSettings, string? Error) TryParse(BTCPayNetwork network, - string data) + public bool TryParse(BTCPayNetwork network, string data, [MaybeNullWhen(false)] out DerivationSchemeSettings derivationSchemeSettings) { - try - { - var jobj = JObject.Parse(data); - if (!jobj.TryGetValue("Descriptor", StringComparison.InvariantCultureIgnoreCase, out var descriptorToken) || - descriptorToken?.Value() is not string desc) - return (null, null); + derivationSchemeSettings = null; + var jobj = JsonConvert.DeserializeObject(data); + if (jobj?.Descriptor is null) + return false; - - var result = _outputDescriptorOnChainWalletParser.TryParse(network, desc); - if (result.DerivationSchemeSettings is not null && jobj.TryGetValue("Source", StringComparison.InvariantCultureIgnoreCase, out var sourceToken)) - result.DerivationSchemeSettings.Source = sourceToken.Value(); - return result; - - } - catch (Exception) - { - return (null, null); - } + if (!_outputDescriptorOnChainWalletParser.TryParse(network, jobj.Descriptor, out derivationSchemeSettings)) + return false; + if (jobj.Source is not null) + derivationSchemeSettings.Source = jobj.Source; + return true; } } diff --git a/BTCPayServer/Services/WalletFileParsing/OutputDescriptorWalletFileParser.cs b/BTCPayServer/Services/WalletFileParsing/OutputDescriptorWalletFileParser.cs index 90c7f079d..8bbb2d716 100644 --- a/BTCPayServer/Services/WalletFileParsing/OutputDescriptorWalletFileParser.cs +++ b/BTCPayServer/Services/WalletFileParsing/OutputDescriptorWalletFileParser.cs @@ -1,42 +1,33 @@ #nullable enable using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using BTCPayServer; namespace BTCPayServer.Services.WalletFileParsing; public class OutputDescriptorWalletFileParser : IWalletFileParser { - public (BTCPayServer.DerivationSchemeSettings? DerivationSchemeSettings, string? Error) TryParse(BTCPayNetwork network, - string data) + public bool TryParse(BTCPayNetwork network, string data, [MaybeNullWhen(false)] out DerivationSchemeSettings derivationSchemeSettings) { - try + derivationSchemeSettings = null; + var maybeOutputDesc = !data.Trim().StartsWith("{", StringComparison.OrdinalIgnoreCase); + if (!maybeOutputDesc) + return false; + var derivationSchemeParser = network.GetDerivationSchemeParser(); + var descriptor = derivationSchemeParser.ParseOutputDescriptor(data); + derivationSchemeSettings = new DerivationSchemeSettings() { - var maybeOutputDesc = !data.Trim().StartsWith("{", StringComparison.OrdinalIgnoreCase); - if (!maybeOutputDesc) - return (null, null); - - var derivationSchemeParser = network.GetDerivationSchemeParser(); - - var descriptor = derivationSchemeParser.ParseOutputDescriptor(data); - - var derivationSchemeSettings = new DerivationSchemeSettings() + Network = network, + Source = "OutputDescriptor", + AccountOriginal = data.Trim(), + AccountDerivation = descriptor.Item1, + AccountKeySettings = descriptor.Item2.Select((path, i) => new AccountKeySettings() { - Network = network, - Source = "OutputDescriptor", - AccountOriginal = data.Trim(), - AccountDerivation = descriptor.Item1, - AccountKeySettings = descriptor.Item2.Select((path, i) => new AccountKeySettings() - { - RootFingerprint = path?.MasterFingerprint, - AccountKeyPath = path?.KeyPath, - AccountKey = - descriptor.Item1.GetExtPubKeys().ElementAt(i).GetWif(derivationSchemeParser.Network) - }).ToArray() - }; - return (derivationSchemeSettings, null); - } - catch (Exception exception) - { - return (null, exception.Message); - } + RootFingerprint = path?.MasterFingerprint, + AccountKeyPath = path?.KeyPath, + AccountKey = + descriptor.Item1.GetExtPubKeys().ElementAt(i).GetWif(derivationSchemeParser.Network) + }).ToArray() + }; + return true; } } diff --git a/BTCPayServer/Services/WalletFileParsing/SpecterWalletFileParser.cs b/BTCPayServer/Services/WalletFileParsing/SpecterWalletFileParser.cs index 3c232974c..0a2644ce1 100644 --- a/BTCPayServer/Services/WalletFileParsing/SpecterWalletFileParser.cs +++ b/BTCPayServer/Services/WalletFileParsing/SpecterWalletFileParser.cs @@ -1,41 +1,35 @@ #nullable enable using System; +using System.Diagnostics.CodeAnalysis; using BTCPayServer; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace BTCPayServer.Services.WalletFileParsing; public class SpecterWalletFileParser : IWalletFileParser { private readonly OutputDescriptorWalletFileParser _outputDescriptorOnChainWalletParser; + class SpecterFormat + { + public string? descriptor { get; set; } + public int? blockheight { get; set; } + public string? label { get; set; } + } public SpecterWalletFileParser(OutputDescriptorWalletFileParser outputDescriptorOnChainWalletParser) { _outputDescriptorOnChainWalletParser = outputDescriptorOnChainWalletParser; } - public (DerivationSchemeSettings? DerivationSchemeSettings, string? Error) TryParse(BTCPayNetwork network, - string data) + public bool TryParse(BTCPayNetwork network, string data, [MaybeNullWhen(false)] out DerivationSchemeSettings derivationSchemeSettings) { - try - { - var jobj = JObject.Parse(data); - if (!jobj.TryGetValue("descriptor", StringComparison.InvariantCultureIgnoreCase, out var descriptorObj) - || !jobj.ContainsKey("blockheight") - || descriptorObj?.Value() is not string desc) - return (null, null); - - - var result = _outputDescriptorOnChainWalletParser.TryParse(network, desc); - if (result.DerivationSchemeSettings is not null) - result.DerivationSchemeSettings.Source = "Specter"; - - if (result.DerivationSchemeSettings is not null && jobj.TryGetValue("label", - StringComparison.InvariantCultureIgnoreCase, out var label) && label?.Value() is string labelValue) - result.DerivationSchemeSettings.Label = labelValue; - return result; - - } - catch (Exception) - { - return (null, null); - } + derivationSchemeSettings = null; + var jobj = JsonConvert.DeserializeObject(data); + if (jobj?.descriptor is null || jobj.blockheight is null) + 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; + return true; } } diff --git a/BTCPayServer/Services/WalletFileParsing/WasabiWalletFileParser.cs b/BTCPayServer/Services/WalletFileParsing/WasabiWalletFileParser.cs index 246cb1655..4aea07463 100644 --- a/BTCPayServer/Services/WalletFileParsing/WasabiWalletFileParser.cs +++ b/BTCPayServer/Services/WalletFileParsing/WasabiWalletFileParser.cs @@ -1,105 +1,70 @@ #nullable enable using System; +using System.Diagnostics.CodeAnalysis; using System.Linq; using BTCPayServer; using NBitcoin; using NBitcoin.DataEncoders; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace BTCPayServer.Services.WalletFileParsing; public class WasabiWalletFileParser : IWalletFileParser { - - public (DerivationSchemeSettings? DerivationSchemeSettings, string? Error) TryParse(BTCPayNetwork network, - string data) + class WasabiFormat { - try + public string? ExtPubKey { get; set; } + public string? MasterFingerprint { get; set; } + public string? AccountKeyPath { get; set; } + public string? ColdCardFirmwareVersion { get; set; } + public string? CoboVaultFirmwareVersion { get; set; } + public string? DerivationPath { get; set; } + public string? Source { get; set; } + } + public bool TryParse(BTCPayNetwork network, string data, [MaybeNullWhen(false)] out DerivationSchemeSettings derivationSchemeSettings) + { + derivationSchemeSettings = null; + var jobj = JsonConvert.DeserializeObject(data); + var derivationSchemeParser = network.GetDerivationSchemeParser(); + var result = new DerivationSchemeSettings() { - var jobj = JObject.Parse(data); - if (jobj["ExtPubKey"]?.Value() is not string extPubKey) - return (null, null); + Network = network + }; - var derivationSchemeParser = network.GetDerivationSchemeParser(); - var result = new DerivationSchemeSettings() - { - Network = network - }; + if (jobj is null || !derivationSchemeParser.TryParseXpub(jobj.ExtPubKey, ref result, out var error)) + return false; - if (!derivationSchemeParser.TryParseXpub(extPubKey, ref result, out var error)) + if (jobj.MasterFingerprint is not null) + { + // https://github.com/zkSNACKs/WalletWasabi/pull/1663#issuecomment-508073066 + if (uint.TryParse(jobj.MasterFingerprint, out var fingerprint)) { - return (null, error); - } - - if (jobj["MasterFingerprint"]?.ToString()?.Trim() is string mfpString) - { - try - { - // https://github.com/zkSNACKs/WalletWasabi/pull/1663#issuecomment-508073066 - if (uint.TryParse(mfpString, out var fingerprint)) - { - result.AccountKeySettings[0].RootFingerprint = new HDFingerprint(fingerprint); - } - else - { - var bytes = Encoders.Hex.DecodeData(mfpString); - var shouldReverseMfp = jobj["ColdCardFirmwareVersion"]?.Value() == "2.1.0"; - if (shouldReverseMfp) // Bug in previous version of coldcard - bytes = bytes.Reverse().ToArray(); - result.AccountKeySettings[0].RootFingerprint = new HDFingerprint(bytes); - } - } - - catch - { - return (null, "MasterFingerprint was not valid"); - } - } - - if (jobj["AccountKeyPath"]?.Value() is string accountKeyPath) - { - try - { - result.AccountKeySettings[0].AccountKeyPath = new KeyPath(accountKeyPath); - } - catch - { - return (null, "AccountKeyPath was not valid"); - } - } - - if (jobj["DerivationPath"]?.Value()?.ToLowerInvariant() is string derivationPath) - { - try - { - result.AccountKeySettings[0].AccountKeyPath = new KeyPath(derivationPath); - } - catch - { - return (null, "Derivation path was not valid"); - } - } - - if (jobj.ContainsKey("ColdCardFirmwareVersion")) - { - result.Source = "ColdCard"; - } - else if (jobj.ContainsKey("CoboVaultFirmwareVersion")) - { - result.Source = "CoboVault"; - } - else if (jobj.TryGetValue("Source", StringComparison.InvariantCultureIgnoreCase, out var source)) - { - result.Source = source.Value(); + result.AccountKeySettings[0].RootFingerprint = new HDFingerprint(fingerprint); } else - result.Source = "WasabiFile"; - - - return (result, null); + { + var bytes = Encoders.Hex.DecodeData(jobj.MasterFingerprint); + var shouldReverseMfp = jobj.ColdCardFirmwareVersion == "2.1.0"; + if (shouldReverseMfp) // Bug in previous version of coldcard + bytes = bytes.Reverse().ToArray(); + result.AccountKeySettings[0].RootFingerprint = new HDFingerprint(bytes); + } } - catch (Exception) + + if (jobj.AccountKeyPath is not null) + result.AccountKeySettings[0].AccountKeyPath = new KeyPath(jobj.AccountKeyPath); + + if (jobj.ColdCardFirmwareVersion is not null) { - return (null, null); + result.Source = "ColdCard"; } + else if (jobj.CoboVaultFirmwareVersion is not null) + { + result.Source = "CoboVault"; + } + else + result.Source = jobj.Source ?? "WasabiFile"; + derivationSchemeSettings = result; + return true; } }