Support BIP129 Multisig wallet import (#5389)

This commit is contained in:
Andrew Camilleri 2023-12-01 10:54:13 +01:00 committed by GitHub
parent a97172cea6
commit 28265b30d2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 108 additions and 0 deletions

View file

@ -958,6 +958,40 @@ namespace BTCPayServer.Tests
Assert.Equal("49'/0'/0'", specter.AccountKeySettings[0].AccountKeyPath.ToString());
Assert.Equal("Specter", specter.Label);
Assert.Null(error);
//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/*
bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
";
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(bsms,
mainnet, out var nunchuk, out error));
Assert.Equal(2, nunchuk.AccountKeySettings.Length);
//check that the account key settings match those in bsms string
Assert.Equal("5c9e228d", nunchuk.AccountKeySettings[0].RootFingerprint.ToString());
Assert.Equal("48'/0'/0'/2'", nunchuk.AccountKeySettings[0].AccountKeyPath.ToString());
Assert.Equal("2b0e251e", nunchuk.AccountKeySettings[1].RootFingerprint.ToString());
Assert.Equal("48'/0'/0'/2'", nunchuk.AccountKeySettings[1].AccountKeyPath.ToString());
var multsig = Assert.IsType < MultisigDerivationStrategy >
(Assert.IsType<P2WSHDerivationStrategy>(nunchuk.AccountDerivation).Inner);
Assert.True(multsig.LexicographicOrder);
Assert.Equal(1, multsig.RequiredSignatures);
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
var line =nunchuk.AccountDerivation.GetLineFor(deposit).Derive(0);
Assert.Equal(BitcoinAddress.Create("bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku", Network.Main).ScriptPubKey,
line.ScriptPubKey);
Assert.Equal("BSMS", nunchuk.Source);
Assert.Null(error);
// Failure case
Assert.False(DerivationSchemeSettings.TryParseFromWalletFile(

View file

@ -121,6 +121,65 @@ namespace BTCPayServer
}
}
public static bool TryParseBSMSFile(string filecontent, DerivationSchemeParser derivationSchemeParser, ref DerivationSchemeSettings derivationSchemeSettings,
out string error)
{
error = null;
try
{
string[] lines = filecontent.Split(
new[] {"\r\n", "\r", "\n"},
StringSplitOptions.None
);
if (!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/*")
{
error = "BTCPay Server can only derive address to the deposit and change paths";
return false;
}
descriptor = descriptor.Replace("/**", derivationPath);
var testAddress = BitcoinAddress.Create( lines[3], derivationSchemeParser.Network);
var result = derivationSchemeParser.ParseOutputDescriptor(descriptor);
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
var line = result.Item1.GetLineFor(deposit).Derive(0);
if (testAddress.ScriptPubKey != line.ScriptPubKey)
{
error = "BSMS test address did not match our generated address";
return false;
}
derivationSchemeSettings.Source = "BSMS";
derivationSchemeSettings.AccountDerivation = result.Item1;
derivationSchemeSettings.AccountOriginal = descriptor.Trim();
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 e)
{
error = $"BSMS parse error: {e.Message}";
return false;
}
}
public static bool TryParseFromWalletFile(string fileContents, BTCPayNetwork network, out DerivationSchemeSettings settings, out string error)
{
settings = null;
@ -140,6 +199,17 @@ namespace BTCPayServer
}
catch
{
if (TryParseBSMSFile(fileContents, derivationSchemeParser,ref result, out var bsmsError))
{
settings = result;
settings.Network = network;
return true;
}
if (bsmsError is not null)
{
error = bsmsError;
return false;
}
result.Source = "GenericFile";
if (TryParseXpub(fileContents, derivationSchemeParser, ref result, ref error) ||
TryParseXpub(fileContents, derivationSchemeParser, ref result, ref error, false))

View file

@ -56,6 +56,10 @@
<tr>
<td>Passport</td>
<td>Wallet Connect Wallet BTCPay microSD Save wallet file</td>
</tr>
<tr>
<td>Nunchuk</td>
<td>... Export wallet configuration</td>
</tr>
</tbody>
</table>