Skip to content

Commit e3ad850

Browse files
KukksNicolasDorier
andauthored
Implement BIP322 (MetacoSA#1224)
* Implement BIP322 * Fix various warnings * Improve signature parsing * Rely on PSBT for signing * Implement proof of fund * Parse signature properly * Remove useless parameter * Remove useless code path * Simplify a bit --------- Co-authored-by: nicolas.dorier <[email protected]>
1 parent cbbb505 commit e3ad850

13 files changed

+822
-10
lines changed

NBitcoin.Tests/BIP322Tests.cs

+287
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
#if HAS_SPAN
2+
using NBitcoin.BIP322;
3+
using System;
4+
using System.Threading.Tasks;
5+
using Xunit;
6+
7+
namespace NBitcoin.Tests
8+
{
9+
[Trait("UnitTest", "UnitTest")]
10+
public class BIP322Tests
11+
{
12+
//from https://github.com/ACken2/bip322-js/tree/main/test && https://github.com/bitcoin/bitcoin/pull/24058/files#diff-2bd57d7fbec4bb262834d155c304ebe15d26f73fea87c75ff273df3529a15510
13+
[Fact]
14+
public void CanHandleLegacyBIP322Message()
15+
{
16+
var address = BitcoinAddress.Create("1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV", Network.Main);
17+
var addressTestnet = BitcoinAddress.Create("muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi", Network.TestNet);
18+
var addressRegtest = BitcoinAddress.Create("muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi", Network.RegTest);
19+
var addressWrong = BitcoinAddress.Create("1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2", Network.Main);
20+
var addressWrongTestnet = BitcoinAddress.Create("mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn", Network.TestNet);
21+
var addressWrongRegtest = BitcoinAddress.Create("mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn", Network.RegTest);
22+
var message = "This is an example of a signed message.";
23+
var messageWrong = "";
24+
var signature = BIP322.BIP322Signature.Parse("H9L5yLFjti0QTHhPyFrZCT1V/MMnBtXKmoiKDZ78NDBjERki6ZTQZdSMCtkgoNmp17By9ItJr8o7ChX0XxY91nk=", address.Network);
25+
Assert.True(address.VerifyBIP322(message, signature));
26+
Assert.True(addressTestnet.VerifyBIP322(message, signature));
27+
Assert.True(addressRegtest.VerifyBIP322(message, signature));
28+
Assert.False(address.VerifyBIP322(messageWrong, signature));
29+
Assert.False(addressTestnet.VerifyBIP322(messageWrong, signature));
30+
Assert.False(addressRegtest.VerifyBIP322(messageWrong, signature));
31+
Assert.False(addressWrong.VerifyBIP322(message, signature));
32+
Assert.False(addressWrongTestnet.VerifyBIP322(message, signature));
33+
Assert.False(addressWrongRegtest.VerifyBIP322(message, signature));
34+
35+
36+
var privateKey = new BitcoinSecret("L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k", Network.Main)
37+
.PrivateKey;
38+
address = privateKey.GetAddress(ScriptPubKeyType.Legacy, Network.Main);
39+
addressTestnet = privateKey.GetAddress(ScriptPubKeyType.Legacy, Network.TestNet);
40+
addressRegtest = privateKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest);
41+
message = "Hello World";
42+
signature = privateKey.SignBIP322(address, message, SignatureType.Legacy);
43+
var signatureTestnet =
44+
privateKey.SignBIP322(addressTestnet, message, SignatureType.Legacy);
45+
var signatureRegtest =
46+
privateKey.SignBIP322(addressRegtest, message, SignatureType.Legacy);
47+
48+
Assert.True(address.VerifyBIP322(message, signature));
49+
Assert.True(addressTestnet.VerifyBIP322(message, signatureTestnet));
50+
Assert.True(addressRegtest.VerifyBIP322(message, signatureRegtest));
51+
}
52+
53+
[Fact]
54+
public void CanSign()
55+
{
56+
var k = new Key();
57+
var p = k.GetAddress(ScriptPubKeyType.SegwitP2SH, Network.Main);
58+
59+
var privateKey = new BitcoinSecret("KwTbAxmBXjoZM3bzbXixEr9nxLhyYSM4vp2swet58i19bw9sqk5z", Network.Main)
60+
.PrivateKey; // Private key of "3HSVzEhCFuH9Z3wvoWTexy7BMVVp3PjS6f"
61+
var privateKeyTestnet =
62+
new BitcoinSecret("cMpadsm2xoVpWV5FywY5cAeraa1PCtSkzrBM45Ladpf9rgDu6cMz", Network.TestNet)
63+
.PrivateKey; // Equivalent to "KwTbAxmBXjoZM3bzbXixEr9nxLhyYSM4vp2swet58i19bw9sqk5z"
64+
var address = BitcoinAddress.Create("3HSVzEhCFuH9Z3wvoWTexy7BMVVp3PjS6f", Network.Main);
65+
var addressTestnet = BitcoinAddress.Create("2N8zi3ydDsMnVkqaUUe5Xav6SZqhyqEduap", Network.TestNet);
66+
var addressRegtest = BitcoinAddress.Create("2N8zi3ydDsMnVkqaUUe5Xav6SZqhyqEduap", Network.RegTest);
67+
var message = "Hello World";
68+
69+
var signature = privateKey.SignBIP322(address, message, SignatureType.Simple);
70+
var signatureTestnet =
71+
privateKeyTestnet.SignBIP322(addressTestnet, message, SignatureType.Simple);
72+
var signatureRegtest =
73+
privateKeyTestnet.SignBIP322(addressRegtest, message, SignatureType.Simple);
74+
75+
Assert.Equal(signatureTestnet, signature);
76+
Assert.Equal(signatureRegtest, signature);
77+
78+
var k1 = new BitcoinSecret("L4DksdGZ4KQJfcLHD5Dv25fu8Rxyv7hHi2RjZR4TYzr8c6h9VNrp", Network.Main).PrivateKey;
79+
var k2 = new BitcoinSecret("KzSRqnCVwjzY8id2X5oHEJWXkSHwKUYaAXusjwgkES8BuQPJnPNu", Network.Main).PrivateKey;
80+
var k3 = new BitcoinSecret("L1zt9Rw7HrU7jaguMbVzhiX8ffuVkmMis5wLHddXYuHWYf8u8uRj", Network.Main).PrivateKey;
81+
82+
83+
var redeem =
84+
PayToMultiSigTemplate.Instance.GenerateScriptPubKey(2, true,
85+
new PubKey[] {k1.PubKey, k2.PubKey, k3.PubKey});
86+
var p2wshScriptPubKey = redeem.WitHash.ScriptPubKey;
87+
var p2ShAddressScriptPubKey = redeem.Hash.ScriptPubKey;
88+
var p2shAddress = p2ShAddressScriptPubKey.GetDestinationAddress(Network.Main);
89+
var p2wshAddress = p2wshScriptPubKey.GetDestinationAddress(Network.Main);
90+
91+
92+
93+
var message2of3 = "This will be a 2-of-3 multisig BIP 322 signed message";
94+
95+
var toSign = p2shAddress.CreateBIP322PSBT(message2of3);
96+
toSign.AddScripts(redeem);
97+
toSign.SignWithKeys(k2, k3);
98+
99+
Assert.Throws<ArgumentException>(() =>
100+
{
101+
BIP322Signature.FromPSBT(toSign, SignatureType.Simple);
102+
});
103+
Assert.Throws<ArgumentException>(() =>
104+
{
105+
BIP322Signature.FromPSBT(toSign, SignatureType.Legacy);
106+
});
107+
108+
var p2sh_signature2of3_k2_k3 = BIP322Signature.FromPSBT(toSign, SignatureType.Full);
109+
110+
111+
toSign = p2shAddress.CreateBIP322PSBT(message2of3);
112+
toSign.AddScripts(redeem);
113+
toSign.SignWithKeys(k1, k3);
114+
var p2sh_signature2of3_k1_k3 = BIP322Signature.FromPSBT(toSign, SignatureType.Full);
115+
116+
Assert.Throws<PSBTException>(() =>
117+
{
118+
toSign = p2shAddress.CreateBIP322PSBT(message2of3);
119+
toSign.AddScripts(redeem);
120+
toSign.SignWithKeys(k1);
121+
// Missing one sig out of 3
122+
BIP322Signature.FromPSBT(toSign, SignatureType.Full);
123+
});
124+
125+
Assert.True(p2shAddress.VerifyBIP322(message2of3, p2sh_signature2of3_k2_k3));
126+
Assert.True(p2shAddress.VerifyBIP322(message2of3, p2sh_signature2of3_k1_k3));
127+
128+
toSign = p2wshAddress.CreateBIP322PSBT(message2of3);
129+
toSign.AddScripts(redeem);
130+
toSign.SignWithKeys(k2, k3);
131+
var p2wsh_signature2of3_k2_k3 = BIP322Signature.FromPSBT(toSign, SignatureType.Simple);
132+
133+
toSign = p2wshAddress.CreateBIP322PSBT(message2of3);
134+
toSign.AddScripts(redeem);
135+
toSign.SignWithKeys(k1, k3);
136+
var p2wsh_signature2of3_k1_k3 = BIP322Signature.FromPSBT(toSign, SignatureType.Simple);
137+
138+
Assert.True(p2wshAddress.VerifyBIP322(message2of3, p2wsh_signature2of3_k2_k3));
139+
Assert.True(p2wshAddress.VerifyBIP322(message2of3, p2wsh_signature2of3_k1_k3));
140+
141+
p2wsh_signature2of3_k1_k3 = BIP322Signature.FromPSBT(toSign, SignatureType.Full);
142+
Assert.True(p2wshAddress.VerifyBIP322(message2of3, p2wsh_signature2of3_k1_k3));
143+
144+
Assert.Throws<PSBTException>(() =>
145+
{
146+
toSign = p2wshAddress.CreateBIP322PSBT(message2of3);
147+
toSign.AddScripts(redeem);
148+
toSign.SignWithKeys(k1);
149+
BIP322Signature.FromPSBT(toSign, SignatureType.Simple);
150+
});
151+
}
152+
153+
154+
[Fact]
155+
public void CanVerifyBIP322Message()
156+
{
157+
var key = new BitcoinSecret("L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k", Network.Main)
158+
.PrivateKey;
159+
160+
var segwitAddress = key.GetAddress(ScriptPubKeyType.Segwit, Network.Main);
161+
Assert.Equal("bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l", segwitAddress.ToString());
162+
163+
var emptyStringPSBT = segwitAddress.CreateBIP322PSBT("");
164+
var helloWorldToSpendPSBT = segwitAddress.CreateBIP322PSBT("Hello World");
165+
166+
Assert.Equal("c5680aa69bb8d860bf82d4e9cd3504b55dde018de765a91bb566283c545a99a7",
167+
emptyStringPSBT.Inputs[0].NonWitnessUtxo.GetHash().ToString());
168+
Assert.Equal("b79d196740ad5217771c1098fc4a4b51e0535c32236c71f1ea4d61a2d603352b",
169+
helloWorldToSpendPSBT.Inputs[0].NonWitnessUtxo.GetHash().ToString());
170+
171+
Assert.Equal("1e9654e951a5ba44c8604c4de6c67fd78a27e81dcadcfe1edf638ba3aaebaed6",
172+
emptyStringPSBT.GetGlobalTransaction().GetHash().ToString());
173+
Assert.Equal("88737ae86f2077145f93cc4b153ae9a1cb8d56afa511988c149c5c8c9d93bddf",
174+
helloWorldToSpendPSBT.GetGlobalTransaction().GetHash().ToString());
175+
176+
var simpleHelloWorldSignature = key.SignBIP322(segwitAddress, "Hello World", SignatureType.Simple);
177+
var simpleEmptySignature = key.SignBIP322(segwitAddress, "", SignatureType.Simple);
178+
179+
var fullHelloWorldSignature = key.SignBIP322(segwitAddress, "Hello World", SignatureType.Full);
180+
var fullEmptySignature = key.SignBIP322(segwitAddress, "", SignatureType.Full);
181+
182+
183+
Assert.True(segwitAddress.VerifyBIP322("Hello World", simpleHelloWorldSignature));
184+
Assert.True(segwitAddress.VerifyBIP322("", simpleEmptySignature));
185+
Assert.True(segwitAddress.VerifyBIP322("Hello World", fullHelloWorldSignature));
186+
Assert.True(segwitAddress.VerifyBIP322("", fullEmptySignature));
187+
188+
Assert.False(segwitAddress.VerifyBIP322("nuhuh", simpleHelloWorldSignature));
189+
Assert.False(segwitAddress.VerifyBIP322("nuhuh", simpleEmptySignature));
190+
Assert.False(segwitAddress.VerifyBIP322("nuhuh", fullHelloWorldSignature));
191+
Assert.False(segwitAddress.VerifyBIP322("nuhuh", fullEmptySignature));
192+
193+
foreach (var t in new[]
194+
{
195+
("", "AkcwRAIgM2gBAQqvZX15ZiysmKmQpDrG83avLIT492QBzLnQIxYCIBaTpOaD20qRlEylyxFSeEA2ba9YOixpX8z46TSDtS40ASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI="),
196+
("", "AkgwRQIhAPkJ1Q4oYS0htvyuSFHLxRQpFAY56b70UvE7Dxazen0ZAiAtZfFz1S6T6I23MWI2lK/pcNTWncuyL8UL+oMdydVgzAEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy"),
197+
("Hello World", "AkcwRAIgZRfIY3p7/DoVTty6YZbWS71bc5Vct9p9Fia83eRmw2QCICK/ENGfwLtptFluMGs2KsqoNSk89pO7F29zJLUx9a/sASECx/EgAxlkQpQ9hYjgGu6EBCPMVPwVIVJqO4XCsMvViHI="),
198+
("Hello World", "AkgwRQIhAOzyynlqt93lOKJr+wmmxIens//zPzl9tqIOua93wO6MAiBi5n5EyAcPScOjf1lAqIUIQtr3zKNeavYabHyR8eGhowEhAsfxIAMZZEKUPYWI4BruhAQjzFT8FSFSajuFwrDL1Yhy")
199+
})
200+
{
201+
(var message, var sig) = t;
202+
Assert.True(segwitAddress.VerifyBIP322(message, sig));
203+
}
204+
205+
// // 2-of-3 p2sh multisig BIP322 signature (created with the buidl-python library)
206+
// // Keys are defined as (HDRootWIF, bip322_path)
207+
// // Key1 (L4DksdGZ4KQJfcLHD5Dv25fu8Rxyv7hHi2RjZR4TYzr8c6h9VNrp, m/45'/0/0/1)
208+
// // Key2 (KzSRqnCVwjzY8id2X5oHEJWXkSHwKUYaAXusjwgkES8BuQPJnPNu, m/45'/0/0/3)
209+
// // Key3 (L1zt9Rw7HrU7jaguMbVzhiX8ffuVkmMis5wLHddXYuHWYf8u8uRj, m/45'/0/0/6)
210+
// // BIP322 includes signs from Key2 and Key3
211+
// BOOST_CHECK_EQUAL(
212+
// MessageVerify(
213+
// "3LnYoUkFrhyYP3V7rq3mhpwALz1XbCY9Uq",
214+
// "AAAAAAHNcfHaNfl8f/+ZC2gTr8aF+0KgppYjKM94egaNm/u1ZAAAAAD8AEcwRAIhAJ6hdj61vLDP+aFa30qUZQmrbBfE0kiOObYvt5nqPSxsAh9IrOKFwflfPRUcQ/5e0REkdFHVP2GGdUsMgDet+sNlAUcwRAIgH3eW/VyFDoXvCasd8qxgwj5NDVo0weXvM6qyGXLCR5YCIEwjbEV6fS6RWP6QsKOcMwvlGr1/SgdCC6pW4eH87/YgAUxpUiECKJfGy28imLcuAeNBLHCNv3NRP5jnJwFDNRXCYNY/vJ4hAv1RQtaZs7+vKqQeWl2rb/jd/gMxkEjUnjZdDGPDZkMLIQL65cH2X5O7LujjTLDL2l8Pxy0Y2UUR99u1qCfjdz7dklOuAAAAAAEAAAAAAAAAAAFqAAAAAA==",
215+
// "This will be a p2sh 2-of-3 multisig BIP 322 signed message"),
216+
// MessageVerificationResult::OK);
217+
Assert.True(BitcoinAddress.Create("3LnYoUkFrhyYP3V7rq3mhpwALz1XbCY9Uq", Network.Main).VerifyBIP322("This will be a p2sh 2-of-3 multisig BIP 322 signed message",
218+
"AAAAAAHNcfHaNfl8f/+ZC2gTr8aF+0KgppYjKM94egaNm/u1ZAAAAAD8AEcwRAIhAJ6hdj61vLDP+aFa30qUZQmrbBfE0kiOObYvt5nqPSxsAh9IrOKFwflfPRUcQ/5e0REkdFHVP2GGdUsMgDet+sNlAUcwRAIgH3eW/VyFDoXvCasd8qxgwj5NDVo0weXvM6qyGXLCR5YCIEwjbEV6fS6RWP6QsKOcMwvlGr1/SgdCC6pW4eH87/YgAUxpUiECKJfGy28imLcuAeNBLHCNv3NRP5jnJwFDNRXCYNY/vJ4hAv1RQtaZs7+vKqQeWl2rb/jd/gMxkEjUnjZdDGPDZkMLIQL65cH2X5O7LujjTLDL2l8Pxy0Y2UUR99u1qCfjdz7dklOuAAAAAAEAAAAAAAAAAAFqAAAAAA=="));
219+
220+
// // 3-of-3 p2wsh multisig BIP322 signature (created with the buidl-python library)
221+
// // Keys are defined as (HDRootWIF, bip322_path)
222+
// // Key1 (L4DksdGZ4KQJfcLHD5Dv25fu8Rxyv7hHi2RjZR4TYzr8c6h9VNrp, m/45'/0/0/6)
223+
// // Key2 (KzSRqnCVwjzY8id2X5oHEJWXkSHwKUYaAXusjwgkES8BuQPJnPNu, m/45'/0/0/9)
224+
// // Key3 (L1zt9Rw7HrU7jaguMbVzhiX8ffuVkmMis5wLHddXYuHWYf8u8uRj, m/45'/0/0/11)
225+
// BOOST_CHECK_EQUAL(
226+
// MessageVerify(
227+
// "bc1qlqtuzpmazp2xmcutlwv0qvggdvem8vahkc333usey4gskug8nutsz53msw", "BQBIMEUCIQDQoXvGKLH58exuujBOta+7+GN7vi0lKwiQxzBpuNuXuAIgIE0XYQlFDOfxbegGYYzlf+tqegleAKE6SXYIa1U+uCcBRzBEAiATegywVl6GWrG9jJuPpNwtgHKyVYCX2yfuSSDRFATAaQIgTLlU6reLQsSIrQSF21z3PtUO2yAUseUWGZqRUIE7VKoBSDBFAiEAgxtpidsU0Z4u/+5RB9cyeQtoCW5NcreLJmWXZ8kXCZMCIBR1sXoEinhZE4CF9P9STGIcMvCuZjY6F5F0XTVLj9SjAWlTIQP3dyWvTZjUENWJowMWBsQrrXCUs20Gu5YF79CG5Ga0XSEDwqI5GVBOuFkFzQOGH5eTExSAj2Z/LDV/hbcvAPQdlJMhA17FuuJd+4wGuj+ZbVxEsFapTKAOwyhfw9qpch52JKxbU64=",
228+
// "This will be a p2wsh 3-of-3 multisig BIP 322 signed message"),
229+
// MessageVerificationResult::OK);
230+
Assert.True(BitcoinAddress.Create("bc1qlqtuzpmazp2xmcutlwv0qvggdvem8vahkc333usey4gskug8nutsz53msw", Network.Main).VerifyBIP322("This will be a p2wsh 3-of-3 multisig BIP 322 signed message",
231+
"BQBIMEUCIQDQoXvGKLH58exuujBOta+7+GN7vi0lKwiQxzBpuNuXuAIgIE0XYQlFDOfxbegGYYzlf+tqegleAKE6SXYIa1U+uCcBRzBEAiATegywVl6GWrG9jJuPpNwtgHKyVYCX2yfuSSDRFATAaQIgTLlU6reLQsSIrQSF21z3PtUO2yAUseUWGZqRUIE7VKoBSDBFAiEAgxtpidsU0Z4u/+5RB9cyeQtoCW5NcreLJmWXZ8kXCZMCIBR1sXoEinhZE4CF9P9STGIcMvCuZjY6F5F0XTVLj9SjAWlTIQP3dyWvTZjUENWJowMWBsQrrXCUs20Gu5YF79CG5Ga0XSEDwqI5GVBOuFkFzQOGH5eTExSAj2Z/LDV/hbcvAPQdlJMhA17FuuJd+4wGuj+ZbVxEsFapTKAOwyhfw9qpch52JKxbU64="));
232+
233+
// // Single key p2tr BIP322 signature (created with the buidl-python library)
234+
// // PrivateKeyWIF L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k
235+
// BOOST_CHECK_EQUAL(
236+
// MessageVerify(
237+
// "bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3",
238+
// "AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ==",
239+
// "Hello World"),
240+
// MessageVerificationResult::OK);
241+
Assert.True(BitcoinAddress.Create("bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3", Network.Main).VerifyBIP322("Hello World",
242+
"AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ=="));
243+
}
244+
245+
246+
[Fact]
247+
public void CanDoProofOfFunds()
248+
{
249+
var key = new Key();
250+
var key2 = new Key();
251+
var addr = key.GetAddress(ScriptPubKeyType.Segwit, Network.Main);
252+
var addr2 = key2.GetAddress(ScriptPubKeyType.Segwit, Network.Main);
253+
var addr3 = key2.GetAddress(ScriptPubKeyType.SegwitP2SH, Network.Main);
254+
255+
var multisigRedeem =
256+
PayToMultiSigTemplate.Instance.GenerateScriptPubKey(2, new[] {key.PubKey, key2.PubKey});
257+
var p2wshScriptPubKey = multisigRedeem.WitHash.ScriptPubKey;
258+
var p2ShAddressScriptPubKey = multisigRedeem.Hash.ScriptPubKey;
259+
260+
var p2shAddress = p2ShAddressScriptPubKey.GetDestinationAddress(Network.Main);
261+
var p2wshAddress = p2wshScriptPubKey.GetDestinationAddress(Network.Main);
262+
263+
264+
var toSpend = p2shAddress.Network.CreateTransaction();
265+
toSpend.Inputs.Add(new TxIn(new OutPoint(uint256.Zero, 0xFFFFFFFF), new Script(OpcodeType.OP_0))); ;
266+
toSpend.Outputs.Add(new TxOut(Money.Zero, p2shAddress.ScriptPubKey));
267+
268+
var coins = new Coin[]
269+
{
270+
new Coin(OutPoint.Zero, new TxOut(Money.Coins(1), addr2)),
271+
new Coin(new OutPoint(uint256.One, 1), new TxOut(Money.Coins(1), addr3)).ToScriptCoin(addr2.ScriptPubKey),
272+
new ScriptCoin(new OutPoint(toSpend, 0), new TxOut(Money.Coins(1), p2shAddress), multisigRedeem),
273+
new ScriptCoin(new OutPoint(uint256.One, 4), new TxOut(Money.Coins(1), p2wshAddress), multisigRedeem)
274+
};
275+
276+
var message = "I own these coins";
277+
278+
var psbt = addr.CreateBIP322PSBT(message, fundProofOutputs: coins);
279+
psbt.AddScripts(multisigRedeem);
280+
psbt.AddTransactions(toSpend);
281+
psbt.SignWithKeys(key, key2);
282+
var signature = BIP322Signature.FromPSBT(psbt, SignatureType.Full);
283+
Assert.True(addr.VerifyBIP322(message, signature, coins));
284+
}
285+
}
286+
}
287+
#endif

NBitcoin/BIP174/PSBTInput.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -361,12 +361,14 @@ public void UpdateFromCoin(ICoin coin)
361361
!coin.IsMalleable || witness_script != null)
362362
{
363363
witness_utxo = coin.TxOut;
364-
non_witness_utxo = null;
364+
if (Parent.Settings.AutomaticUTXOTrimming)
365+
non_witness_utxo = null;
365366
}
366367
else
367368
{
368369
orphanTxOut = coin.TxOut;
369-
witness_utxo = null;
370+
if (Parent.Settings.AutomaticUTXOTrimming)
371+
witness_utxo = null;
370372
}
371373
if (IsFinalized())
372374
ClearForFinalize();
@@ -1044,6 +1046,7 @@ internal static bool IsTaprootReady(SigningOptions signingOptions, Coin coin)
10441046
return !coin.ScriptPubKey.IsScriptType(ScriptType.Taproot) || (signingOptions.PrecomputedTransactionData is TaprootReadyPrecomputedTransactionData);
10451047
}
10461048

1049+
public bool VerifyScript(PrecomputedTransactionData? precomputedTransactionData, out ScriptError err) => VerifyScript(Parent.Settings.ScriptVerify, precomputedTransactionData, out err);
10471050
public bool VerifyScript(ScriptVerify scriptVerify, PrecomputedTransactionData? precomputedTransactionData, out ScriptError err)
10481051
{
10491052
var eval = new ScriptEvaluationContext

NBitcoin/BIP174/PartiallySignedTransaction.cs

+11-4
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,21 @@ public class PSBTSettings
8484
public bool SkipVerifyScript { get; set; } = false;
8585
public SigningOptions SigningOptions { get; set; } = new SigningOptions();
8686
public ScriptVerify ScriptVerify { get; internal set; } = ScriptVerify.Standard;
87-
87+
/// <summary>
88+
/// Some opereration may strip non_witness_utxo if deemed safe. This is to prevent the PSBT from growing too large.
89+
/// Set this to false if you want to disable this behavior.
90+
/// </summary>
91+
public bool AutomaticUTXOTrimming { get; set; } = true;
8892
public PSBTSettings Clone()
8993
{
9094
return new PSBTSettings()
9195
{
9296
SigningOptions = SigningOptions.Clone(),
9397
CustomBuilderExtensions = CustomBuilderExtensions?.ToArray(),
94-
IsSmart = IsSmart
98+
IsSmart = IsSmart,
99+
ScriptVerify = ScriptVerify,
100+
SkipVerifyScript = SkipVerifyScript,
101+
AutomaticUTXOTrimming = AutomaticUTXOTrimming
95102
};
96103
}
97104
}
@@ -1074,14 +1081,14 @@ public PSBT AddScripts(params Script[] redeems)
10741081
else if (txout.ScriptPubKey == p2wsh)
10751082
{
10761083
o.WitnessScript = redeem;
1077-
if (o is PSBTInput i)
1084+
if (o is PSBTInput i && Settings.AutomaticUTXOTrimming)
10781085
i.TrySlimUTXO();
10791086
}
10801087
else if (txout.ScriptPubKey == p2shp2wsh)
10811088
{
10821089
o.WitnessScript = redeem;
10831090
o.RedeemScript = redeem.WitHash.ScriptPubKey;
1084-
if (o is PSBTInput i)
1091+
if (o is PSBTInput i && Settings.AutomaticUTXOTrimming)
10851092
i.TrySlimUTXO();
10861093
}
10871094
}

0 commit comments

Comments
 (0)