mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-27 02:37:08 +01:00
Fix Output Descriptor parsing for WSH multisig case (#4402)
* Fix Output Descriptor parsing for WSH multisig case Reuse existing function for extracting from a multisig descriptor, instead of recursively parsing the inner output descriptor. The latter would run into invalid cases, because it'd be interpreted as bare multisig, which supports only up to three public keys. For further details see MetacoSA/NBitcoin#1151. * Add CanParseDerivationSchemes test
This commit is contained in:
parent
ad3c15df9b
commit
80a257e85f
2 changed files with 57 additions and 12 deletions
|
@ -645,6 +645,49 @@ namespace BTCPayServer.Tests
|
|||
Assert.Equal(4, tor.Services.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanParseDerivationSchemes()
|
||||
{
|
||||
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
|
||||
var parser = new DerivationSchemeParser(networkProvider.BTC);
|
||||
|
||||
// xpub
|
||||
var xpub = "xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw";
|
||||
DerivationStrategyBase strategyBase = parser.Parse(xpub);
|
||||
Assert.IsType<DirectDerivationStrategy>(strategyBase);
|
||||
Assert.True(((DirectDerivationStrategy)strategyBase).Segwit);
|
||||
Assert.Equal("tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS", strategyBase.ToString());
|
||||
|
||||
// Multisig
|
||||
var multisig = "wsh(sortedmulti(2,[62a7956f/84'/1'/0']tpubDDXgATYzdQkHHhZZCMcNJj8BGDENvzMVou5v9NdxiP4rxDLj33nS233dGFW4htpVZSJ6zds9eVqAV9RyRHHiKtwQKX8eR4n4KN3Dwmj7A3h/0/*,[11312aa2/84'/1'/0']tpubDC8a54NFtQtMQAZ97VhoU9V6jVTvi9w4Y5SaAXJSBYETKg3AoX5CCKndznhPWxJUBToPCpT44s86QbKdGpKAnSjcMTGW4kE6UQ8vpBjcybW/0/*,[8f71b834/84'/1'/0']tpubDChjnP9LXNrJp43biqjY7FH93wgRRNrNxB4Q8pH7PPRy8UPcH2S6V46WGVJ47zVGF7SyBJNCpnaogsFbsybVQckGtVhCkng3EtFn8qmxptS/0/*))";
|
||||
var expected = "2-of-tpubDDXgATYzdQkHHhZZCMcNJj8BGDENvzMVou5v9NdxiP4rxDLj33nS233dGFW4htpVZSJ6zds9eVqAV9RyRHHiKtwQKX8eR4n4KN3Dwmj7A3h-tpubDC8a54NFtQtMQAZ97VhoU9V6jVTvi9w4Y5SaAXJSBYETKg3AoX5CCKndznhPWxJUBToPCpT44s86QbKdGpKAnSjcMTGW4kE6UQ8vpBjcybW-tpubDChjnP9LXNrJp43biqjY7FH93wgRRNrNxB4Q8pH7PPRy8UPcH2S6V46WGVJ47zVGF7SyBJNCpnaogsFbsybVQckGtVhCkng3EtFn8qmxptS";
|
||||
(strategyBase, RootedKeyPath[] rootedKeyPath) = parser.ParseOutputDescriptor(multisig);
|
||||
Assert.Equal(3, rootedKeyPath.Length);
|
||||
Assert.IsType<P2WSHDerivationStrategy>(strategyBase);
|
||||
Assert.IsType<MultisigDerivationStrategy>(((P2WSHDerivationStrategy)strategyBase).Inner);
|
||||
Assert.Equal(expected, strategyBase.ToString());
|
||||
|
||||
var inner = (MultisigDerivationStrategy)((P2WSHDerivationStrategy)strategyBase).Inner;
|
||||
Assert.False(inner.IsLegacy);
|
||||
Assert.Equal(3, inner.Keys.Count);
|
||||
Assert.Equal(2, inner.RequiredSignatures);
|
||||
Assert.Equal(expected, inner.ToString());
|
||||
|
||||
// Output Descriptor
|
||||
networkProvider = new BTCPayNetworkProvider(ChainName.Mainnet);
|
||||
parser = new DerivationSchemeParser(networkProvider.BTC);
|
||||
var od = "wpkh([8bafd160/49h/0h/0h]xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw/0/*)#9x4vkw48";
|
||||
(strategyBase, rootedKeyPath) = parser.ParseOutputDescriptor(od);
|
||||
Assert.Equal(1, rootedKeyPath.Length);
|
||||
Assert.IsType<DirectDerivationStrategy>(strategyBase);
|
||||
Assert.True(((DirectDerivationStrategy)strategyBase).Segwit);
|
||||
|
||||
// Failure cases
|
||||
Assert.Throws<FormatException>(() => { parser.Parse("xpub 661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw"); }); // invalid format because of space
|
||||
Assert.Throws<ParsingException>(() => { parser.ParseOutputDescriptor("invalid"); }); // invalid in general
|
||||
Assert.Throws<ParsingException>(() => { parser.ParseOutputDescriptor("wpkh([8b60afd1/49h/0h/0h]xpub661MyMwAFXkMnyoBjyHndD3QwRbcGVBsTGeNZN6QGVHcfz4MPzBUxjSevweNFQx7SqmMHLdSA4FteGsRrEriu4pnVZMZWnruFFAYZATtcDw/0/*)#9x4vkw48"); }); // invalid checksum
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseDerivationSchemeSettings()
|
||||
{
|
||||
|
|
|
@ -33,7 +33,6 @@ namespace BTCPayServer
|
|||
{
|
||||
throw new FormatException("Custom change paths are not supported.");
|
||||
}
|
||||
|
||||
return (Parse($"{hd.Extkey}{suffix}"), null);
|
||||
case PubKeyProvider.Origin origin:
|
||||
var innerResult = ExtractFromPkProvider(origin.Inner, suffix);
|
||||
|
@ -42,7 +41,16 @@ namespace BTCPayServer
|
|||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
(DerivationStrategyBase, RootedKeyPath[]) ExtractFromMulti(OutputDescriptor.Multi multi)
|
||||
{
|
||||
var xpubs = multi.PkProviders.Select(provider => ExtractFromPkProvider(provider));
|
||||
return (
|
||||
Parse(
|
||||
$"{multi.Threshold}-of-{(string.Join('-', xpubs.Select(tuple => tuple.Item1.ToString())))}{(multi.IsSorted ? "" : "-[keeporder]")}"),
|
||||
xpubs.SelectMany(tuple => tuple.Item2).ToArray());
|
||||
}
|
||||
|
||||
ArgumentNullException.ThrowIfNull(str);
|
||||
str = str.Trim();
|
||||
var outputDescriptor = OutputDescriptor.Parse(str, Network);
|
||||
|
@ -55,11 +63,7 @@ namespace BTCPayServer
|
|||
case OutputDescriptor.Combo _:
|
||||
throw new FormatException("Only output descriptors of one format are supported.");
|
||||
case OutputDescriptor.Multi multi:
|
||||
var xpubs = multi.PkProviders.Select(provider => ExtractFromPkProvider(provider));
|
||||
return (
|
||||
Parse(
|
||||
$"{multi.Threshold}-of-{(string.Join('-', xpubs.Select(tuple => tuple.Item1.ToString())))}{(multi.IsSorted ? "" : "-[keeporder]")}"),
|
||||
xpubs.SelectMany(tuple => tuple.Item2).ToArray());
|
||||
return ExtractFromMulti(multi);
|
||||
case OutputDescriptor.PKH pkh:
|
||||
return ExtractFromPkProvider(pkh.PkProvider, "-[legacy]");
|
||||
case OutputDescriptor.SH sh:
|
||||
|
@ -79,11 +83,9 @@ namespace BTCPayServer
|
|||
throw new FormatException("sh descriptors are only supported with multsig(legacy or p2wsh) and segwit(p2wpkh)");
|
||||
case OutputDescriptor.WPKH wpkh:
|
||||
return ExtractFromPkProvider(wpkh.PkProvider, "");
|
||||
case OutputDescriptor.WSH wsh:
|
||||
if (wsh.Inner is OutputDescriptor.Multi)
|
||||
{
|
||||
return ParseOutputDescriptor(wsh.Inner.ToString());
|
||||
}
|
||||
case OutputDescriptor.WSH { Inner: OutputDescriptor.Multi multi }:
|
||||
return ExtractFromMulti(multi);
|
||||
case OutputDescriptor.WSH:
|
||||
throw new FormatException("wsh descriptors are only supported with multisig");
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(outputDescriptor));
|
||||
|
|
Loading…
Add table
Reference in a new issue