Skip to content

Commit 0cb0e0a

Browse files
committed
WIP: Update output descriptor according to bitcoin core
1 parent cd063e3 commit 0cb0e0a

12 files changed

Lines changed: 1052 additions & 209 deletions

NBitcoin.Tests/Generators/CryptoGenerator.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,5 +97,23 @@ from raw in Gen.NonEmptyListOf(PrimitiveGenerator.RandomBytes(4))
9797
select NBitcoin.KeyPath.FromBytes(flattenBytes);
9898

9999
public static Gen<ExtPubKey> ExtPubKey() => ExtKey().Select(ek => ek.Neuter());
100+
public static Gen<BitcoinExtPubKey> BitcoinExtPubKey() =>
101+
from extKey in ExtPubKey()
102+
from network in ChainParamsGenerator.NetworkGen()
103+
select new BitcoinExtPubKey(extKey, network);
104+
105+
public static Gen<BitcoinExtKey> BitcoinExtKey() =>
106+
from extKey in ExtKey()
107+
from network in ChainParamsGenerator.NetworkGen()
108+
select new BitcoinExtKey(extKey, network);
109+
110+
public static Gen<RootedKeyPath> RootedKeyPath() =>
111+
from parentFingerPrint in HDFingerPrint()
112+
from kp in KeyPath()
113+
select new RootedKeyPath(parentFingerPrint, kp);
114+
115+
public static Gen<HDFingerprint> HDFingerPrint() =>
116+
from x in PrimitiveGenerator.UInt32()
117+
select new HDFingerprint(x);
100118
}
101119
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
using FsCheck;
2+
using NBitcoin.Scripting;
3+
4+
namespace NBitcoin.Tests.Generators
5+
{
6+
public class OutputDescriptorGenerator
7+
{
8+
public static Arbitrary<OutputDescriptor> OutputDescriptorArb() =>
9+
Arb.From(OutputDescriptorGen());
10+
11+
public static Gen<OutputDescriptor> OutputDescriptorGen() =>
12+
Gen.OneOf(
13+
AddrOutputDescriptorGen(),
14+
RawOutputDescriptorGen(),
15+
PKOutputDescriptorGen(),
16+
PKHOutputDescriptorGen(),
17+
WPKHOutputDescriptorGen(),
18+
ComboOutputDescriptorGen(),
19+
MultisigOutputDescriptorGen(),
20+
SHOutputDescriptorGen(),
21+
WSHOutputDescriptorGen()
22+
);
23+
private static Gen<OutputDescriptor> AddrOutputDescriptorGen() =>
24+
from addr in AddressGenerator.RandomAddress()
25+
select OutputDescriptor.NewAddr(addr);
26+
27+
private static Gen<OutputDescriptor> RawOutputDescriptorGen() =>
28+
from addr in ScriptGenerator.RandomScriptSig()
29+
select OutputDescriptor.NewRaw(addr);
30+
private static Gen<OutputDescriptor> PKOutputDescriptorGen() =>
31+
from pkProvider in PubKeyProviderGen()
32+
select OutputDescriptor.NewPK(pkProvider);
33+
34+
private static Gen<OutputDescriptor> PKHOutputDescriptorGen() =>
35+
from pkProvider in PubKeyProviderGen()
36+
select OutputDescriptor.NewPKH(pkProvider);
37+
38+
private static Gen<OutputDescriptor> WPKHOutputDescriptorGen() =>
39+
from pkProvider in PubKeyProviderGen()
40+
select OutputDescriptor.NewWPKH(pkProvider);
41+
42+
private static Gen<OutputDescriptor> ComboOutputDescriptorGen() =>
43+
from pkProvider in PubKeyProviderGen()
44+
select OutputDescriptor.NewCombo(pkProvider);
45+
46+
private static Gen<OutputDescriptor> MultisigOutputDescriptorGen() =>
47+
from pkProviders in Gen.NonEmptyListOf(PubKeyProviderGen())
48+
select OutputDescriptor.NewMulti(pkProviders);
49+
50+
private static Gen<OutputDescriptor> InnerOutputDescriptorGen() =>
51+
Gen.OneOf(
52+
PKOutputDescriptorGen(),
53+
PKHOutputDescriptorGen(),
54+
WPKHOutputDescriptorGen(),
55+
MultisigOutputDescriptorGen()
56+
);
57+
private static Gen<OutputDescriptor> SHOutputDescriptorGen() =>
58+
from inner in Gen.OneOf(InnerOutputDescriptorGen(), WSHOutputDescriptorGen())
59+
select OutputDescriptor.NewSH(inner);
60+
61+
private static Gen<OutputDescriptor> WSHOutputDescriptorGen() =>
62+
from inner in InnerOutputDescriptorGen()
63+
select OutputDescriptor.NewWSH(inner);
64+
65+
#region pubkey providers
66+
67+
private static Gen<PubKeyProvider> PubKeyProviderGen() =>
68+
Gen.OneOf(OriginPubKeyProviderGen(), ConstPubKeyProviderGen(), HDPubKeyProviderGen());
69+
70+
private static Gen<PubKeyProvider> OriginPubKeyProviderGen() =>
71+
from keyOrigin in CryptoGenerator.RootedKeyPath()
72+
from inner in Gen.OneOf(ConstPubKeyProviderGen(), HDPubKeyProviderGen())
73+
select PubKeyProvider.NewOrigin(keyOrigin, inner);
74+
75+
private static Gen<PubKeyProvider> ConstPubKeyProviderGen() =>
76+
from pk in CryptoGenerator.PublicKey()
77+
select PubKeyProvider.NewConst(pk);
78+
79+
private static Gen<PubKeyProvider> HDPubKeyProviderGen() =>
80+
from extPk in CryptoGenerator.BitcoinExtPubKey()
81+
from kp in CryptoGenerator.KeyPath()
82+
from t in Arb.Generate<PubKeyProvider.DeriveType>()
83+
select PubKeyProvider.NewHD(extPk, kp, t);
84+
85+
# endregion
86+
}
87+
}

NBitcoin.Tests/MiniscriptTests.cs

Lines changed: 0 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -291,59 +291,6 @@ public void ShouldDeserializeScriptOriginatesFromMiniscriptToOrigin(AbstractPoli
291291
public void ShouldDeserializeScriptOriginatesFromMiniscript(AbstractPolicy policy)
292292
=> DeserializationTestCore(policy, false);
293293

294-
# region OutputDescriptor
295-
296-
[Property]
297-
[Trait("PropertyTest", "BidirectionalConversion")]
298-
public void ScriptOutputDescriptorShouldConvertToStringBidirectionally(AbstractPolicy policy, OutputDescriptorType type)
299-
{
300-
if (type != OutputDescriptorType.P2ShWpkh && type != OutputDescriptorType.Pkh && type != OutputDescriptorType.Wpkh)
301-
{
302-
var od = new OutputDescriptor(Miniscript.FromPolicy(policy), type);
303-
Assert.Equal(
304-
od,
305-
OutputDescriptor.Parse(od.ToString())
306-
);
307-
}
308-
}
309-
310-
[Property]
311-
[Trait("PropertyTest", "BidirectionalConversion")]
312-
public void PubKeyOutputDescriptorShouldConvertToStringBidirectionally(PubKey pk, OutputDescriptorType type)
313-
{
314-
if (type == OutputDescriptorType.P2ShWpkh || type == OutputDescriptorType.Pkh || type == OutputDescriptorType.Wpkh)
315-
{
316-
var od = new OutputDescriptor(pk, type);
317-
Assert.Equal(
318-
od,
319-
OutputDescriptor.Parse(od.ToString())
320-
);
321-
}
322-
}
323-
324-
[Fact]
325-
[Trait("UnitTest", "UnitTest")]
326-
public void OutputDescriptorParserTests()
327-
{
328-
var testVectors = new string[] {
329-
"pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)",
330-
"pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)",
331-
"wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)",
332-
"sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))",
333-
"combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)",
334-
"sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))",
335-
"multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)",
336-
"sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))",
337-
"wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))",
338-
"sh(wsh(multi(1,03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8,03499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)))",
339-
"pk(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8)",
340-
"pkh(xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw/1'/2)",
341-
"pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)",
342-
"wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))"
343-
};
344-
}
345-
346-
# endregion
347294

348295
[Property]
349296
[Trait("PropertyTest", "Verification")]
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System.Collections.Generic;
2+
using FsCheck;
3+
using FsCheck.Xunit;
4+
using NBitcoin.Scripting;
5+
using NBitcoin.Tests.Generators;
6+
using Xunit;
7+
8+
namespace NBitcoin.Tests
9+
{
10+
public class OutputDescriptorTests
11+
{
12+
public OutputDescriptorTests()
13+
{
14+
Arb.Register<OutputDescriptorGenerator>();
15+
}
16+
17+
[Property]
18+
[Trait("PropertyTest", "BidirectionalConversion")]
19+
public void DescriptorShouldConvertToStringBidirectionally(OutputDescriptor desc)
20+
{
21+
}
22+
23+
24+
[Fact]
25+
[Trait("UnitTest", "UnitTest")]
26+
public void OutputDescriptorParserTests()
27+
{
28+
// https://github.com/bitcoin/bitcoin/blob/9b085f4863eaefde4bec0638f1cbc8509d6ee59a/doc/descriptors.md
29+
var testVectors = new string[] {
30+
"pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)",
31+
"pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)",
32+
"wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)",
33+
"sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))",
34+
"combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)",
35+
"sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))",
36+
"multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)",
37+
"sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))",
38+
"wsh(multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))",
39+
"sh(wsh(multi(1,03f28773c2d975288bc7d1d205c3748651b075fbc6610e58cddeeddf8f19405aa8,03499fdf9e895e719cfd64e67f07d38e3226aa7b63678949e6e49b241a60e823e4,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)))",
40+
"pk(xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8)",
41+
"pkh(xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw/1'/2)",
42+
"pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)",
43+
"wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*))",
44+
// same with above except hardend derivation
45+
"wsh(multi(1,xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/1/0/*,xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH/0/0/*'))"
46+
};
47+
}
48+
49+
[Fact]
50+
[Trait("Core", "Core")]
51+
public void DescriptorTests()
52+
{
53+
54+
}
55+
56+
private void Check(string prv, string pub, int flags, string[] script, HashSet<uint[]> paths = null)
57+
{
58+
OutputDescriptor.Parse(prv);
59+
OutputDescriptor.Parse(pub);
60+
}
61+
62+
}
63+
}

NBitcoin/BIP32/HDFingerPrint.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using NBitcoin.DataEncoders;
22
using System;
33
using System.Collections.Generic;
4+
using System.Linq;
45
using System.Text;
56

67
namespace NBitcoin
@@ -28,6 +29,7 @@ public static bool TryParse(string str, out HDFingerprint result)
2829
return true;
2930
}
3031

32+
3133
public HDFingerprint Parse(string str)
3234
{
3335
if (!TryParse(str, out var result))
@@ -46,6 +48,11 @@ public HDFingerprint(ReadOnlySpan<byte> bytes)
4648
}
4749
#endif
4850

51+
public static HDFingerprint FromKeyId(KeyId id)
52+
{
53+
return new HDFingerprint(id.ToBytes().Take(4).ToArray());
54+
}
55+
4956
public HDFingerprint(byte[] bytes, int index)
5057
{
5158
if (bytes == null)

NBitcoin/Scripting/MiniscriptDSLParsers.cs

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -62,21 +62,6 @@ private static string[] SafeSplit(string s)
6262
return items.ToArray();
6363
}
6464

65-
internal static Parser<char, T> TryConvert<T>(string str, Func<string, T> converter)
66-
{
67-
return i =>
68-
{
69-
try
70-
{
71-
return ParserResult<char, T>.Success(i, converter(str));
72-
}
73-
catch (FormatException)
74-
{
75-
return ParserResult<char, T>.Failure(i, $"Failed to parse {str}");
76-
}
77-
};
78-
}
79-
8065
internal static Parser<char, string> ExprP(string name)
8166
=>
8267
from identifier in Parse.String(name)
@@ -89,23 +74,23 @@ from x in ExprP(name)
8974
select SafeSplit(x);
9075

9176
private static readonly Parser<char, AbstractPolicy> PPubKeyExpr =
92-
from pk in ExprP("pk").Then(s => TryConvert(s, c => new PubKey(c)))
77+
from pk in ExprP("pk").Then(s => Parse.TryConvert(s, c => new PubKey(c)))
9378
select AbstractPolicy.NewCheckSig(pk);
9479

9580
private static readonly Parser<char, AbstractPolicy> PMultisigExpr =
9681
from contents in ExprPMany("multi")
97-
from m in TryConvert(contents.First(), UInt32.Parse)
82+
from m in Parse.TryConvert(contents.First(), UInt32.Parse)
9883
from pks in contents.Skip(1)
99-
.Select(pk => TryConvert(pk, c => new PubKey(c)))
84+
.Select(pk => Parse.TryConvert(pk, c => new PubKey(c)))
10085
.Sequence()
10186
select AbstractPolicy.NewMulti(m, pks.ToArray());
10287

10388
private static readonly Parser<char, AbstractPolicy> PHashExpr =
104-
from hash in ExprP("hash").Then(s => TryConvert(s, uint256.Parse))
89+
from hash in ExprP("hash").Then(s => Parse.TryConvert(s, uint256.Parse))
10590
select AbstractPolicy.NewHash(hash);
10691

10792
private static readonly Parser<char, AbstractPolicy> PTimeExpr =
108-
from t in ExprP("time").Then(s => TryConvert(s, UInt32.Parse))
93+
from t in ExprP("time").Then(s => Parse.TryConvert(s, UInt32.Parse))
10994
where t <= 65535
11095
select AbstractPolicy.NewTime(t);
11196

@@ -133,7 +118,7 @@ from _n in Parse.String("thres")
133118
from _left in Parse.Char('(')
134119
from numStr in Parse.Digit.AtLeastOnce().Text()
135120
from _sep in Parse.Char(',')
136-
from num in TryConvert(numStr, UInt32.Parse)
121+
from num in Parse.TryConvert(numStr, UInt32.Parse)
137122
from x in Parse
138123
.Ref(() => DSLParser)
139124
.DelimitedBy(Parse.Char(',').Token()).Token()

0 commit comments

Comments
 (0)