mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2026-02-02 21:04:25 +01:00
Derivation scheme parsing incorporates fingerprint and key path (#4781)
This commit is contained in:
@@ -709,19 +709,41 @@ namespace BTCPayServer.Tests
|
||||
[Fact]
|
||||
public void ParseDerivationSchemeSettings()
|
||||
{
|
||||
var testnet = new BTCPayNetworkProvider(ChainName.Testnet).GetNetwork<BTCPayNetwork>("BTC");
|
||||
var mainnet = new BTCPayNetworkProvider(ChainName.Mainnet).GetNetwork<BTCPayNetwork>("BTC");
|
||||
var root = new Mnemonic(
|
||||
"usage fever hen zero slide mammal silent heavy donate budget pulse say brain thank sausage brand craft about save attract muffin advance illegal cabbage")
|
||||
.DeriveExtKey();
|
||||
|
||||
// xpub
|
||||
var tpub = "tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS";
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(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());
|
||||
|
||||
// xpub with fingerprint and account
|
||||
tpub = "tpubDCXK98mNrPWuoWweaoUkqwxQF5NMWpQLy7n7XJgDCpwYfoZRXGafPaVM7mYqD7UKhsbMxkN864JY2PniMkt1Uk4dNuAMnWFVqdquyvZNyca";
|
||||
var vpub = "vpub5YVA1ZbrqkUVq8NZTtvRDrS2a1yoeBvHbG9NbxqJ6uRtpKGFwjQT11WEqKYsgoDF6gpqrDf8ddmPZe4yXWCjzqF8ad2Cw9xHiE8DSi3X3ik";
|
||||
var fingerprint = "e5746fd9";
|
||||
var account = "84'/1'/0'";
|
||||
var str = $"[{fingerprint}/{account}]{vpub}";
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(str, testnet, out settings, out error));
|
||||
Assert.Null(error);
|
||||
Assert.True(settings.AccountDerivation is DirectDerivationStrategy { Segwit: true });
|
||||
Assert.Equal(vpub, settings.AccountOriginal);
|
||||
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());
|
||||
|
||||
// ColdCard
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
|
||||
"{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}",
|
||||
mainnet, out var settings, out var error));
|
||||
mainnet, out settings, out error));
|
||||
Assert.Null(error);
|
||||
Assert.Equal(root.GetPublicKey().GetHDFingerPrint(), settings.AccountKeySettings[0].RootFingerprint);
|
||||
Assert.Equal(settings.AccountKeySettings[0].RootFingerprint,
|
||||
HDFingerprint.TryParse("8bafd160", out var hd) ? hd : default);
|
||||
HDFingerprint.TryParse("8bafd160", out hd) ? hd : default);
|
||||
Assert.Equal("Coldcard Import 0x60d1af8b", settings.Label);
|
||||
Assert.Equal("49'/0'/0'", settings.AccountKeySettings[0].AccountKeyPath.ToString());
|
||||
Assert.Equal(
|
||||
@@ -729,28 +751,26 @@ 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);
|
||||
var testnet = new BTCPayNetworkProvider(ChainName.Testnet).GetNetwork<BTCPayNetwork>("BTC");
|
||||
|
||||
// Should be legacy
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
|
||||
"{\"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 s && !s.Segwit);
|
||||
Assert.True(settings.AccountDerivation is DirectDerivationStrategy { Segwit: false });
|
||||
Assert.Null(error);
|
||||
|
||||
// Should be segwit p2sh
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
|
||||
"{\"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 p &&
|
||||
p.Inner is DirectDerivationStrategy s2 && s2.Segwit);
|
||||
Assert.True(settings.AccountDerivation is P2SHDerivationStrategy { Inner: DirectDerivationStrategy { Segwit: true } });
|
||||
Assert.Null(error);
|
||||
|
||||
// Should be segwit
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
|
||||
"{\"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 s3 && s3.Segwit);
|
||||
Assert.True(settings.AccountDerivation is DirectDerivationStrategy { Segwit: true });
|
||||
Assert.Null(error);
|
||||
|
||||
// Specter
|
||||
|
||||
@@ -49,6 +49,7 @@ namespace BTCPayServer
|
||||
{
|
||||
return AccountDerivation is null ? null : DBUtils.nbxv1_get_wallet_id(Network.CryptoCode, AccountDerivation.ToString());
|
||||
}
|
||||
|
||||
private static bool TryParseXpub(string xpub, DerivationSchemeParser derivationSchemeParser, ref DerivationSchemeSettings derivationSchemeSettings, ref string error, bool electrum = true)
|
||||
{
|
||||
if (!electrum)
|
||||
@@ -78,15 +79,36 @@ namespace BTCPayServer
|
||||
}
|
||||
try
|
||||
{
|
||||
// Extract fingerprint and account key path from export formats that contain them.
|
||||
// Possible formats: [fingerprint/account_key_path]xpub, [fingerprint]xpub, xpub
|
||||
HDFingerprint? rootFingerprint = null;
|
||||
KeyPath accountKeyPath = null;
|
||||
var derivationRegex = new Regex(@"^(?:\[(\w+)(?:\/(.*?))?\])?(\w+)$", RegexOptions.IgnoreCase);
|
||||
var match = derivationRegex.Match(xpub.Trim());
|
||||
if (match.Success)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(match.Groups[1].Value)) rootFingerprint = HDFingerprint.Parse(match.Groups[1].Value);
|
||||
if (!string.IsNullOrEmpty(match.Groups[2].Value)) accountKeyPath = KeyPath.Parse(match.Groups[2].Value);
|
||||
if (!string.IsNullOrEmpty(match.Groups[3].Value)) xpub = match.Groups[3].Value;
|
||||
}
|
||||
derivationSchemeSettings.AccountOriginal = xpub.Trim();
|
||||
derivationSchemeSettings.AccountDerivation = electrum ? derivationSchemeParser.ParseElectrum(derivationSchemeSettings.AccountOriginal) : derivationSchemeParser.Parse(derivationSchemeSettings.AccountOriginal);
|
||||
derivationSchemeSettings.AccountKeySettings = derivationSchemeSettings.AccountDerivation.GetExtPubKeys()
|
||||
.Select(key => new AccountKeySettings()
|
||||
.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
|
||||
// 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;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
|
||||
@@ -96,8 +96,6 @@
|
||||
} else if (typeof(data) === 'string') {
|
||||
xpub = data;
|
||||
}
|
||||
// remove potentially leading derivation path
|
||||
xpub = xpub.replace(/^\[.*?\]/, '');
|
||||
// submit
|
||||
document.getElementById("WalletFileContent").value = xpub;
|
||||
document.getElementById("qr-import-form").submit();
|
||||
|
||||
Reference in New Issue
Block a user