From 8d29ad111a63776a48b573484cc4f55ab36b21b6 Mon Sep 17 00:00:00 2001 From: "nicolas.dorier" Date: Thu, 5 Sep 2024 17:10:22 +0900 Subject: [PATCH] Rely on PSBT for signing --- NBitcoin.Tests/BIP322Tests.cs | 164 +++++++++--------- NBitcoin/BIP322/BIP322Signature.cs | 17 ++ NBitcoin/BIP322o.cs | 266 ----------------------------- NBitcoin/BitcoinAddress.cs | 2 +- NBitcoin/BitcoinAddress.partial.cs | 89 ++++++++++ NBitcoin/Key.BIP322.cs | 133 +++++++++++++++ NBitcoin/Key.cs | 2 +- 7 files changed, 318 insertions(+), 355 deletions(-) delete mode 100644 NBitcoin/BIP322o.cs create mode 100644 NBitcoin/BitcoinAddress.partial.cs create mode 100644 NBitcoin/Key.BIP322.cs diff --git a/NBitcoin.Tests/BIP322Tests.cs b/NBitcoin.Tests/BIP322Tests.cs index 27c0510a3b..51066aaee0 100644 --- a/NBitcoin.Tests/BIP322Tests.cs +++ b/NBitcoin.Tests/BIP322Tests.cs @@ -1,4 +1,6 @@ -using System; +#if HAS_SPAN +using NBitcoin.BIP322; +using System; using System.Threading.Tasks; using Xunit; @@ -9,7 +11,7 @@ public class BIP322Tests { //from https://github.com/ACken2/bip322-js/tree/main/test && https://github.com/bitcoin/bitcoin/pull/24058/files#diff-2bd57d7fbec4bb262834d155c304ebe15d26f73fea87c75ff273df3529a15510 [Fact] - public async Task CanHandleLegacyBIP322Message() + public void CanHandleLegacyBIP322Message() { var address = BitcoinAddress.Create("1F3sAm6ZtwLAUnj7d38pGFxtP3RVEvtsbV", Network.Main); var addressTestnet = BitcoinAddress.Create("muZpTpBYhxmRFuCjLc7C6BBDF32C8XVJUi", Network.TestNet); @@ -20,15 +22,15 @@ public async Task CanHandleLegacyBIP322Message() var message = "This is an example of a signed message."; var messageWrong = ""; var signature = BIP322.BIP322Signature.Parse("H9L5yLFjti0QTHhPyFrZCT1V/MMnBtXKmoiKDZ78NDBjERki6ZTQZdSMCtkgoNmp17By9ItJr8o7ChX0XxY91nk=", address.Network); - Assert.True(await BIP322o.Verify(message, address, signature)); - Assert.True(await BIP322o.Verify(message, addressTestnet, signature)); - Assert.True(await BIP322o.Verify(message, addressRegtest, signature)); - Assert.False(await BIP322o.Verify(messageWrong, address, signature)); - Assert.False(await BIP322o.Verify(messageWrong, addressTestnet, signature)); - Assert.False(await BIP322o.Verify(messageWrong, addressRegtest, signature)); - Assert.False(await BIP322o.Verify(message, addressWrong, signature)); - Assert.False(await BIP322o.Verify(message, addressWrongTestnet, signature)); - Assert.False(await BIP322o.Verify(message, addressWrongRegtest, signature)); + Assert.True(address.VerifyBIP322(message, signature)); + Assert.True(addressTestnet.VerifyBIP322(message, signature)); + Assert.True(addressRegtest.VerifyBIP322(message, signature)); + Assert.False(address.VerifyBIP322(messageWrong, signature)); + Assert.False(addressTestnet.VerifyBIP322(messageWrong, signature)); + Assert.False(addressRegtest.VerifyBIP322(messageWrong, signature)); + Assert.False(addressWrong.VerifyBIP322(message, signature)); + Assert.False(addressWrongTestnet.VerifyBIP322(message, signature)); + Assert.False(addressWrongRegtest.VerifyBIP322(message, signature)); var privateKey = new BitcoinSecret("L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k", Network.Main) @@ -37,19 +39,19 @@ public async Task CanHandleLegacyBIP322Message() addressTestnet = privateKey.GetAddress(ScriptPubKeyType.Legacy, Network.TestNet); addressRegtest = privateKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest); message = "Hello World"; - signature = await BIP322o.SignEncoded(address, message, BIP322o.SignatureType.Legacy, privateKey); + signature = privateKey.SignBIP322(address, message, SignatureType.Legacy); var signatureTestnet = - await BIP322o.SignEncoded(addressTestnet, message, BIP322o.SignatureType.Legacy, privateKey); + privateKey.SignBIP322(addressTestnet, message, SignatureType.Legacy); var signatureRegtest = - await BIP322o.SignEncoded(addressRegtest, message, BIP322o.SignatureType.Legacy, privateKey); + privateKey.SignBIP322(addressRegtest, message, SignatureType.Legacy); - Assert.True(await BIP322o.Verify(message, address, signature)); - Assert.True(await BIP322o.Verify(message, addressTestnet, signatureTestnet)); - Assert.True(await BIP322o.Verify(message, addressRegtest, signatureRegtest)); + Assert.True(address.VerifyBIP322(message, signature)); + Assert.True(addressTestnet.VerifyBIP322(message, signatureTestnet)); + Assert.True(addressRegtest.VerifyBIP322(message, signatureRegtest)); } [Fact] - public async Task CanSign() + public void CanSign() { var k = new Key(); var p = k.GetAddress(ScriptPubKeyType.SegwitP2SH, Network.Main); @@ -64,11 +66,11 @@ public async Task CanSign() var addressRegtest = BitcoinAddress.Create("2N8zi3ydDsMnVkqaUUe5Xav6SZqhyqEduap", Network.RegTest); var message = "Hello World"; - var signature = await BIP322o.SignEncoded(address, message, BIP322o.SignatureType.Simple, privateKey); + var signature = privateKey.SignBIP322(address, message, SignatureType.Simple); var signatureTestnet = - await BIP322o.SignEncoded(addressTestnet, message, BIP322o.SignatureType.Simple, privateKeyTestnet); + privateKeyTestnet.SignBIP322(addressTestnet, message, SignatureType.Simple); var signatureRegtest = - await BIP322o.SignEncoded(addressRegtest, message, BIP322o.SignatureType.Simple, privateKeyTestnet); + privateKeyTestnet.SignBIP322(addressRegtest, message, SignatureType.Simple); Assert.Equal(signatureTestnet, signature); Assert.Equal(signatureRegtest, signature); @@ -86,40 +88,40 @@ public async Task CanSign() var p2shAddress = p2ShAddressScriptPubKey.GetDestinationAddress(Network.Main); var p2wshAddress = p2wshScriptPubKey.GetDestinationAddress(Network.Main); - var message2of3 = "This will be a 2-of-3 multisig BIP 322 signed message"; - await Assert.ThrowsAsync(async () => - { - await BIP322o.SignEncoded(p2shAddress, message2of3, BIP322o.SignatureType.Simple, redeem, null,k2, k3); - }); - var p2sh_signature2of3_k2_k3 = - await BIP322o.SignEncoded(p2shAddress, message2of3, BIP322o.SignatureType.Full, redeem, null,k2, k3); - var p2sh_signature2of3_k1_k3 = - await BIP322o.SignEncoded(p2shAddress, message2of3, BIP322o.SignatureType.Full, redeem, null,k1, k3); - await Assert.ThrowsAsync(async () => - { - await BIP322o.SignEncoded(p2shAddress, message2of3, BIP322o.SignatureType.Full, redeem, null,k1); - }); - Assert.True(await BIP322o.Verify(message2of3, p2shAddress, p2sh_signature2of3_k2_k3)); - Assert.True(await BIP322o.Verify(message2of3, p2shAddress, p2sh_signature2of3_k1_k3)); - - var p2wsh_signature2of3_k2_k3 = - await BIP322o.SignEncoded(p2wshAddress, message2of3, BIP322o.SignatureType.Simple, redeem, null,k2, k3); - var p2wsh_signature2of3_k1_k3 = - await BIP322o.SignEncoded(p2wshAddress, message2of3, BIP322o.SignatureType.Simple, redeem, null,k1, k3); - await Assert.ThrowsAsync(async () => - { - await BIP322o.SignEncoded(p2wshAddress, message2of3, BIP322o.SignatureType.Simple, redeem, null,k1); - }); - Assert.True(await BIP322o.Verify(message2of3, p2wshAddress, p2wsh_signature2of3_k2_k3)); - Assert.True(await BIP322o.Verify(message2of3, p2wshAddress, p2wsh_signature2of3_k1_k3)); + //var message2of3 = "This will be a 2-of-3 multisig BIP 322 signed message"; + //Assert.Throws(() => + //{ + // Key.SignEncoded(p2shAddress, message2of3, SignatureType.Simple, redeem, null,k2, k3); + //}); + //var p2sh_signature2of3_k2_k3 = + // Key.SignEncoded(p2shAddress, message2of3, SignatureType.Full, redeem, null,k2, k3); + //var p2sh_signature2of3_k1_k3 = + // Key.SignEncoded(p2shAddress, message2of3, SignatureType.Full, redeem, null,k1, k3); + //Assert.Throws(() => + //{ + // Key.SignEncoded(p2shAddress, message2of3, SignatureType.Full, redeem, null,k1); + //}); + //Assert.True(p2shAddress.VerifyBIP322(message2of3, p2sh_signature2of3_k2_k3)); + //Assert.True(p2shAddress.VerifyBIP322(message2of3, p2sh_signature2of3_k1_k3)); + + //var p2wsh_signature2of3_k2_k3 = + // Key.SignEncoded(p2wshAddress, message2of3, SignatureType.Simple, redeem, null,k2, k3); + //var p2wsh_signature2of3_k1_k3 = + // Key.SignEncoded(p2wshAddress, message2of3, SignatureType.Simple, redeem, null,k1, k3); + //Assert.Throws(() => + //{ + // Key.SignEncoded(p2wshAddress, message2of3, SignatureType.Simple, redeem, null,k1); + //}); + //Assert.True(p2wshAddress.VerifyBIP322(message2of3, p2wsh_signature2of3_k2_k3)); + //Assert.True(p2wshAddress.VerifyBIP322(message2of3, p2wsh_signature2of3_k1_k3)); } [Fact] - public async Task CanVerifyBIP322Message() + public void CanVerifyBIP322Message() { - var emptyStringHash = BIP322o.CreateMessageHash(""); - var helloWorldHash = BIP322o.CreateMessageHash("Hello World"); + var emptyStringHash = Key.CreateMessageHash(""); + var helloWorldHash = Key.CreateMessageHash("Hello World"); Assert.Equal(new uint256("c90c269c4f8fcbe6880f72a721ddfbf1914268a794cbb21cfafee13770ae19f1"), emptyStringHash); @@ -132,43 +134,35 @@ public async Task CanVerifyBIP322Message() var segwitAddress = key.GetAddress(ScriptPubKeyType.Segwit, Network.Main); Assert.Equal("bc1q9vza2e8x573nczrlzms0wvx3gsqjx7vavgkx0l", segwitAddress.ToString()); + var emptyStringPSBT = Key.CreateToSignPSBT(Network.Main, emptyStringHash, segwitAddress.ScriptPubKey); + var helloWorldToSpendPSBT = Key.CreateToSignPSBT(Network.Main, helloWorldHash, segwitAddress.ScriptPubKey); - var emptyStringToSpendTx = - BIP322o.CreateToSpendTransaction(Network.Main, emptyStringHash, segwitAddress.ScriptPubKey); - var helloWorldToSpendTx = - BIP322o.CreateToSpendTransaction(Network.Main, helloWorldHash, segwitAddress.ScriptPubKey); Assert.Equal("c5680aa69bb8d860bf82d4e9cd3504b55dde018de765a91bb566283c545a99a7", - emptyStringToSpendTx.GetHash().ToString()); + emptyStringPSBT.Inputs[0].NonWitnessUtxo.GetHash().ToString()); Assert.Equal("b79d196740ad5217771c1098fc4a4b51e0535c32236c71f1ea4d61a2d603352b", - helloWorldToSpendTx.GetHash().ToString()); - - var emptyStringToSignTx = BIP322o.CreateToSignTransaction(Network.Main, emptyStringToSpendTx.GetHash()); - var helloWorldToSignTx = BIP322o.CreateToSignTransaction(Network.Main, helloWorldToSpendTx.GetHash()); + helloWorldToSpendPSBT.Inputs[0].NonWitnessUtxo.GetHash().ToString()); Assert.Equal("1e9654e951a5ba44c8604c4de6c67fd78a27e81dcadcfe1edf638ba3aaebaed6", - emptyStringToSignTx.GetHash().ToString()); + emptyStringPSBT.GetGlobalTransaction().GetHash().ToString()); Assert.Equal("88737ae86f2077145f93cc4b153ae9a1cb8d56afa511988c149c5c8c9d93bddf", - helloWorldToSignTx.GetHash().ToString()); + helloWorldToSpendPSBT.GetGlobalTransaction().GetHash().ToString()); - var simpleHelloWorldSignature = await - BIP322o.SignEncoded(segwitAddress, "Hello World", BIP322o.SignatureType.Simple, key); - var simpleEmptySignature = await - BIP322o.SignEncoded(segwitAddress, "", BIP322o.SignatureType.Simple, key); + var simpleHelloWorldSignature = key.SignBIP322(segwitAddress, "Hello World", SignatureType.Simple); + var simpleEmptySignature = key.SignBIP322(segwitAddress, "", SignatureType.Simple); - var fullHelloWorldSignature = await - BIP322o.SignEncoded(segwitAddress, "Hello World", BIP322o.SignatureType.Full, key); - var fullEmptySignature = await BIP322o.SignEncoded(segwitAddress, "", BIP322o.SignatureType.Full, key); + var fullHelloWorldSignature = key.SignBIP322(segwitAddress, "Hello World", SignatureType.Full); + var fullEmptySignature = key.SignBIP322(segwitAddress, "", SignatureType.Full); - Assert.True(await BIP322o.Verify("Hello World", segwitAddress, simpleHelloWorldSignature)); - Assert.True(await BIP322o.Verify("", segwitAddress, simpleEmptySignature)); - Assert.True(await BIP322o.Verify("Hello World", segwitAddress, fullHelloWorldSignature)); - Assert.True(await BIP322o.Verify("", segwitAddress, fullEmptySignature)); + Assert.True(segwitAddress.VerifyBIP322("Hello World", simpleHelloWorldSignature)); + Assert.True(segwitAddress.VerifyBIP322("", simpleEmptySignature)); + Assert.True(segwitAddress.VerifyBIP322("Hello World", fullHelloWorldSignature)); + Assert.True(segwitAddress.VerifyBIP322("", fullEmptySignature)); - Assert.False(await BIP322o.Verify("nuhuh", segwitAddress, simpleHelloWorldSignature)); - Assert.False(await BIP322o.Verify("nuhuh", segwitAddress, simpleEmptySignature)); - Assert.False(await BIP322o.Verify("nuhuh", segwitAddress, fullHelloWorldSignature)); - Assert.False(await BIP322o.Verify("nuhuh", segwitAddress, fullEmptySignature)); + Assert.False(segwitAddress.VerifyBIP322("nuhuh", simpleHelloWorldSignature)); + Assert.False(segwitAddress.VerifyBIP322("nuhuh", simpleEmptySignature)); + Assert.False(segwitAddress.VerifyBIP322("nuhuh", fullHelloWorldSignature)); + Assert.False(segwitAddress.VerifyBIP322("nuhuh", fullEmptySignature)); foreach (var t in new[] { @@ -179,7 +173,7 @@ public async Task CanVerifyBIP322Message() }) { (var message, var sig) = t; - Assert.True(await BIP322o.Verify(message, segwitAddress, sig)); + Assert.True(segwitAddress.VerifyBIP322(message, sig)); } // // 2-of-3 p2sh multisig BIP322 signature (created with the buidl-python library) @@ -194,8 +188,7 @@ public async Task CanVerifyBIP322Message() // "AAAAAAHNcfHaNfl8f/+ZC2gTr8aF+0KgppYjKM94egaNm/u1ZAAAAAD8AEcwRAIhAJ6hdj61vLDP+aFa30qUZQmrbBfE0kiOObYvt5nqPSxsAh9IrOKFwflfPRUcQ/5e0REkdFHVP2GGdUsMgDet+sNlAUcwRAIgH3eW/VyFDoXvCasd8qxgwj5NDVo0weXvM6qyGXLCR5YCIEwjbEV6fS6RWP6QsKOcMwvlGr1/SgdCC6pW4eH87/YgAUxpUiECKJfGy28imLcuAeNBLHCNv3NRP5jnJwFDNRXCYNY/vJ4hAv1RQtaZs7+vKqQeWl2rb/jd/gMxkEjUnjZdDGPDZkMLIQL65cH2X5O7LujjTLDL2l8Pxy0Y2UUR99u1qCfjdz7dklOuAAAAAAEAAAAAAAAAAAFqAAAAAA==", // "This will be a p2sh 2-of-3 multisig BIP 322 signed message"), // MessageVerificationResult::OK); - Assert.True(await BIP322o.Verify("This will be a p2sh 2-of-3 multisig BIP 322 signed message", - BitcoinAddress.Create("3LnYoUkFrhyYP3V7rq3mhpwALz1XbCY9Uq", Network.Main), + Assert.True(BitcoinAddress.Create("3LnYoUkFrhyYP3V7rq3mhpwALz1XbCY9Uq", Network.Main).VerifyBIP322("This will be a p2sh 2-of-3 multisig BIP 322 signed message", "AAAAAAHNcfHaNfl8f/+ZC2gTr8aF+0KgppYjKM94egaNm/u1ZAAAAAD8AEcwRAIhAJ6hdj61vLDP+aFa30qUZQmrbBfE0kiOObYvt5nqPSxsAh9IrOKFwflfPRUcQ/5e0REkdFHVP2GGdUsMgDet+sNlAUcwRAIgH3eW/VyFDoXvCasd8qxgwj5NDVo0weXvM6qyGXLCR5YCIEwjbEV6fS6RWP6QsKOcMwvlGr1/SgdCC6pW4eH87/YgAUxpUiECKJfGy28imLcuAeNBLHCNv3NRP5jnJwFDNRXCYNY/vJ4hAv1RQtaZs7+vKqQeWl2rb/jd/gMxkEjUnjZdDGPDZkMLIQL65cH2X5O7LujjTLDL2l8Pxy0Y2UUR99u1qCfjdz7dklOuAAAAAAEAAAAAAAAAAAFqAAAAAA==")); // // 3-of-3 p2wsh multisig BIP322 signature (created with the buidl-python library) @@ -208,11 +201,9 @@ public async Task CanVerifyBIP322Message() // "bc1qlqtuzpmazp2xmcutlwv0qvggdvem8vahkc333usey4gskug8nutsz53msw", "BQBIMEUCIQDQoXvGKLH58exuujBOta+7+GN7vi0lKwiQxzBpuNuXuAIgIE0XYQlFDOfxbegGYYzlf+tqegleAKE6SXYIa1U+uCcBRzBEAiATegywVl6GWrG9jJuPpNwtgHKyVYCX2yfuSSDRFATAaQIgTLlU6reLQsSIrQSF21z3PtUO2yAUseUWGZqRUIE7VKoBSDBFAiEAgxtpidsU0Z4u/+5RB9cyeQtoCW5NcreLJmWXZ8kXCZMCIBR1sXoEinhZE4CF9P9STGIcMvCuZjY6F5F0XTVLj9SjAWlTIQP3dyWvTZjUENWJowMWBsQrrXCUs20Gu5YF79CG5Ga0XSEDwqI5GVBOuFkFzQOGH5eTExSAj2Z/LDV/hbcvAPQdlJMhA17FuuJd+4wGuj+ZbVxEsFapTKAOwyhfw9qpch52JKxbU64=", // "This will be a p2wsh 3-of-3 multisig BIP 322 signed message"), // MessageVerificationResult::OK); - Assert.True(await BIP322o.Verify("This will be a p2wsh 3-of-3 multisig BIP 322 signed message", - BitcoinAddress.Create("bc1qlqtuzpmazp2xmcutlwv0qvggdvem8vahkc333usey4gskug8nutsz53msw", Network.Main), + Assert.True(BitcoinAddress.Create("bc1qlqtuzpmazp2xmcutlwv0qvggdvem8vahkc333usey4gskug8nutsz53msw", Network.Main).VerifyBIP322("This will be a p2wsh 3-of-3 multisig BIP 322 signed message", "BQBIMEUCIQDQoXvGKLH58exuujBOta+7+GN7vi0lKwiQxzBpuNuXuAIgIE0XYQlFDOfxbegGYYzlf+tqegleAKE6SXYIa1U+uCcBRzBEAiATegywVl6GWrG9jJuPpNwtgHKyVYCX2yfuSSDRFATAaQIgTLlU6reLQsSIrQSF21z3PtUO2yAUseUWGZqRUIE7VKoBSDBFAiEAgxtpidsU0Z4u/+5RB9cyeQtoCW5NcreLJmWXZ8kXCZMCIBR1sXoEinhZE4CF9P9STGIcMvCuZjY6F5F0XTVLj9SjAWlTIQP3dyWvTZjUENWJowMWBsQrrXCUs20Gu5YF79CG5Ga0XSEDwqI5GVBOuFkFzQOGH5eTExSAj2Z/LDV/hbcvAPQdlJMhA17FuuJd+4wGuj+ZbVxEsFapTKAOwyhfw9qpch52JKxbU64=")); -#if HAS_SPAN // // Single key p2tr BIP322 signature (created with the buidl-python library) // // PrivateKeyWIF L3VFeEujGtevx9w18HD1fhRbCH67Az2dpCymeRE1SoPK6XQtaN2k // BOOST_CHECK_EQUAL( @@ -221,15 +212,13 @@ public async Task CanVerifyBIP322Message() // "AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ==", // "Hello World"), // MessageVerificationResult::OK); - Assert.True(await BIP322o.Verify("Hello World", - BitcoinAddress.Create("bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3", Network.Main), + Assert.True(BitcoinAddress.Create("bc1ppv609nr0vr25u07u95waq5lucwfm6tde4nydujnu8npg4q75mr5sxq8lt3", Network.Main).VerifyBIP322("Hello World", "AUHd69PrJQEv+oKTfZ8l+WROBHuy9HKrbFCJu7U1iK2iiEy1vMU5EfMtjc+VSHM7aU0SDbak5IUZRVno2P5mjSafAQ==")); -#endif } [Fact] - public async Task CanDoProofOfFunds() + public void CanDoProofOfFunds() { var key = new Key(); var key2 = new Key(); @@ -256,7 +245,7 @@ public async Task CanDoProofOfFunds() var message = "I own these coins"; - var signature = await BIP322o.SignEncoded(addr, message, BIP322o.SignatureType.Full, null, coins, key, key2); + //var signature = Key.SignEncoded(addr, message, SignatureType.Full, null, coins, key, key2); @@ -264,3 +253,4 @@ public async Task CanDoProofOfFunds() } } } +#endif diff --git a/NBitcoin/BIP322/BIP322Signature.cs b/NBitcoin/BIP322/BIP322Signature.cs index eaefb51566..81792c4178 100644 --- a/NBitcoin/BIP322/BIP322Signature.cs +++ b/NBitcoin/BIP322/BIP322Signature.cs @@ -10,6 +10,18 @@ namespace NBitcoin.BIP322 { + public enum SignatureType + { + Legacy, + Simple, + Full + } + + public enum HashType + { + Legacy, + BIP322 + } public abstract class BIP322Signature { public Network Network { get; } @@ -132,11 +144,16 @@ public Full(Transaction signedTransaction, Network network) : base(network) if (!IsValid(signedTransaction)) throw new ArgumentException("This isn't a valid BIP0322 to_sign transaction", nameof(signedTransaction)); SignedTransaction = signedTransaction; + FundProofs = signedTransaction.Inputs.Skip(1).ToArray(); } + public TxIn[] FundProofs { get; } + public Transaction SignedTransaction { get; } static Script OpReturn = new Script("OP_RETURN"); internal static bool IsValid(Transaction tx) => + tx.Outputs.Count == 1 && + tx.Inputs.Count > 0 && tx.Inputs[0].Sequence == 0 && tx.Outputs[0].Value == Money.Zero && tx.Outputs[0].ScriptPubKey == OpReturn; diff --git a/NBitcoin/BIP322o.cs b/NBitcoin/BIP322o.cs deleted file mode 100644 index d62db91c64..0000000000 --- a/NBitcoin/BIP322o.cs +++ /dev/null @@ -1,266 +0,0 @@ -#if HAS_SPAN -#nullable enable -using System; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using NBitcoin.BIP322; -using NBitcoin.Crypto; -using NBitcoin.DataEncoders; -using NBitcoin.Protocol; - -namespace NBitcoin -{ - public static class BIP322o - { - public enum SignatureType - { - Legacy, - Simple, - Full - } - - public enum MessageType - { - Legacy, - BIP322 - } - - private static byte[] TAG = Encoding.UTF8.GetBytes("BIP0322-signed-message"); - - private static byte[] BITCOIN_SIGNED_MESSAGE_HEADER_BYTES => - Encoding.UTF8.GetBytes("Bitcoin Signed Message:\n"); - - public static uint256 CreateMessageHash(string message, MessageType type = MessageType.BIP322) => - CreateMessageHash(Encoding.UTF8.GetBytes(message), type); - - public static uint256 CreateMessageHash(byte[] message, MessageType type) - { - if (type == MessageType.Legacy) - { - var ms = new MemoryStream(); - - ms.WriteByte((byte) BITCOIN_SIGNED_MESSAGE_HEADER_BYTES.Length); - ms.Write(BITCOIN_SIGNED_MESSAGE_HEADER_BYTES, 0, BITCOIN_SIGNED_MESSAGE_HEADER_BYTES.Length); - - var size = new VarInt((ulong) message.Length).ToBytes(); - ms.Write(size, 0, size.Length); - ms.Write(message, 0, message.Length); - return Hashes.DoubleSHA256(ms.ToArray()); - } - - var tagHash = Hashes.SHA256(TAG); - var tagBytes = tagHash.Concat(tagHash).ToArray(); - return new uint256(Hashes.SHA256(tagBytes.Concat(message).ToArray()), false); - } - - internal static Transaction CreateToSpendTransaction(Network network, uint256 messageHash, Script scriptPubKey) - { - var tx = network.CreateTransaction(); - tx.Version = 0; - tx.LockTime = 0; - tx.Inputs.Add(new TxIn( new OutPoint(uint256.Zero, 0xFFFFFFFF), new Script(OpcodeType.OP_0, Op.GetPushOp(messageHash.ToBytes(false)))) - { - Sequence = 0, - WitScript = WitScript.Empty, - }); - tx.Outputs.Add(new TxOut(Money.Zero, scriptPubKey)); - return tx; - } - - internal static Transaction CreateToSignTransaction(Network network, uint256 toSpendTxId, - WitScript? messageSignature = null, - uint version = 0, uint lockTime = 0, uint sequence = 0, ScriptCoin[]? additionalInputs = null) - { - var tx = network.CreateTransaction(); - tx.Version = version; - tx.LockTime = lockTime; - tx.Inputs.Add(new TxIn(new OutPoint(toSpendTxId, 0)) - { - Sequence = sequence, - WitScript = messageSignature?? WitScript.Empty, - }); - if (additionalInputs is not null) - { - foreach (var input in additionalInputs) - { - tx.Inputs.Add(new TxIn(input.Outpoint, input.Redeem) - { - Sequence = sequence, - }); - } - } - - tx.Outputs.Add(new TxOut(Money.Zero, new Script(OpcodeType.OP_RETURN))); - return tx; - } - public static async Task SignEncoded(BitcoinAddress address, string message, SignatureType type, - Script? redeemScript = null, ICoin[]? additionalCoins = null, params Key[] keys) => await Sign(address, message, type, keys, redeemScript, additionalCoins); - public static async Task SignEncoded(BitcoinAddress address, string message, SignatureType type, - params Key[] keys) => await Sign(address, message, type, keys); - public static async Task Sign(BitcoinAddress address, string message, SignatureType type, - Key[] keys, Script? redeemScript = null, ICoin[]? additionalCoins = null) - { - // if(address.ScriptPubKey.IsScriptType(ScriptType.P2SH) && type == SignatureType.Simple) - // throw new InvalidOperationException("Simple signatures are not supported for P2SH scripts."); - - if(additionalCoins?.Length > 0 && type != SignatureType.Full) - throw new InvalidOperationException("Additional coins are only supported for full signatures."); - if(keys.Length == 0) - throw new ArgumentException("At least one key is required.", nameof(keys)); - var messageHash = CreateMessageHash(message, - type == SignatureType.Legacy ? MessageType.Legacy : MessageType.BIP322); - var network = address.Network; - switch (type) - { - case SignatureType.Legacy when !address.ScriptPubKey.IsScriptType(ScriptType.P2PKH): - throw new InvalidOperationException("Legacy signing is only supported for P2PKH scripts."); - case SignatureType.Legacy when keys.Length != 1: - throw new InvalidOperationException("Legacy signing only supports one key."); - case SignatureType.Legacy: - { - var key = keys[0]; - var sig = key.SignCompact(messageHash); - var recovered = PubKey.RecoverCompact(messageHash, sig); - if (recovered != key.PubKey) - { - throw new InvalidOperationException("Invalid signature."); - } - return new BIP322.BIP322Signature.Legacy(key.IsCompressed, sig, address.Network); - } - } - - var toSpendTx = CreateToSpendTransaction(network, new uint256(messageHash), address.ScriptPubKey); - var toSignTx = CreateToSignTransaction(network, toSpendTx.GetHash(), WitScript.Empty); - - Func>? proofOfFundsLookup = null; - if (additionalCoins is not null) - { - foreach (var coin in additionalCoins) - { - toSignTx.Inputs.Add(new TxIn(coin.Outpoint)); - } - proofOfFundsLookup = async (outpoints) => additionalCoins.Select(coin => coin.TxOut).ToArray(); - } - var coins = additionalCoins is null ? toSpendTx.Outputs.AsCoins().Select(coin => redeemScript is null ? coin: new ScriptCoin(coin, redeemScript)).ToArray() : toSpendTx.Outputs.AsCoins().Concat(additionalCoins).ToArray(); - - toSignTx.Sign(keys.Select(key=> key.GetBitcoinSecret(address.Network)), coins); - - BIP322.BIP322Signature result = - type == SignatureType.Simple ? new BIP322.BIP322Signature.Simple(toSignTx.Inputs[0].WitScript, address.Network) - : new BIP322.BIP322Signature.Full(toSignTx, address.Network); - if (result is BIP322.BIP322Signature.Simple { WitnessScript : { PushCount: 0 } } || - result is BIP322.BIP322Signature.Full full && full.SignedTransaction.Inputs[0].WitScript.PushCount == 0 && full.SignedTransaction.Inputs[0].ScriptSig.Length == 0) - { - throw new InvalidOperationException("Failed to sign the message. Did you forget to provide a redeem script?"); - } - - - if (!await Verify(message, address, result!, proofOfFundsLookup)) - { - throw new InvalidOperationException("Could not produce a valid signature."); - } - - return result; - } - public static Task Verify(string message, BitcoinAddress address, string signature, - Func>? proofOfFundsLookup = null) - { - var sig = BIP322Signature.Parse(signature, address.Network); - return Verify(message, address, sig, proofOfFundsLookup); - } - - public static async Task Verify(string message, BitcoinAddress address, BIP322.BIP322Signature signature, - Func>? proofOfFundsLookup = null) - { - var messageBytes = Encoding.UTF8.GetBytes(message); - if (signature is BIP322.BIP322Signature.Simple { WitnessScript: var script }) - { - if (script.PushCount < 2 && !address.ScriptPubKey.IsScriptType(ScriptType.Taproot)) - { - return false; - } - - var toSpend = CreateToSpendTransaction(address.Network, CreateMessageHash(message, MessageType.BIP322), - address.ScriptPubKey); - var toSign = CreateToSignTransaction(address.Network, toSpend.GetHash(), script); - ScriptEvaluationContext evalContext = new ScriptEvaluationContext() - { - ScriptVerify = ScriptVerify.Const_ScriptCode - | ScriptVerify.LowS - | ScriptVerify.StrictEnc - | ScriptVerify.NullFail - | ScriptVerify.MinimalData - | ScriptVerify.CleanStack - | ScriptVerify.P2SH - | ScriptVerify.Witness - | ScriptVerify.Taproot - | ScriptVerify.MinimalIf - }; - - if (address.ScriptPubKey.IsScriptType(ScriptType.P2SH)) - { - - var withScriptParams = PayToWitPubKeyHashTemplate.Instance.ExtractWitScriptParameters(script); - toSign.Inputs[0].ScriptSig = PayToScriptHashTemplate.Instance.GenerateScriptSig(new Op[0], withScriptParams.Hash.ScriptPubKey); - } - // Create a checker for the signature - TransactionChecker checker = address.ScriptPubKey.IsScriptType(ScriptType.Taproot) ? new TransactionChecker(toSign, 0, toSpend.Outputs[0], new TaprootReadyPrecomputedTransactionData(toSign, toSpend.Outputs.ToArray())) : new TransactionChecker(toSign, 0, toSpend.Outputs[0]); - return evalContext.VerifyScript(toSign.Inputs[0].ScriptSig, script, address.ScriptPubKey, checker); - } - else if (signature is BIP322.BIP322Signature.Legacy { CompactSignature : var sig}) - { - try - { - if (!address.ScriptPubKey.IsScriptType(ScriptType.P2PKH)) - { - return false; - } - var hash = CreateMessageHash(message, MessageType.Legacy); - var k = sig.RecoverPubKey(hash); - if (k.GetAddress(ScriptPubKeyType.Legacy, address.Network) != address) - { - return false; - } - return ECDSASignature.TryParseFromCompact(sig.Signature, out var ecSig) && k.Verify(hash, ecSig); - } - catch - { - return false; - } - } - else if (signature is BIP322.BIP322Signature.Full { SignedTransaction: var toSign }) - { - var toSpend = CreateToSpendTransaction(address.Network, CreateMessageHash(message, MessageType.BIP322), - address.ScriptPubKey); - if (toSign!.Inputs[0].PrevOut.Hash != toSpend.GetHash()) - { - return false; - } - - if (toSign.Inputs.Count > 1) - { - if (proofOfFundsLookup is null) - { - return false; - } - - var utxosToLookup = toSign.Inputs.Skip(1).Select(x => x.PrevOut).ToArray(); - var lookup = await proofOfFundsLookup(utxosToLookup); - if (lookup.Length != utxosToLookup.Length) - { - return false; - } - - return toSign.CreateValidator(toSpend.Outputs.Concat(lookup).ToArray()).ValidateInputs() - .All(x => x.Error is null); - } - - return toSign.CreateValidator(toSpend.Outputs.ToArray()).ValidateInputs().All(x => x.Error is null); - } - return false; - } - } -} -#endif diff --git a/NBitcoin/BitcoinAddress.cs b/NBitcoin/BitcoinAddress.cs index 8916cd4c78..2778693202 100644 --- a/NBitcoin/BitcoinAddress.cs +++ b/NBitcoin/BitcoinAddress.cs @@ -85,7 +85,7 @@ protected override Script GeneratePaymentScript() /// /// Base58 representation of a bitcoin address /// - public abstract class BitcoinAddress : IDestination, IBitcoinString + public abstract partial class BitcoinAddress : IDestination, IBitcoinString { /// /// Detect whether the input base58 is a pubkey hash or a script hash diff --git a/NBitcoin/BitcoinAddress.partial.cs b/NBitcoin/BitcoinAddress.partial.cs new file mode 100644 index 0000000000..e674567f12 --- /dev/null +++ b/NBitcoin/BitcoinAddress.partial.cs @@ -0,0 +1,89 @@ +#if HAS_SPAN +#nullable enable +using NBitcoin.BIP322; +using NBitcoin.Crypto; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + + +namespace NBitcoin +{ + public abstract partial class BitcoinAddress + { + public bool VerifyBIP322(string message, string signature, Coin[]? fundProofOutputs = null) + { + var sig = BIP322Signature.Parse(signature, Network); + return VerifyBIP322(message, sig, fundProofOutputs); + } + static readonly ScriptVerify BIP322ScriptVerify = ScriptVerify.Const_ScriptCode + | ScriptVerify.LowS + | ScriptVerify.StrictEnc + | ScriptVerify.NullFail + | ScriptVerify.MinimalData + | ScriptVerify.CleanStack + | ScriptVerify.P2SH + | ScriptVerify.Witness + | ScriptVerify.Taproot + | ScriptVerify.MinimalIf; + public bool VerifyBIP322(string message, BIP322.BIP322Signature signature, Coin[]? fundProofOutputs = null) + { + var messageBytes = Encoding.UTF8.GetBytes(message); + if (signature is BIP322.BIP322Signature.Simple { WitnessScript: var script }) + { + if (script.PushCount < 2 && !ScriptPubKey.IsScriptType(ScriptType.Taproot)) + { + return false; + } + var psbtToSign = Key.CreateToSignPSBT(Network, Key.CreateMessageHash(message, HashType.BIP322), ScriptPubKey); + psbtToSign.Inputs[0].FinalScriptWitness = script; + if (this is BitcoinScriptAddress) + { + var withScriptParams = PayToWitPubKeyHashTemplate.Instance.ExtractWitScriptParameters(script); + if (withScriptParams is null) + return false; + psbtToSign.Inputs[0].FinalScriptSig = PayToScriptHashTemplate.Instance.GenerateScriptSig(new Op[0], withScriptParams.Hash.ScriptPubKey); + } + return psbtToSign.Inputs[0].VerifyScript(BIP322ScriptVerify, psbtToSign.PrecomputeTransactionData(), out _); + } + else if (signature is BIP322.BIP322Signature.Legacy { CompactSignature: var sig }) + { + try + { + if (!ScriptPubKey.IsScriptType(ScriptType.P2PKH)) + { + return false; + } + var hash = Key.CreateMessageHash(message, HashType.Legacy); + var k = sig.RecoverPubKey(hash); + if (k.GetAddress(ScriptPubKeyType.Legacy, Network) != this) + { + return false; + } + return ECDSASignature.TryParseFromCompact(sig.Signature, out var ecSig) && k.Verify(hash, ecSig); + } + catch + { + return false; + } + } + else if (signature is BIP322.BIP322Signature.Full { SignedTransaction: var toSign } full) + { + fundProofOutputs ??= Array.Empty(); + var toSignPSBT = Key.CreateToSignPSBT(Network, Key.CreateMessageHash(message, HashType.BIP322), ScriptPubKey, additionalInputs: fundProofOutputs); + toSignPSBT.AddCoins(fundProofOutputs); + for (int i = 0; i < toSignPSBT.Inputs.Count; i++) + { + toSignPSBT.Inputs[i].FinalScriptWitness = toSign.Inputs[i].WitScript; + toSignPSBT.Inputs[i].FinalScriptSig = toSign.Inputs[i].ScriptSig; + } + var txData = toSignPSBT.PrecomputeTransactionData(); + return toSignPSBT.Inputs.Select(i => i.VerifyScript(BIP322ScriptVerify, txData, out _)).All(o => o); + } + return false; + } + } +} +#endif diff --git a/NBitcoin/Key.BIP322.cs b/NBitcoin/Key.BIP322.cs new file mode 100644 index 0000000000..71fadd3f00 --- /dev/null +++ b/NBitcoin/Key.BIP322.cs @@ -0,0 +1,133 @@ +#if HAS_SPAN +#nullable enable +using NBitcoin.BIP322; +using NBitcoin.Crypto; +using NBitcoin.Protocol; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace NBitcoin +{ + public partial class Key + { + private static string TAG = "BIP0322-signed-message"; + + private static byte[] BITCOIN_SIGNED_MESSAGE_HEADER_BYTES => + Encoding.UTF8.GetBytes("Bitcoin Signed Message:\n"); + + public static uint256 CreateMessageHash(string message, HashType type = HashType.BIP322) => + CreateMessageHash(Encoding.UTF8.GetBytes(message), type); + + public static uint256 CreateMessageHash(byte[] message, HashType type) + { + if (type == HashType.Legacy) + { + var ms = new MemoryStream(); + ms.WriteByte((byte)BITCOIN_SIGNED_MESSAGE_HEADER_BYTES.Length); + ms.Write(BITCOIN_SIGNED_MESSAGE_HEADER_BYTES, 0, BITCOIN_SIGNED_MESSAGE_HEADER_BYTES.Length); + + var size = new VarInt((ulong)message.Length).ToBytes(); + ms.Write(size, 0, size.Length); + ms.Write(message, 0, message.Length); + return Hashes.DoubleSHA256(ms.ToArray()); + } + else + { + using Secp256k1.SHA256 sha = new Secp256k1.SHA256(); + sha.InitializeTagged(TAG); + sha.Write(message); + return new uint256(sha.GetHash(), false); + } + } + + internal static PSBT CreateToSignPSBT( + Network network, uint256 messageHash, Script scriptPubKey, + uint version = 0, uint lockTime = 0, uint sequence = 0, Coin[]? additionalInputs = null) + { + var toSpend = network.CreateTransaction(); + toSpend.Version = 0; + toSpend.LockTime = 0; + toSpend.Inputs.Add(new TxIn(new OutPoint(uint256.Zero, 0xFFFFFFFF), new Script(OpcodeType.OP_0, Op.GetPushOp(messageHash.ToBytes(false)))) + { + Sequence = 0, + WitScript = WitScript.Empty, + }); + toSpend.Outputs.Add(new TxOut(Money.Zero, scriptPubKey)); + var toSpendTxId = toSpend.GetHash(); + var toSign = network.CreateTransaction(); + toSign.Version = version; + toSign.LockTime = lockTime; + toSign.Inputs.Add(new TxIn(new OutPoint(toSpendTxId, 0)) + { + Sequence = sequence + }); + additionalInputs ??= additionalInputs ?? Array.Empty(); + + foreach (var input in additionalInputs) + { + toSign.Inputs.Add(new TxIn(input.Outpoint, Script.Empty) + { + Sequence = sequence, + }); + } + toSign.Outputs.Add(new TxOut(Money.Zero, new Script(OpcodeType.OP_RETURN))); + var psbt = PSBT.FromTransaction(toSign, network); + psbt.AddTransactions(toSpend); + psbt.AddCoins(additionalInputs); + return psbt; + } + + public BIP322.BIP322Signature SignBIP322(BitcoinAddress address, string message, SignatureType type) + { + var messageHash = CreateMessageHash(message, + type == SignatureType.Legacy ? HashType.Legacy : HashType.BIP322); + var network = address.Network; + switch (type) + { + case SignatureType.Legacy when !address.ScriptPubKey.IsScriptType(ScriptType.P2PKH): + throw new InvalidOperationException("Legacy signing is only supported for P2PKH scripts."); + case SignatureType.Legacy: + { + var sig = SignCompact(messageHash); + var recovered = PubKey.RecoverCompact(messageHash, sig); + if (recovered != PubKey) + { + throw new InvalidOperationException("Invalid signature."); + } + return new BIP322.BIP322Signature.Legacy(IsCompressed, sig, address.Network); + } + } + + var toSignPSBT = CreateToSignPSBT(network, messageHash, address.ScriptPubKey); + toSignPSBT.AddScripts(this.GetScriptPubKey(ScriptPubKeyType.Segwit)); + toSignPSBT.SignWithKeys(this); + + Transaction toSignTx; + try + { + toSignPSBT.Finalize(); + toSignTx = toSignPSBT.ExtractTransaction(); + } + catch + { + throw new InvalidOperationException("Failed to sign the message. Did you forget to provide a redeem script?"); + } + + BIP322.BIP322Signature result = + type == SignatureType.Simple ? new BIP322.BIP322Signature.Simple(toSignTx.Inputs[0].WitScript, address.Network) + : new BIP322.BIP322Signature.Full(toSignTx, address.Network); + + if (!address.VerifyBIP322(message, result!)) + { + throw new InvalidOperationException("Could not produce a valid signature."); + } + + return result; + } + } +} +#endif diff --git a/NBitcoin/Key.cs b/NBitcoin/Key.cs index e91bb63fd6..1233ad400c 100644 --- a/NBitcoin/Key.cs +++ b/NBitcoin/Key.cs @@ -9,7 +9,7 @@ #endif namespace NBitcoin { - public class Key : IDestination, IDisposable + public partial class Key : IDestination, IDisposable { private const int KEY_SIZE = 32; private readonly static uint256 N = uint256.Parse("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141");