Support wasabi file format input (#1671)

* Support wasabi file format input

This adds support to importing the wasabi file format which seems to be used more for imports by Wasabi,ColdCard,BlueWallet & Cobo Vault (and way better than electrum anyway)

* fixes

* add test
This commit is contained in:
Andrew Camilleri
2020-06-22 08:39:29 +02:00
committed by GitHub
parent e751be21ea
commit 0dd1b668cd
6 changed files with 149 additions and 53 deletions

View File

@@ -1,9 +1,11 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Payments;
using NBitcoin;
using NBitcoin.DataEncoders;
using NBXplorer.DerivationStrategy;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -38,12 +40,12 @@ namespace BTCPayServer
return strategy != null;
}
private static bool TryParseXpub(string xpub, DerivationSchemeParser derivationSchemeParser, ref DerivationSchemeSettings derivationSchemeSettings)
private static bool TryParseXpub(string xpub, DerivationSchemeParser derivationSchemeParser, ref DerivationSchemeSettings derivationSchemeSettings, bool electrum = true)
{
try
{
derivationSchemeSettings.AccountOriginal = xpub.Trim();
derivationSchemeSettings.AccountDerivation = derivationSchemeParser.ParseElectrum(derivationSchemeSettings.AccountOriginal);
derivationSchemeSettings.AccountDerivation = electrum ? derivationSchemeParser.ParseElectrum(derivationSchemeSettings.AccountOriginal) : derivationSchemeParser.Parse(derivationSchemeSettings.AccountOriginal);
derivationSchemeSettings.AccountKeySettings = new AccountKeySettings[1];
derivationSchemeSettings.AccountKeySettings[0] = new AccountKeySettings();
derivationSchemeSettings.AccountKeySettings[0].AccountKey = derivationSchemeSettings.AccountDerivation.GetExtPubKeys().Single().GetWif(derivationSchemeParser.Network);
@@ -57,58 +59,122 @@ namespace BTCPayServer
}
}
public static bool TryParseFromElectrumWallet(string coldcardExport, BTCPayNetwork network, out DerivationSchemeSettings settings)
public static bool TryParseFromWalletFile(string fileContents, BTCPayNetwork network, out DerivationSchemeSettings settings)
{
settings = null;
if (coldcardExport == null)
throw new ArgumentNullException(nameof(coldcardExport));
if (fileContents == null)
throw new ArgumentNullException(nameof(fileContents));
if (network == null)
throw new ArgumentNullException(nameof(network));
var result = new DerivationSchemeSettings();
result.Source = "Electrum/Airgap hardware wallet";
var derivationSchemeParser = new DerivationSchemeParser(network);
JObject jobj = null;
try
{
jobj = JObject.Parse(coldcardExport);
jobj = (JObject)jobj["keystore"];
jobj = JObject.Parse(fileContents);
}
catch
{
return TryParseXpub(coldcardExport, derivationSchemeParser, ref result);
result.Source = "GenericFile";
return TryParseXpub(fileContents, derivationSchemeParser, ref result);
}
if (!jobj.ContainsKey("xpub") ||
!TryParseXpub(jobj["xpub"].Value<string>(), derivationSchemeParser, ref result))
//electrum
if (jobj.ContainsKey("keystore"))
{
return false;
}
if (jobj.ContainsKey("label"))
{
try
result.Source = "ElectrumFile";
jobj = (JObject)jobj["keystore"];
if (!jobj.ContainsKey("xpub") ||
!TryParseXpub(jobj["xpub"].Value<string>(), derivationSchemeParser, ref result))
{
result.Label = jobj["label"].Value<string>();
return false;
}
catch { return false; }
}
if (jobj.ContainsKey("ckcc_xfp"))
{
try
if (jobj.ContainsKey("label"))
{
result.AccountKeySettings[0].RootFingerprint = new HDFingerprint(jobj["ckcc_xfp"].Value<uint>());
try
{
result.Label = jobj["label"].Value<string>();
}
catch { return false; }
}
catch { return false; }
}
if (jobj.ContainsKey("derivation"))
{
try
if (jobj.ContainsKey("ckcc_xfp"))
{
result.AccountKeySettings[0].AccountKeyPath = new KeyPath(jobj["derivation"].Value<string>());
try
{
result.AccountKeySettings[0].RootFingerprint = new HDFingerprint(jobj["ckcc_xfp"].Value<uint>());
}
catch { return false; }
}
if (jobj.ContainsKey("derivation"))
{
try
{
result.AccountKeySettings[0].AccountKeyPath = new KeyPath(jobj["derivation"].Value<string>());
}
catch { return false; }
}
}
else
{
result.Source = "WasabiFile";
//wasabi format
if (!jobj.ContainsKey("ExtPubKey") ||
!TryParseXpub(jobj["ExtPubKey"].Value<string>(), derivationSchemeParser, ref result, false))
{
return false;
}
if (jobj.ContainsKey("MasterFingerprint"))
{
try
{
var mfpString = jobj["MasterFingerprint"].ToString().Trim();
// 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 shouldReverseMfp = jobj.ContainsKey("ColdCardFirmwareVersion") &&
jobj["ColdCardFirmwareVersion"].ToString() == "2.1.0";
var bytes = Encoders.Hex.DecodeData(mfpString);
result.AccountKeySettings[0].RootFingerprint = shouldReverseMfp ? new HDFingerprint(bytes.Reverse().ToArray()) : new HDFingerprint(bytes);
}
}
catch { return false; }
}
if (jobj.ContainsKey("AccountKeyPath"))
{
try
{
result.AccountKeySettings[0].AccountKeyPath = new KeyPath(jobj["AccountKeyPath"].Value<string>());
}
catch { return false; }
}
if (jobj.ContainsKey("DerivationPath"))
{
try
{
result.AccountKeySettings[0].AccountKeyPath = new KeyPath(jobj["DerivationPath"].Value<string>().ToLowerInvariant());
}
catch { return false; }
}
if (jobj.ContainsKey("ColdCardFirmwareVersion"))
{
result.Source = "ColdCard";
}
if (jobj.ContainsKey("CoboVaultFirmwareVersion"))
{
result.Source = "CoboVault";
}
catch { return false; }
}
settings = result;
settings.Network = network;