From 75a358a5d4d19909520c389cd7e2935fd5801660 Mon Sep 17 00:00:00 2001 From: Rowan de Haas Date: Thu, 8 Apr 2021 16:11:27 +1000 Subject: [PATCH 01/17] Add SCL to compiler --- .../Compilation/ReferencedAssemblyResolver.cs | 3 ++- .../Stratis.SmartContracts.CLR.csproj | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Stratis.SmartContracts.CLR/Compilation/ReferencedAssemblyResolver.cs b/src/Stratis.SmartContracts.CLR/Compilation/ReferencedAssemblyResolver.cs index 76a05075a0..53a73676d7 100644 --- a/src/Stratis.SmartContracts.CLR/Compilation/ReferencedAssemblyResolver.cs +++ b/src/Stratis.SmartContracts.CLR/Compilation/ReferencedAssemblyResolver.cs @@ -23,7 +23,8 @@ public static class ReferencedAssemblyResolver Core, typeof(SmartContract).Assembly, typeof(Enumerable).Assembly, - typeof(IStandardToken).Assembly + typeof(IStandardToken).Assembly, + typeof(SCL.Base.Operations).Assembly }; } } \ No newline at end of file diff --git a/src/Stratis.SmartContracts.CLR/Stratis.SmartContracts.CLR.csproj b/src/Stratis.SmartContracts.CLR/Stratis.SmartContracts.CLR.csproj index bc62647bbf..eb42b78b56 100644 --- a/src/Stratis.SmartContracts.CLR/Stratis.SmartContracts.CLR.csproj +++ b/src/Stratis.SmartContracts.CLR/Stratis.SmartContracts.CLR.csproj @@ -17,6 +17,7 @@ + From 01db9c1483f66c73ed1f6642c767c28cf73eed06 Mon Sep 17 00:00:00 2001 From: Rowan de Haas Date: Thu, 8 Apr 2021 16:35:51 +1000 Subject: [PATCH 02/17] Add SCL to validation --- .../DeterminismPolicy.cs | 3 ++- src/Stratis.SmartContracts.CLR.Validation/FormatPolicy.cs | 3 ++- .../Stratis.SmartContracts.CLR.Validation.csproj | 4 ++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Stratis.SmartContracts.CLR.Validation/DeterminismPolicy.cs b/src/Stratis.SmartContracts.CLR.Validation/DeterminismPolicy.cs index 28b28af563..38ec0cd561 100644 --- a/src/Stratis.SmartContracts.CLR.Validation/DeterminismPolicy.cs +++ b/src/Stratis.SmartContracts.CLR.Validation/DeterminismPolicy.cs @@ -14,7 +14,8 @@ public static class DeterminismPolicy public static WhitelistPolicy WhitelistPolicy = new WhitelistPolicy() .Namespace(nameof(System), AccessPolicy.Denied, SystemPolicy) .Namespace(typeof(RuntimeHelpers).Namespace, AccessPolicy.Denied, CompilerServicesPolicy) - .Namespace(typeof(SmartContract).Namespace, AccessPolicy.Allowed, SmartContractsPolicy); + .Namespace(typeof(SmartContract).Namespace, AccessPolicy.Allowed, SmartContractsPolicy) + .Namespace(typeof(SCL.Base.Operations).Namespace, AccessPolicy.Allowed); public static ValidationPolicy Default = new ValidationPolicy() .WhitelistValidator(WhitelistPolicy) diff --git a/src/Stratis.SmartContracts.CLR.Validation/FormatPolicy.cs b/src/Stratis.SmartContracts.CLR.Validation/FormatPolicy.cs index 037c833857..7140fc1698 100644 --- a/src/Stratis.SmartContracts.CLR.Validation/FormatPolicy.cs +++ b/src/Stratis.SmartContracts.CLR.Validation/FormatPolicy.cs @@ -23,7 +23,8 @@ public static class FormatPolicy Core, typeof(SmartContract).Assembly, typeof(Enumerable).Assembly, - typeof(IStandardToken).Assembly + typeof(IStandardToken).Assembly, + typeof(SCL.Base.Operations).Assembly }; public static ValidationPolicy Default = new ValidationPolicy() diff --git a/src/Stratis.SmartContracts.CLR.Validation/Stratis.SmartContracts.CLR.Validation.csproj b/src/Stratis.SmartContracts.CLR.Validation/Stratis.SmartContracts.CLR.Validation.csproj index a1640e41ac..c504994c0a 100644 --- a/src/Stratis.SmartContracts.CLR.Validation/Stratis.SmartContracts.CLR.Validation.csproj +++ b/src/Stratis.SmartContracts.CLR.Validation/Stratis.SmartContracts.CLR.Validation.csproj @@ -13,4 +13,8 @@ + + + + From 5a00154fd7d9e18e51ce0cc407f1bf6be8e1d27b Mon Sep 17 00:00:00 2001 From: Rowan de Haas Date: Thu, 8 Apr 2021 17:04:32 +1000 Subject: [PATCH 03/17] Add tests --- .../ContractCompilerTests.cs | 3 +- .../ContractExecutorTests.cs | 6 +++ .../SmartContracts/LibraryTest.cs | 16 ++++++++ .../Stratis.SmartContracts.CLR.Tests.csproj | 3 ++ .../SmartContractDeterminismValidatorTests.cs | 40 ++++++++++++++++++- 5 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 src/Stratis.SmartContracts.CLR.Tests/SmartContracts/LibraryTest.cs diff --git a/src/Stratis.SmartContracts.CLR.Tests/ContractCompilerTests.cs b/src/Stratis.SmartContracts.CLR.Tests/ContractCompilerTests.cs index 5a30e83cc5..7976d4fadc 100644 --- a/src/Stratis.SmartContracts.CLR.Tests/ContractCompilerTests.cs +++ b/src/Stratis.SmartContracts.CLR.Tests/ContractCompilerTests.cs @@ -33,12 +33,13 @@ public void SmartContract_ReferenceResolver_HasCorrectAssemblies() { List allowedAssemblies = ReferencedAssemblyResolver.AllowedAssemblies.ToList(); - Assert.Equal(5, allowedAssemblies.Count); + Assert.Equal(6, allowedAssemblies.Count); Assert.Contains(allowedAssemblies, a => a.GetName().Name == "System.Runtime"); Assert.Contains(allowedAssemblies, a => a.GetName().Name == "System.Private.CoreLib"); Assert.Contains(allowedAssemblies, a => a.GetName().Name == "Stratis.SmartContracts"); Assert.Contains(allowedAssemblies, a => a.GetName().Name == "System.Linq"); Assert.Contains(allowedAssemblies, a => a.GetName().Name == "Stratis.SmartContracts.Standards"); + Assert.Contains(allowedAssemblies, a => a.GetName().Name == "Stratis.SCL"); } [Fact] diff --git a/src/Stratis.SmartContracts.CLR.Tests/ContractExecutorTests.cs b/src/Stratis.SmartContracts.CLR.Tests/ContractExecutorTests.cs index a17cb0059b..ab98de8824 100644 --- a/src/Stratis.SmartContracts.CLR.Tests/ContractExecutorTests.cs +++ b/src/Stratis.SmartContracts.CLR.Tests/ContractExecutorTests.cs @@ -333,6 +333,12 @@ public void Execute_MultipleIfElseBlocks_ExecutionSucceeds() AssertSuccessfulContractMethodExecution(nameof(MultipleIfElseBlocks), nameof(MultipleIfElseBlocks.PersistNormalizeValue), new object[] { "z" }); } + [Fact] + public void Execute_LibraryContract_ExecutionSucceeds() + { + AssertSuccessfulContractMethodExecution(nameof(LibraryTest), nameof(LibraryTest.Exists)); + } + private void AssertSuccessfulContractMethodExecution(string contractName, string methodName, object[] methodParameters = null, string expectedReturn = null) { var transactionValue = (Money)100; diff --git a/src/Stratis.SmartContracts.CLR.Tests/SmartContracts/LibraryTest.cs b/src/Stratis.SmartContracts.CLR.Tests/SmartContracts/LibraryTest.cs new file mode 100644 index 0000000000..9b48635de2 --- /dev/null +++ b/src/Stratis.SmartContracts.CLR.Tests/SmartContracts/LibraryTest.cs @@ -0,0 +1,16 @@ +using Stratis.SmartContracts; +using Base = Stratis.SCL.Base; + +[Deploy] +public class LibraryTest : SmartContract +{ + public LibraryTest(ISmartContractState state) : base(state) + { + Base.Operations.Noop(); + } + + public void Exists() + { + State.SetBool("Exists", true); + } +} diff --git a/src/Stratis.SmartContracts.CLR.Tests/Stratis.SmartContracts.CLR.Tests.csproj b/src/Stratis.SmartContracts.CLR.Tests/Stratis.SmartContracts.CLR.Tests.csproj index 6a4b6b4f7d..0ae485aca5 100644 --- a/src/Stratis.SmartContracts.CLR.Tests/Stratis.SmartContracts.CLR.Tests.csproj +++ b/src/Stratis.SmartContracts.CLR.Tests/Stratis.SmartContracts.CLR.Tests.csproj @@ -74,6 +74,9 @@ Always + + Always + PreserveNewest diff --git a/src/Stratis.SmartContracts.CLR.Validation.Tests/SmartContractDeterminismValidatorTests.cs b/src/Stratis.SmartContracts.CLR.Validation.Tests/SmartContractDeterminismValidatorTests.cs index 5971b9029c..9ed64d361c 100644 --- a/src/Stratis.SmartContracts.CLR.Validation.Tests/SmartContractDeterminismValidatorTests.cs +++ b/src/Stratis.SmartContracts.CLR.Validation.Tests/SmartContractDeterminismValidatorTests.cs @@ -834,6 +834,44 @@ public Test(ISmartContractState state): base(state) Assert.False(result.IsValid); Assert.NotEmpty(result.Errors); Assert.True(result.Errors.All(e => e is ModuleDefinitionValidationResult)); - } + } + + [Fact] + public void SmartContractValidator_Allows_SCL() + { + var adjustedSource = @" +using Stratis.SmartContracts; +using Base = Stratis.SCL.Base; + +[Deploy] +public class LibraryTest : SmartContract +{ + public LibraryTest(ISmartContractState state) : base(state) + { + Base.Operations.Noop(); + } + + public void Exists() + { + State.SetBool(""Exists"", true); + } +}"; + ContractCompilationResult compilationResult = ContractCompiler.Compile(adjustedSource); + Assert.True(compilationResult.Success); + + byte[] assemblyBytes = compilationResult.Compilation; + IContractModuleDefinition decompilation = ContractDecompiler.GetModuleDefinition(assemblyBytes).Value; + + // Add a module reference + decompilation.ModuleDefinition.ModuleReferences.Add(new ModuleReference("Test.dll")); + + var moduleDefinition = decompilation.ModuleDefinition; + + SmartContractValidationResult result = new SmartContractValidator().Validate(moduleDefinition); + + Assert.False(result.IsValid); + Assert.NotEmpty(result.Errors); + Assert.True(result.Errors.All(e => e is ModuleDefinitionValidationResult)); + } } } \ No newline at end of file From c14e0a32e617dba787092394e0ea7b5a042501d2 Mon Sep 17 00:00:00 2001 From: Rowan de Haas Date: Thu, 8 Apr 2021 18:53:53 +1000 Subject: [PATCH 04/17] Add ECRecover and Keccak256 --- src/Stratis.SCL/ECRecover.cs | 53 ++++++++++++++++++++++++++++++ src/Stratis.SCL/SHA3.cs | 17 ++++++++++ src/Stratis.SCL/Stratis.SCL.csproj | 15 +++++++++ 3 files changed, 85 insertions(+) create mode 100644 src/Stratis.SCL/ECRecover.cs create mode 100644 src/Stratis.SCL/SHA3.cs create mode 100644 src/Stratis.SCL/Stratis.SCL.csproj diff --git a/src/Stratis.SCL/ECRecover.cs b/src/Stratis.SCL/ECRecover.cs new file mode 100644 index 0000000000..25cdd7c271 --- /dev/null +++ b/src/Stratis.SCL/ECRecover.cs @@ -0,0 +1,53 @@ +using System; +using NBitcoin; +using Stratis.SmartContracts; + +namespace Stratis.SCL.Crypto +{ + public static class ECRecover + { + /// + /// Retrieves the address of the signer of an ECDSA signature. + /// + /// + /// The ECDSA signature prepended with header information specifying the correct value of recId. + /// The Address for the signer of a signature. + public static Address GetSigner(byte[] message, byte[] signature) + { + uint256 hashedUint256 = GetUint256FromMessage(message); + + PubKey pubKey = PubKey.RecoverCompact(hashedUint256, signature); + + return CreateAddress(pubKey.Hash.ToBytes()); + } + + /// + /// Signs a message, returning an ECDSA signature. + /// + /// The private key used to sign the message. + /// The complete message to be signed. + /// The ECDSA signature prepended with header information specifying the correct value of recId. + public static byte[] SignMessage(Key privateKey, byte[] message) + { + uint256 hashedUint256 = GetUint256FromMessage(message); + + return privateKey.SignCompact(hashedUint256); + } + + private static uint256 GetUint256FromMessage(byte[] message) + { + return new uint256(SHA3.Keccak256(message)); + } + + private static Address CreateAddress(byte[] bytes) + { + uint pn0 = BitConverter.ToUInt32(bytes, 0); + uint pn1 = BitConverter.ToUInt32(bytes, 4); + uint pn2 = BitConverter.ToUInt32(bytes, 8); + uint pn3 = BitConverter.ToUInt32(bytes, 12); + uint pn4 = BitConverter.ToUInt32(bytes, 16); + + return new Address(pn0, pn1, pn2, pn3, pn4); + } + } +} diff --git a/src/Stratis.SCL/SHA3.cs b/src/Stratis.SCL/SHA3.cs new file mode 100644 index 0000000000..8e1071dc3d --- /dev/null +++ b/src/Stratis.SCL/SHA3.cs @@ -0,0 +1,17 @@ +using HashLib; + +namespace Stratis.SCL.Crypto +{ + public static class SHA3 + { + /// + /// Returns a 32-byte Keccak256 hash of the given bytes. + /// + /// + /// + public static byte[] Keccak256(byte[] input) + { + return HashFactory.Crypto.SHA3.CreateKeccak256().ComputeBytes(input).GetBytes(); + } + } +} diff --git a/src/Stratis.SCL/Stratis.SCL.csproj b/src/Stratis.SCL/Stratis.SCL.csproj new file mode 100644 index 0000000000..8e44d90618 --- /dev/null +++ b/src/Stratis.SCL/Stratis.SCL.csproj @@ -0,0 +1,15 @@ + + + + netcoreapp3.1 + + + + + + + + + + + From dab82f43fd963ddd4276efdc296354e7589ed814 Mon Sep 17 00:00:00 2001 From: Rowan de Haas Date: Thu, 8 Apr 2021 19:00:39 +1000 Subject: [PATCH 05/17] Add EcRecover integration tests --- .../ECRecoverTests.cs | 145 ++++++++++++++++++ .../SmartContracts/EcRecoverContract.cs | 28 ++++ ...tis.SmartContracts.IntegrationTests.csproj | 3 + 3 files changed, 176 insertions(+) create mode 100644 src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs create mode 100644 src/Stratis.SmartContracts.IntegrationTests/SmartContracts/EcRecoverContract.cs diff --git a/src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs b/src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs new file mode 100644 index 0000000000..88a2ec2a6d --- /dev/null +++ b/src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs @@ -0,0 +1,145 @@ +using System.Threading.Tasks; +using Stratis.Bitcoin.Features.SmartContracts.Models; +using Stratis.SmartContracts.CLR; +using Stratis.SmartContracts.CLR.Compilation; +using Stratis.SmartContracts.CLR.Serialization; +using Stratis.SmartContracts.Core; +using Stratis.SmartContracts.Tests.Common.MockChain; +using Xunit; +using EcRecoverProvider = Stratis.SCL.Crypto.ECRecover; +using Key = NBitcoin.Key; + +namespace Stratis.SmartContracts.IntegrationTests +{ + public class ECRecoverTests + { + // 2 things to test: + + // 1) That we have the ECDSA code and can make it available. + + [Fact] + public void CanSignAndRetrieveSender() + { + using (PoWMockChain chain = new PoWMockChain(1)) + { + var network = chain.Nodes[0].CoreNode.FullNode.Network; + var privateKey = new Key(); + Address address = privateKey.PubKey.GetAddress(network).ToString().ToAddress(network); + byte[] message = new byte[] { 0x69, 0x76, 0xAA }; + + // Sign a message + byte[] offChainSignature = EcRecoverProvider.SignMessage(privateKey, message); + + // Get the address out of the signature + Address recoveredAddress = EcRecoverProvider.GetSigner(message, offChainSignature); + + // Check that the address matches that generated from the private key. + Assert.Equal(address, recoveredAddress); + } + } + + [Fact] + public void CanCallEcRecoverContractWithValidSignatureAsync() + { + using (PoWMockChain chain = new PoWMockChain(2)) + { + var node1 = chain.Nodes[0]; + + node1.MineBlocks(1); + + var network = chain.Nodes[0].CoreNode.FullNode.Network; + + var privateKey = new Key(); + string address = privateKey.PubKey.GetAddress(network).ToString(); + byte[] message = new byte[] { 0x69, 0x76, 0xAA }; + byte[] signature = EcRecoverProvider.SignMessage(privateKey, message); + + // TODO: If the incorrect parameters are passed to the constructor, the contract does not get properly created ('Method does not exist on contract'), but a success response is still returned? + + byte[] contract = ContractCompiler.CompileFile("SmartContracts/EcRecoverContract.cs").Compilation; + string[] createParameters = new string[] { string.Format("{0}#{1}", (int)MethodParameterDataType.Address, address) }; + BuildCreateContractTransactionResponse createResult = node1.SendCreateContractTransaction(contract, 1, createParameters); + + Assert.NotNull(createResult); + Assert.True(createResult.Success); + + node1.WaitMempoolCount(1); + node1.MineBlocks(1); + + string[] callParameters = new string[] + { + string.Format("{0}#{1}", (int)MethodParameterDataType.ByteArray, message.ToHexString()), + string.Format("{0}#{1}", (int)MethodParameterDataType.ByteArray, signature.ToHexString()) + }; + + BuildCallContractTransactionResponse response = node1.SendCallContractTransaction("CheckThirdPartySignature", createResult.NewContractAddress, 1, callParameters); + Assert.NotNull(response); + Assert.True(response.Success); + + node1.WaitMempoolCount(1); + node1.MineBlocks(1); + + ReceiptResponse receipt = node1.GetReceipt(response.TransactionId.ToString()); + + Assert.NotNull(receipt); + Assert.True(receipt.Success); + Assert.Equal("True", receipt.ReturnValue); + } + } + + [Fact] + public void CanCallEcRecoverContractWithInvalidSignatureAsync() + { + using (PoWMockChain chain = new PoWMockChain(2)) + { + var node1 = chain.Nodes[0]; + + node1.MineBlocks(1); + + var network = chain.Nodes[0].CoreNode.FullNode.Network; + + var privateKey = new Key(); + string address = privateKey.PubKey.GetAddress(network).ToString(); + byte[] message = new byte[] { 0x69, 0x76, 0xAA }; + + // Make the signature with a key unrelated to the third party signer for the contract. + byte[] signature = EcRecoverProvider.SignMessage(new Key(), message); + + // TODO: If the incorrect parameters are passed to the constructor, the contract does not get properly created ('Method does not exist on contract'), but a success response is still returned? + + byte[] contract = ContractCompiler.CompileFile("SmartContracts/EcRecoverContract.cs").Compilation; + string[] createParameters = new string[] { string.Format("{0}#{1}", (int)MethodParameterDataType.Address, address) }; + BuildCreateContractTransactionResponse createResult = node1.SendCreateContractTransaction(contract, 1, createParameters); + + Assert.NotNull(createResult); + Assert.True(createResult.Success); + + node1.WaitMempoolCount(1); + node1.MineBlocks(1); + + string[] callParameters = new string[] + { + string.Format("{0}#{1}", (int)MethodParameterDataType.ByteArray, message.ToHexString()), + string.Format("{0}#{1}", (int)MethodParameterDataType.ByteArray, signature.ToHexString()) + }; + + BuildCallContractTransactionResponse response = node1.SendCallContractTransaction("CheckThirdPartySignature", createResult.NewContractAddress, 1, callParameters); + Assert.NotNull(response); + Assert.True(response.Success); + + node1.WaitMempoolCount(1); + node1.MineBlocks(1); + + ReceiptResponse receipt = node1.GetReceipt(response.TransactionId.ToString()); + + Assert.NotNull(receipt); + Assert.True(receipt.Success); + Assert.Equal("False", receipt.ReturnValue); + } + } + + // 2) That we can enable the method in new contracts without affecting the older contracts + + // TODO + } +} \ No newline at end of file diff --git a/src/Stratis.SmartContracts.IntegrationTests/SmartContracts/EcRecoverContract.cs b/src/Stratis.SmartContracts.IntegrationTests/SmartContracts/EcRecoverContract.cs new file mode 100644 index 0000000000..276993563c --- /dev/null +++ b/src/Stratis.SmartContracts.IntegrationTests/SmartContracts/EcRecoverContract.cs @@ -0,0 +1,28 @@ +using Stratis.SmartContracts; +using EcRecover = Stratis.SCL.Crypto.ECRecover; + +public class EcRecoverContract : SmartContract +{ + public Address ThirdPartySigner + { + get + { + return this.State.GetAddress(nameof(this.ThirdPartySigner)); + } + set + { + this.State.SetAddress(nameof(this.ThirdPartySigner), value); + } + } + + public EcRecoverContract(ISmartContractState state, Address thirdPartySigner) : base(state) + { + this.ThirdPartySigner = thirdPartySigner; + } + + public bool CheckThirdPartySignature(byte[] message, byte[] signature) + { + Address signerOfMessage = EcRecover.GetSigner(message, signature); + return (signerOfMessage == this.ThirdPartySigner); + } +} \ No newline at end of file diff --git a/src/Stratis.SmartContracts.IntegrationTests/Stratis.SmartContracts.IntegrationTests.csproj b/src/Stratis.SmartContracts.IntegrationTests/Stratis.SmartContracts.IntegrationTests.csproj index 3910ccb5ad..8cd489c9e0 100644 --- a/src/Stratis.SmartContracts.IntegrationTests/Stratis.SmartContracts.IntegrationTests.csproj +++ b/src/Stratis.SmartContracts.IntegrationTests/Stratis.SmartContracts.IntegrationTests.csproj @@ -93,6 +93,9 @@ PreserveNewest + + Always + PreserveNewest From 99484a445ad569a8c0efec10f1629e0c08a3fb0d Mon Sep 17 00:00:00 2001 From: Rowan de Haas Date: Thu, 8 Apr 2021 19:08:33 +1000 Subject: [PATCH 06/17] Whitelist SCL.Crypto --- src/Stratis.SmartContracts.CLR.Validation/DeterminismPolicy.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Stratis.SmartContracts.CLR.Validation/DeterminismPolicy.cs b/src/Stratis.SmartContracts.CLR.Validation/DeterminismPolicy.cs index 38ec0cd561..9dfaf08246 100644 --- a/src/Stratis.SmartContracts.CLR.Validation/DeterminismPolicy.cs +++ b/src/Stratis.SmartContracts.CLR.Validation/DeterminismPolicy.cs @@ -15,7 +15,8 @@ public static class DeterminismPolicy .Namespace(nameof(System), AccessPolicy.Denied, SystemPolicy) .Namespace(typeof(RuntimeHelpers).Namespace, AccessPolicy.Denied, CompilerServicesPolicy) .Namespace(typeof(SmartContract).Namespace, AccessPolicy.Allowed, SmartContractsPolicy) - .Namespace(typeof(SCL.Base.Operations).Namespace, AccessPolicy.Allowed); + .Namespace(typeof(SCL.Base.Operations).Namespace, AccessPolicy.Allowed) + .Namespace(typeof(SCL.Crypto.ECRecover).Namespace, AccessPolicy.Allowed); public static ValidationPolicy Default = new ValidationPolicy() .WhitelistValidator(WhitelistPolicy) From 4ce2e6bb83293da3784b29973234f4a87fcc9abf Mon Sep 17 00:00:00 2001 From: Rowan de Haas Date: Thu, 8 Apr 2021 19:15:47 +1000 Subject: [PATCH 07/17] Remove SignMessage --- src/Stratis.SCL/ECRecover.cs | 13 ------------ .../ECRecoverTests.cs | 21 ++++++++++++++++--- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/Stratis.SCL/ECRecover.cs b/src/Stratis.SCL/ECRecover.cs index 25cdd7c271..a90f739df5 100644 --- a/src/Stratis.SCL/ECRecover.cs +++ b/src/Stratis.SCL/ECRecover.cs @@ -21,19 +21,6 @@ public static Address GetSigner(byte[] message, byte[] signature) return CreateAddress(pubKey.Hash.ToBytes()); } - /// - /// Signs a message, returning an ECDSA signature. - /// - /// The private key used to sign the message. - /// The complete message to be signed. - /// The ECDSA signature prepended with header information specifying the correct value of recId. - public static byte[] SignMessage(Key privateKey, byte[] message) - { - uint256 hashedUint256 = GetUint256FromMessage(message); - - return privateKey.SignCompact(hashedUint256); - } - private static uint256 GetUint256FromMessage(byte[] message) { return new uint256(SHA3.Keccak256(message)); diff --git a/src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs b/src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs index 88a2ec2a6d..6f6311b5e4 100644 --- a/src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs +++ b/src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs @@ -1,5 +1,7 @@ using System.Threading.Tasks; +using NBitcoin; using Stratis.Bitcoin.Features.SmartContracts.Models; +using Stratis.SCL.Crypto; using Stratis.SmartContracts.CLR; using Stratis.SmartContracts.CLR.Compilation; using Stratis.SmartContracts.CLR.Serialization; @@ -28,7 +30,7 @@ public void CanSignAndRetrieveSender() byte[] message = new byte[] { 0x69, 0x76, 0xAA }; // Sign a message - byte[] offChainSignature = EcRecoverProvider.SignMessage(privateKey, message); + byte[] offChainSignature = SignMessage(privateKey, message); // Get the address out of the signature Address recoveredAddress = EcRecoverProvider.GetSigner(message, offChainSignature); @@ -38,6 +40,19 @@ public void CanSignAndRetrieveSender() } } + /// + /// Signs a message, returning an ECDSA signature. + /// + /// The private key used to sign the message. + /// The complete message to be signed. + /// The ECDSA signature prepended with header information specifying the correct value of recId. + private static byte[] SignMessage(Key privateKey, byte[] message) + { + uint256 hashedUint256 = new uint256(SHA3.Keccak256(message)); + + return privateKey.SignCompact(hashedUint256); + } + [Fact] public void CanCallEcRecoverContractWithValidSignatureAsync() { @@ -52,7 +67,7 @@ public void CanCallEcRecoverContractWithValidSignatureAsync() var privateKey = new Key(); string address = privateKey.PubKey.GetAddress(network).ToString(); byte[] message = new byte[] { 0x69, 0x76, 0xAA }; - byte[] signature = EcRecoverProvider.SignMessage(privateKey, message); + byte[] signature = SignMessage(privateKey, message); // TODO: If the incorrect parameters are passed to the constructor, the contract does not get properly created ('Method does not exist on contract'), but a success response is still returned? @@ -103,7 +118,7 @@ public void CanCallEcRecoverContractWithInvalidSignatureAsync() byte[] message = new byte[] { 0x69, 0x76, 0xAA }; // Make the signature with a key unrelated to the third party signer for the contract. - byte[] signature = EcRecoverProvider.SignMessage(new Key(), message); + byte[] signature = SignMessage(new Key(), message); // TODO: If the incorrect parameters are passed to the constructor, the contract does not get properly created ('Method does not exist on contract'), but a success response is still returned? From 9298046e8660f7cdfda162480417b78452f75b46 Mon Sep 17 00:00:00 2001 From: Rowan de Haas Date: Thu, 8 Apr 2021 23:00:34 +1000 Subject: [PATCH 08/17] Add error handling and adjust signature --- src/Stratis.SCL/ECRecover.cs | 26 ++++++++--- .../ECRecoverTests.cs | 45 +++++++++++++------ .../SmartContracts/EcRecoverContract.cs | 2 +- 3 files changed, 54 insertions(+), 19 deletions(-) diff --git a/src/Stratis.SCL/ECRecover.cs b/src/Stratis.SCL/ECRecover.cs index a90f739df5..bbb74ee33b 100644 --- a/src/Stratis.SCL/ECRecover.cs +++ b/src/Stratis.SCL/ECRecover.cs @@ -11,14 +11,30 @@ public static class ECRecover /// /// /// The ECDSA signature prepended with header information specifying the correct value of recId. - /// The Address for the signer of a signature. - public static Address GetSigner(byte[] message, byte[] signature) + /// The Address for the signer of a signature. + /// A bool representing whether or not the signer was retrieved successfully. + public static bool TryGetSigner(byte[] message, byte[] signature, out Address address) { - uint256 hashedUint256 = GetUint256FromMessage(message); + address = Address.Zero; - PubKey pubKey = PubKey.RecoverCompact(hashedUint256, signature); + if (message == null || signature == null) + return false; - return CreateAddress(pubKey.Hash.ToBytes()); + // NBitcoin is very throwy + try + { + uint256 hashedUint256 = GetUint256FromMessage(message); + + PubKey pubKey = PubKey.RecoverCompact(hashedUint256, signature); + + address = CreateAddress(pubKey.Hash.ToBytes()); + + return true; + } + catch + { + return false; + } } private static uint256 GetUint256FromMessage(byte[] message) diff --git a/src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs b/src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs index 6f6311b5e4..28404a729f 100644 --- a/src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs +++ b/src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs @@ -6,6 +6,7 @@ using Stratis.SmartContracts.CLR.Compilation; using Stratis.SmartContracts.CLR.Serialization; using Stratis.SmartContracts.Core; +using Stratis.SmartContracts.Networks; using Stratis.SmartContracts.Tests.Common.MockChain; using Xunit; using EcRecoverProvider = Stratis.SCL.Crypto.ECRecover; @@ -22,22 +23,40 @@ public class ECRecoverTests [Fact] public void CanSignAndRetrieveSender() { - using (PoWMockChain chain = new PoWMockChain(1)) - { - var network = chain.Nodes[0].CoreNode.FullNode.Network; - var privateKey = new Key(); - Address address = privateKey.PubKey.GetAddress(network).ToString().ToAddress(network); - byte[] message = new byte[] { 0x69, 0x76, 0xAA }; + var network = new SmartContractsRegTest(); + var privateKey = new Key(); + Address address = privateKey.PubKey.GetAddress(network).ToString().ToAddress(network); + byte[] message = new byte[] { 0x69, 0x76, 0xAA }; - // Sign a message - byte[] offChainSignature = SignMessage(privateKey, message); + // Sign a message + byte[] offChainSignature = SignMessage(privateKey, message); - // Get the address out of the signature - Address recoveredAddress = EcRecoverProvider.GetSigner(message, offChainSignature); + // Get the address out of the signature + EcRecoverProvider.TryGetSigner(message, offChainSignature, out Address recoveredAddress); - // Check that the address matches that generated from the private key. - Assert.Equal(address, recoveredAddress); - } + // Check that the address matches that generated from the private key. + Assert.Equal(address, recoveredAddress); + } + + [Fact] + public void GetSigner_Returns_Address_Zero_When_Message_Or_Signature_Null() + { + var network = new SmartContractsRegTest(); + var privateKey = new Key(); + Address address = privateKey.PubKey.GetAddress(network).ToString().ToAddress(network); + byte[] message = new byte[] { 0x69, 0x76, 0xAA }; + + // Sign a message + byte[] offChainSignature = SignMessage(privateKey, message); + + // Get the address out of the signature + Assert.False(EcRecoverProvider.TryGetSigner(null, offChainSignature, out Address recoveredAddress)); + + Assert.Equal(Address.Zero, recoveredAddress); + + Assert.False(EcRecoverProvider.TryGetSigner(message, null, out Address recoveredAddress2)); + + Assert.Equal(Address.Zero, recoveredAddress2); } /// diff --git a/src/Stratis.SmartContracts.IntegrationTests/SmartContracts/EcRecoverContract.cs b/src/Stratis.SmartContracts.IntegrationTests/SmartContracts/EcRecoverContract.cs index 276993563c..2e40d6ef2f 100644 --- a/src/Stratis.SmartContracts.IntegrationTests/SmartContracts/EcRecoverContract.cs +++ b/src/Stratis.SmartContracts.IntegrationTests/SmartContracts/EcRecoverContract.cs @@ -22,7 +22,7 @@ public EcRecoverContract(ISmartContractState state, Address thirdPartySigner) : public bool CheckThirdPartySignature(byte[] message, byte[] signature) { - Address signerOfMessage = EcRecover.GetSigner(message, signature); + EcRecover.TryGetSigner(message, signature, out Address signerOfMessage); return (signerOfMessage == this.ThirdPartySigner); } } \ No newline at end of file From 3eb93e8c1ff9a50c51a2cb15f7076210176f886c Mon Sep 17 00:00:00 2001 From: Rowan de Haas Date: Fri, 9 Apr 2021 13:36:09 +1000 Subject: [PATCH 09/17] Remove unused reference --- src/Stratis.SmartContracts.CLR/Stratis.SmartContracts.CLR.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Stratis.SmartContracts.CLR/Stratis.SmartContracts.CLR.csproj b/src/Stratis.SmartContracts.CLR/Stratis.SmartContracts.CLR.csproj index eb42b78b56..bc62647bbf 100644 --- a/src/Stratis.SmartContracts.CLR/Stratis.SmartContracts.CLR.csproj +++ b/src/Stratis.SmartContracts.CLR/Stratis.SmartContracts.CLR.csproj @@ -17,7 +17,6 @@ - From 50ff163b1ff2cf39abdb6d9882840e2f81a4378e Mon Sep 17 00:00:00 2001 From: Rowan de Haas Date: Fri, 9 Apr 2021 15:06:05 +1000 Subject: [PATCH 10/17] Remove ECRecover implementation --- src/Stratis.SCL/ECRecover.cs | 46 ----- .../ECRecoverTests.cs | 179 ------------------ .../SmartContracts/EcRecoverContract.cs | 28 --- ...tis.SmartContracts.IntegrationTests.csproj | 3 - 4 files changed, 256 deletions(-) delete mode 100644 src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs delete mode 100644 src/Stratis.SmartContracts.IntegrationTests/SmartContracts/EcRecoverContract.cs diff --git a/src/Stratis.SCL/ECRecover.cs b/src/Stratis.SCL/ECRecover.cs index bbb74ee33b..0d3c83aa88 100644 --- a/src/Stratis.SCL/ECRecover.cs +++ b/src/Stratis.SCL/ECRecover.cs @@ -6,51 +6,5 @@ namespace Stratis.SCL.Crypto { public static class ECRecover { - /// - /// Retrieves the address of the signer of an ECDSA signature. - /// - /// - /// The ECDSA signature prepended with header information specifying the correct value of recId. - /// The Address for the signer of a signature. - /// A bool representing whether or not the signer was retrieved successfully. - public static bool TryGetSigner(byte[] message, byte[] signature, out Address address) - { - address = Address.Zero; - - if (message == null || signature == null) - return false; - - // NBitcoin is very throwy - try - { - uint256 hashedUint256 = GetUint256FromMessage(message); - - PubKey pubKey = PubKey.RecoverCompact(hashedUint256, signature); - - address = CreateAddress(pubKey.Hash.ToBytes()); - - return true; - } - catch - { - return false; - } - } - - private static uint256 GetUint256FromMessage(byte[] message) - { - return new uint256(SHA3.Keccak256(message)); - } - - private static Address CreateAddress(byte[] bytes) - { - uint pn0 = BitConverter.ToUInt32(bytes, 0); - uint pn1 = BitConverter.ToUInt32(bytes, 4); - uint pn2 = BitConverter.ToUInt32(bytes, 8); - uint pn3 = BitConverter.ToUInt32(bytes, 12); - uint pn4 = BitConverter.ToUInt32(bytes, 16); - - return new Address(pn0, pn1, pn2, pn3, pn4); - } } } diff --git a/src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs b/src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs deleted file mode 100644 index 28404a729f..0000000000 --- a/src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs +++ /dev/null @@ -1,179 +0,0 @@ -using System.Threading.Tasks; -using NBitcoin; -using Stratis.Bitcoin.Features.SmartContracts.Models; -using Stratis.SCL.Crypto; -using Stratis.SmartContracts.CLR; -using Stratis.SmartContracts.CLR.Compilation; -using Stratis.SmartContracts.CLR.Serialization; -using Stratis.SmartContracts.Core; -using Stratis.SmartContracts.Networks; -using Stratis.SmartContracts.Tests.Common.MockChain; -using Xunit; -using EcRecoverProvider = Stratis.SCL.Crypto.ECRecover; -using Key = NBitcoin.Key; - -namespace Stratis.SmartContracts.IntegrationTests -{ - public class ECRecoverTests - { - // 2 things to test: - - // 1) That we have the ECDSA code and can make it available. - - [Fact] - public void CanSignAndRetrieveSender() - { - var network = new SmartContractsRegTest(); - var privateKey = new Key(); - Address address = privateKey.PubKey.GetAddress(network).ToString().ToAddress(network); - byte[] message = new byte[] { 0x69, 0x76, 0xAA }; - - // Sign a message - byte[] offChainSignature = SignMessage(privateKey, message); - - // Get the address out of the signature - EcRecoverProvider.TryGetSigner(message, offChainSignature, out Address recoveredAddress); - - // Check that the address matches that generated from the private key. - Assert.Equal(address, recoveredAddress); - } - - [Fact] - public void GetSigner_Returns_Address_Zero_When_Message_Or_Signature_Null() - { - var network = new SmartContractsRegTest(); - var privateKey = new Key(); - Address address = privateKey.PubKey.GetAddress(network).ToString().ToAddress(network); - byte[] message = new byte[] { 0x69, 0x76, 0xAA }; - - // Sign a message - byte[] offChainSignature = SignMessage(privateKey, message); - - // Get the address out of the signature - Assert.False(EcRecoverProvider.TryGetSigner(null, offChainSignature, out Address recoveredAddress)); - - Assert.Equal(Address.Zero, recoveredAddress); - - Assert.False(EcRecoverProvider.TryGetSigner(message, null, out Address recoveredAddress2)); - - Assert.Equal(Address.Zero, recoveredAddress2); - } - - /// - /// Signs a message, returning an ECDSA signature. - /// - /// The private key used to sign the message. - /// The complete message to be signed. - /// The ECDSA signature prepended with header information specifying the correct value of recId. - private static byte[] SignMessage(Key privateKey, byte[] message) - { - uint256 hashedUint256 = new uint256(SHA3.Keccak256(message)); - - return privateKey.SignCompact(hashedUint256); - } - - [Fact] - public void CanCallEcRecoverContractWithValidSignatureAsync() - { - using (PoWMockChain chain = new PoWMockChain(2)) - { - var node1 = chain.Nodes[0]; - - node1.MineBlocks(1); - - var network = chain.Nodes[0].CoreNode.FullNode.Network; - - var privateKey = new Key(); - string address = privateKey.PubKey.GetAddress(network).ToString(); - byte[] message = new byte[] { 0x69, 0x76, 0xAA }; - byte[] signature = SignMessage(privateKey, message); - - // TODO: If the incorrect parameters are passed to the constructor, the contract does not get properly created ('Method does not exist on contract'), but a success response is still returned? - - byte[] contract = ContractCompiler.CompileFile("SmartContracts/EcRecoverContract.cs").Compilation; - string[] createParameters = new string[] { string.Format("{0}#{1}", (int)MethodParameterDataType.Address, address) }; - BuildCreateContractTransactionResponse createResult = node1.SendCreateContractTransaction(contract, 1, createParameters); - - Assert.NotNull(createResult); - Assert.True(createResult.Success); - - node1.WaitMempoolCount(1); - node1.MineBlocks(1); - - string[] callParameters = new string[] - { - string.Format("{0}#{1}", (int)MethodParameterDataType.ByteArray, message.ToHexString()), - string.Format("{0}#{1}", (int)MethodParameterDataType.ByteArray, signature.ToHexString()) - }; - - BuildCallContractTransactionResponse response = node1.SendCallContractTransaction("CheckThirdPartySignature", createResult.NewContractAddress, 1, callParameters); - Assert.NotNull(response); - Assert.True(response.Success); - - node1.WaitMempoolCount(1); - node1.MineBlocks(1); - - ReceiptResponse receipt = node1.GetReceipt(response.TransactionId.ToString()); - - Assert.NotNull(receipt); - Assert.True(receipt.Success); - Assert.Equal("True", receipt.ReturnValue); - } - } - - [Fact] - public void CanCallEcRecoverContractWithInvalidSignatureAsync() - { - using (PoWMockChain chain = new PoWMockChain(2)) - { - var node1 = chain.Nodes[0]; - - node1.MineBlocks(1); - - var network = chain.Nodes[0].CoreNode.FullNode.Network; - - var privateKey = new Key(); - string address = privateKey.PubKey.GetAddress(network).ToString(); - byte[] message = new byte[] { 0x69, 0x76, 0xAA }; - - // Make the signature with a key unrelated to the third party signer for the contract. - byte[] signature = SignMessage(new Key(), message); - - // TODO: If the incorrect parameters are passed to the constructor, the contract does not get properly created ('Method does not exist on contract'), but a success response is still returned? - - byte[] contract = ContractCompiler.CompileFile("SmartContracts/EcRecoverContract.cs").Compilation; - string[] createParameters = new string[] { string.Format("{0}#{1}", (int)MethodParameterDataType.Address, address) }; - BuildCreateContractTransactionResponse createResult = node1.SendCreateContractTransaction(contract, 1, createParameters); - - Assert.NotNull(createResult); - Assert.True(createResult.Success); - - node1.WaitMempoolCount(1); - node1.MineBlocks(1); - - string[] callParameters = new string[] - { - string.Format("{0}#{1}", (int)MethodParameterDataType.ByteArray, message.ToHexString()), - string.Format("{0}#{1}", (int)MethodParameterDataType.ByteArray, signature.ToHexString()) - }; - - BuildCallContractTransactionResponse response = node1.SendCallContractTransaction("CheckThirdPartySignature", createResult.NewContractAddress, 1, callParameters); - Assert.NotNull(response); - Assert.True(response.Success); - - node1.WaitMempoolCount(1); - node1.MineBlocks(1); - - ReceiptResponse receipt = node1.GetReceipt(response.TransactionId.ToString()); - - Assert.NotNull(receipt); - Assert.True(receipt.Success); - Assert.Equal("False", receipt.ReturnValue); - } - } - - // 2) That we can enable the method in new contracts without affecting the older contracts - - // TODO - } -} \ No newline at end of file diff --git a/src/Stratis.SmartContracts.IntegrationTests/SmartContracts/EcRecoverContract.cs b/src/Stratis.SmartContracts.IntegrationTests/SmartContracts/EcRecoverContract.cs deleted file mode 100644 index 2e40d6ef2f..0000000000 --- a/src/Stratis.SmartContracts.IntegrationTests/SmartContracts/EcRecoverContract.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Stratis.SmartContracts; -using EcRecover = Stratis.SCL.Crypto.ECRecover; - -public class EcRecoverContract : SmartContract -{ - public Address ThirdPartySigner - { - get - { - return this.State.GetAddress(nameof(this.ThirdPartySigner)); - } - set - { - this.State.SetAddress(nameof(this.ThirdPartySigner), value); - } - } - - public EcRecoverContract(ISmartContractState state, Address thirdPartySigner) : base(state) - { - this.ThirdPartySigner = thirdPartySigner; - } - - public bool CheckThirdPartySignature(byte[] message, byte[] signature) - { - EcRecover.TryGetSigner(message, signature, out Address signerOfMessage); - return (signerOfMessage == this.ThirdPartySigner); - } -} \ No newline at end of file diff --git a/src/Stratis.SmartContracts.IntegrationTests/Stratis.SmartContracts.IntegrationTests.csproj b/src/Stratis.SmartContracts.IntegrationTests/Stratis.SmartContracts.IntegrationTests.csproj index 8cd489c9e0..3910ccb5ad 100644 --- a/src/Stratis.SmartContracts.IntegrationTests/Stratis.SmartContracts.IntegrationTests.csproj +++ b/src/Stratis.SmartContracts.IntegrationTests/Stratis.SmartContracts.IntegrationTests.csproj @@ -93,9 +93,6 @@ PreserveNewest - - Always - PreserveNewest From 667cd1b86af8208cc426a5e731fef499ebda6bdf Mon Sep 17 00:00:00 2001 From: Rowan de Haas Date: Mon, 12 Apr 2021 20:58:37 +1000 Subject: [PATCH 11/17] Move SCL into Stratis.SmartContracts.CLR --- src/Stratis.SCL/Stratis.SCL.csproj | 15 --------------- .../Stratis.SmartContracts.CLR.Validation.csproj | 4 ---- .../SCL}/ECRecover.cs | 0 src/Stratis.SmartContracts.CLR/SCL/Operations.cs | 9 +++++++++ .../SCL}/SHA3.cs | 0 5 files changed, 9 insertions(+), 19 deletions(-) delete mode 100644 src/Stratis.SCL/Stratis.SCL.csproj rename src/{Stratis.SCL => Stratis.SmartContracts.CLR/SCL}/ECRecover.cs (100%) create mode 100644 src/Stratis.SmartContracts.CLR/SCL/Operations.cs rename src/{Stratis.SCL => Stratis.SmartContracts.CLR/SCL}/SHA3.cs (100%) diff --git a/src/Stratis.SCL/Stratis.SCL.csproj b/src/Stratis.SCL/Stratis.SCL.csproj deleted file mode 100644 index 8e44d90618..0000000000 --- a/src/Stratis.SCL/Stratis.SCL.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - netcoreapp3.1 - - - - - - - - - - - diff --git a/src/Stratis.SmartContracts.CLR.Validation/Stratis.SmartContracts.CLR.Validation.csproj b/src/Stratis.SmartContracts.CLR.Validation/Stratis.SmartContracts.CLR.Validation.csproj index c504994c0a..a1640e41ac 100644 --- a/src/Stratis.SmartContracts.CLR.Validation/Stratis.SmartContracts.CLR.Validation.csproj +++ b/src/Stratis.SmartContracts.CLR.Validation/Stratis.SmartContracts.CLR.Validation.csproj @@ -13,8 +13,4 @@ - - - - diff --git a/src/Stratis.SCL/ECRecover.cs b/src/Stratis.SmartContracts.CLR/SCL/ECRecover.cs similarity index 100% rename from src/Stratis.SCL/ECRecover.cs rename to src/Stratis.SmartContracts.CLR/SCL/ECRecover.cs diff --git a/src/Stratis.SmartContracts.CLR/SCL/Operations.cs b/src/Stratis.SmartContracts.CLR/SCL/Operations.cs new file mode 100644 index 0000000000..464fa45320 --- /dev/null +++ b/src/Stratis.SmartContracts.CLR/SCL/Operations.cs @@ -0,0 +1,9 @@ +using System; + +namespace Stratis.SCL.Base +{ + public static class Operations + { + public static void Noop() { } + } +} diff --git a/src/Stratis.SCL/SHA3.cs b/src/Stratis.SmartContracts.CLR/SCL/SHA3.cs similarity index 100% rename from src/Stratis.SCL/SHA3.cs rename to src/Stratis.SmartContracts.CLR/SCL/SHA3.cs From 80ded14826307f7903d644c82d0195917d93c281 Mon Sep 17 00:00:00 2001 From: Rowan de Haas Date: Mon, 12 Apr 2021 21:14:01 +1000 Subject: [PATCH 12/17] Move validation policies to Stratis.SmartContracts.CLR --- .../SmartContractTypeDefinitionValidator.cs | 41 ------------------- .../Validation}/DeterminismPolicy.cs | 2 +- .../Validation}/FormatPolicy.cs | 4 +- .../Validation}/Primitives.cs | 3 +- .../SmartContractDeterminismValidator.cs | 1 + .../SmartContractFormatValidator.cs | 3 +- .../Validation}/SmartContractValidator.cs | 8 ++-- 7 files changed, 13 insertions(+), 49 deletions(-) delete mode 100644 src/Stratis.SmartContracts.CLR.Validation/SmartContractTypeDefinitionValidator.cs rename src/{Stratis.SmartContracts.CLR.Validation => Stratis.SmartContracts.CLR/Validation}/DeterminismPolicy.cs (97%) rename src/{Stratis.SmartContracts.CLR.Validation => Stratis.SmartContracts.CLR/Validation}/FormatPolicy.cs (96%) rename src/{Stratis.SmartContracts.CLR.Validation => Stratis.SmartContracts.CLR/Validation}/Primitives.cs (83%) rename src/{Stratis.SmartContracts.CLR.Validation => Stratis.SmartContracts.CLR/Validation}/SmartContractDeterminismValidator.cs (94%) rename src/{Stratis.SmartContracts.CLR.Validation => Stratis.SmartContracts.CLR/Validation}/SmartContractFormatValidator.cs (84%) rename src/{Stratis.SmartContracts.CLR.Validation => Stratis.SmartContracts.CLR/Validation}/SmartContractValidator.cs (58%) diff --git a/src/Stratis.SmartContracts.CLR.Validation/SmartContractTypeDefinitionValidator.cs b/src/Stratis.SmartContracts.CLR.Validation/SmartContractTypeDefinitionValidator.cs deleted file mode 100644 index 739af9db3d..0000000000 --- a/src/Stratis.SmartContracts.CLR.Validation/SmartContractTypeDefinitionValidator.cs +++ /dev/null @@ -1,41 +0,0 @@ -//using System.Collections.Generic; -//using System.Linq; -//using Mono.Cecil; -//using Stratis.ModuleValidation.Net; -//using Stratis.ModuleValidation.Net.Format; - -//namespace Stratis.SmartContracts.CLR.Validation -//{ -// /// -// /// Validates the Type definitions contained within a module definition -// /// -// public class SmartContractTypeDefinitionValidator : IModuleDefinitionValidator -// { -// private static readonly IEnumerable TypeDefinitionValidators = new List -// { -// new NestedTypeValidator(), -// new NamespaceValidator(), -// new InheritsSmartContractValidator(), -// new SingleConstructorValidator(), -// new ConstructorParamValidator(), -// new AsyncValidator() -// }; - -// public IEnumerable Validate(ModuleDefinition moduleDefinition) -// { -// var errors = new List(); - -// IEnumerable contractTypes = moduleDefinition.Types.Where(x => !TypesToIgnoreUtil.Ignore.Contains(x.FullName)).ToList(); - -// foreach(TypeDefinition contractType in contractTypes) -// { -// foreach (ITypeDefinitionValidator typeDefValidator in TypeDefinitionValidators) -// { -// errors.AddRange(typeDefValidator.Validate(contractType)); -// } -// } - -// return errors; -// } -// } -//} \ No newline at end of file diff --git a/src/Stratis.SmartContracts.CLR.Validation/DeterminismPolicy.cs b/src/Stratis.SmartContracts.CLR/Validation/DeterminismPolicy.cs similarity index 97% rename from src/Stratis.SmartContracts.CLR.Validation/DeterminismPolicy.cs rename to src/Stratis.SmartContracts.CLR/Validation/DeterminismPolicy.cs index 9dfaf08246..f4ca45a37f 100644 --- a/src/Stratis.SmartContracts.CLR.Validation/DeterminismPolicy.cs +++ b/src/Stratis.SmartContracts.CLR/Validation/DeterminismPolicy.cs @@ -39,7 +39,7 @@ private static void SystemPolicy(NamespacePolicy policy) .Member(nameof(Array.SetValue), AccessPolicy.Allowed) .Member(nameof(Array.Resize), AccessPolicy.Allowed)) .Type(typeof(void).Name, AccessPolicy.Allowed) - .Type(typeof(object).Name, AccessPolicy.Denied, + .Type(typeof(object).Name, AccessPolicy.Denied, m => m.Member(nameof(ToString), AccessPolicy.Allowed) .Constructor(AccessPolicy.Allowed)); } diff --git a/src/Stratis.SmartContracts.CLR.Validation/FormatPolicy.cs b/src/Stratis.SmartContracts.CLR/Validation/FormatPolicy.cs similarity index 96% rename from src/Stratis.SmartContracts.CLR.Validation/FormatPolicy.cs rename to src/Stratis.SmartContracts.CLR/Validation/FormatPolicy.cs index 7140fc1698..89bf6d5eb8 100644 --- a/src/Stratis.SmartContracts.CLR.Validation/FormatPolicy.cs +++ b/src/Stratis.SmartContracts.CLR/Validation/FormatPolicy.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using Stratis.SmartContracts; +using Stratis.SmartContracts.CLR.Validation; using Stratis.SmartContracts.CLR.Validation.Validators.Method; using Stratis.SmartContracts.CLR.Validation.Validators.Module; using Stratis.SmartContracts.CLR.Validation.Validators.Type; @@ -47,5 +49,5 @@ public static class FormatPolicy .MethodDefValidator(new PInvokeValidator()) .InstructionValidator(new MultiDimensionalArrayValidator()) .InstructionValidator(new NewObjValidator()); - } + } } \ No newline at end of file diff --git a/src/Stratis.SmartContracts.CLR.Validation/Primitives.cs b/src/Stratis.SmartContracts.CLR/Validation/Primitives.cs similarity index 83% rename from src/Stratis.SmartContracts.CLR.Validation/Primitives.cs rename to src/Stratis.SmartContracts.CLR/Validation/Primitives.cs index 211a72bf23..ad524a5a74 100644 --- a/src/Stratis.SmartContracts.CLR.Validation/Primitives.cs +++ b/src/Stratis.SmartContracts.CLR/Validation/Primitives.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; +using Stratis.SmartContracts; namespace Stratis.SmartContracts.CLR.Validation { public static class Primitives { - public static IEnumerable Types { get; } = new [] + public static IEnumerable Types { get; } = new[] { typeof(bool), typeof(byte), diff --git a/src/Stratis.SmartContracts.CLR.Validation/SmartContractDeterminismValidator.cs b/src/Stratis.SmartContracts.CLR/Validation/SmartContractDeterminismValidator.cs similarity index 94% rename from src/Stratis.SmartContracts.CLR.Validation/SmartContractDeterminismValidator.cs rename to src/Stratis.SmartContracts.CLR/Validation/SmartContractDeterminismValidator.cs index 2f307ebd37..17e799d526 100644 --- a/src/Stratis.SmartContracts.CLR.Validation/SmartContractDeterminismValidator.cs +++ b/src/Stratis.SmartContracts.CLR/Validation/SmartContractDeterminismValidator.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using Mono.Cecil; +using Stratis.SmartContracts.CLR.Validation; namespace Stratis.SmartContracts.CLR.Validation { diff --git a/src/Stratis.SmartContracts.CLR.Validation/SmartContractFormatValidator.cs b/src/Stratis.SmartContracts.CLR/Validation/SmartContractFormatValidator.cs similarity index 84% rename from src/Stratis.SmartContracts.CLR.Validation/SmartContractFormatValidator.cs rename to src/Stratis.SmartContracts.CLR/Validation/SmartContractFormatValidator.cs index e94a5f7692..a19eef9d19 100644 --- a/src/Stratis.SmartContracts.CLR.Validation/SmartContractFormatValidator.cs +++ b/src/Stratis.SmartContracts.CLR/Validation/SmartContractFormatValidator.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Linq; using Mono.Cecil; +using Stratis.SmartContracts.CLR.Validation; namespace Stratis.SmartContracts.CLR.Validation { @@ -15,7 +16,7 @@ public SmartContractValidationResult Validate(ModuleDefinition moduleDefinition) var validator = new ModulePolicyValidator(policy); - List results = validator.Validate(moduleDefinition).ToList(); + var results = validator.Validate(moduleDefinition).ToList(); return new SmartContractValidationResult(results); } diff --git a/src/Stratis.SmartContracts.CLR.Validation/SmartContractValidator.cs b/src/Stratis.SmartContracts.CLR/Validation/SmartContractValidator.cs similarity index 58% rename from src/Stratis.SmartContracts.CLR.Validation/SmartContractValidator.cs rename to src/Stratis.SmartContracts.CLR/Validation/SmartContractValidator.cs index 18c25f6e14..d444c1e204 100644 --- a/src/Stratis.SmartContracts.CLR.Validation/SmartContractValidator.cs +++ b/src/Stratis.SmartContracts.CLR/Validation/SmartContractValidator.cs @@ -1,6 +1,6 @@ -using System.Collections.Generic; -using System.Linq; +using System.Linq; using Mono.Cecil; +using Stratis.SmartContracts.CLR.Validation; namespace Stratis.SmartContracts.CLR.Validation { @@ -8,10 +8,10 @@ public sealed class SmartContractValidator : ISmartContractValidator { public SmartContractValidationResult Validate(ModuleDefinition moduleDefinition) { - ValidationPolicy policy = ValidationPolicy.FromExisting(new[] { FormatPolicy.Default, DeterminismPolicy.Default }); + var policy = ValidationPolicy.FromExisting(new[] { FormatPolicy.Default, DeterminismPolicy.Default }); var validator = new ModulePolicyValidator(policy); - List results = validator.Validate(moduleDefinition).ToList(); + var results = validator.Validate(moduleDefinition).ToList(); return new SmartContractValidationResult(results); } } From 3cd034579810a164640ad70da8b57240c97cdc2c Mon Sep 17 00:00:00 2001 From: Rowan de Haas Date: Mon, 12 Apr 2021 21:14:47 +1000 Subject: [PATCH 13/17] Bump version --- .../Stratis.SmartContracts.CLR.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Stratis.SmartContracts.CLR/Stratis.SmartContracts.CLR.csproj b/src/Stratis.SmartContracts.CLR/Stratis.SmartContracts.CLR.csproj index bc62647bbf..bbd907b3cd 100644 --- a/src/Stratis.SmartContracts.CLR/Stratis.SmartContracts.CLR.csproj +++ b/src/Stratis.SmartContracts.CLR/Stratis.SmartContracts.CLR.csproj @@ -3,9 +3,9 @@ netcoreapp3.1 - 2.0.1.0 - 2.0.1.0 - 2.0.1.0 + 3.0.0.0 + 2.0.0.0 + 3.0.0.0-dev Stratis Group Ltd. Stratis.SmartContracts.CLR From eb26a29fbd7aa5aa53719cf28c5bed45a34cea23 Mon Sep 17 00:00:00 2001 From: Rowan de Haas Date: Mon, 12 Apr 2021 21:17:39 +1000 Subject: [PATCH 14/17] Bump validation version --- .../Stratis.SmartContracts.CLR.Validation.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Stratis.SmartContracts.CLR.Validation/Stratis.SmartContracts.CLR.Validation.csproj b/src/Stratis.SmartContracts.CLR.Validation/Stratis.SmartContracts.CLR.Validation.csproj index a1640e41ac..64cbc77d5f 100644 --- a/src/Stratis.SmartContracts.CLR.Validation/Stratis.SmartContracts.CLR.Validation.csproj +++ b/src/Stratis.SmartContracts.CLR.Validation/Stratis.SmartContracts.CLR.Validation.csproj @@ -3,7 +3,7 @@ netcoreapp3.1 - 2.0.1.0 + 3.0.0.0-dev Stratis Group Ltd. From 18fba745afcefa343dbff9b679595ce752cc53e6 Mon Sep 17 00:00:00 2001 From: Rowan de Haas Date: Mon, 6 Sep 2021 13:46:17 +1200 Subject: [PATCH 15/17] Revert "Remove ECRecover implementation" This reverts commit d5334fb75853b4506e0c13a7a0f7228e694eaaf0. --- .../SCL/ECRecover.cs | 46 +++++ .../ECRecoverTests.cs | 179 ++++++++++++++++++ .../SmartContracts/EcRecoverContract.cs | 28 +++ ...tis.SmartContracts.IntegrationTests.csproj | 3 + 4 files changed, 256 insertions(+) create mode 100644 src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs create mode 100644 src/Stratis.SmartContracts.IntegrationTests/SmartContracts/EcRecoverContract.cs diff --git a/src/Stratis.SmartContracts.CLR/SCL/ECRecover.cs b/src/Stratis.SmartContracts.CLR/SCL/ECRecover.cs index 0d3c83aa88..bbb74ee33b 100644 --- a/src/Stratis.SmartContracts.CLR/SCL/ECRecover.cs +++ b/src/Stratis.SmartContracts.CLR/SCL/ECRecover.cs @@ -6,5 +6,51 @@ namespace Stratis.SCL.Crypto { public static class ECRecover { + /// + /// Retrieves the address of the signer of an ECDSA signature. + /// + /// + /// The ECDSA signature prepended with header information specifying the correct value of recId. + /// The Address for the signer of a signature. + /// A bool representing whether or not the signer was retrieved successfully. + public static bool TryGetSigner(byte[] message, byte[] signature, out Address address) + { + address = Address.Zero; + + if (message == null || signature == null) + return false; + + // NBitcoin is very throwy + try + { + uint256 hashedUint256 = GetUint256FromMessage(message); + + PubKey pubKey = PubKey.RecoverCompact(hashedUint256, signature); + + address = CreateAddress(pubKey.Hash.ToBytes()); + + return true; + } + catch + { + return false; + } + } + + private static uint256 GetUint256FromMessage(byte[] message) + { + return new uint256(SHA3.Keccak256(message)); + } + + private static Address CreateAddress(byte[] bytes) + { + uint pn0 = BitConverter.ToUInt32(bytes, 0); + uint pn1 = BitConverter.ToUInt32(bytes, 4); + uint pn2 = BitConverter.ToUInt32(bytes, 8); + uint pn3 = BitConverter.ToUInt32(bytes, 12); + uint pn4 = BitConverter.ToUInt32(bytes, 16); + + return new Address(pn0, pn1, pn2, pn3, pn4); + } } } diff --git a/src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs b/src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs new file mode 100644 index 0000000000..28404a729f --- /dev/null +++ b/src/Stratis.SmartContracts.IntegrationTests/ECRecoverTests.cs @@ -0,0 +1,179 @@ +using System.Threading.Tasks; +using NBitcoin; +using Stratis.Bitcoin.Features.SmartContracts.Models; +using Stratis.SCL.Crypto; +using Stratis.SmartContracts.CLR; +using Stratis.SmartContracts.CLR.Compilation; +using Stratis.SmartContracts.CLR.Serialization; +using Stratis.SmartContracts.Core; +using Stratis.SmartContracts.Networks; +using Stratis.SmartContracts.Tests.Common.MockChain; +using Xunit; +using EcRecoverProvider = Stratis.SCL.Crypto.ECRecover; +using Key = NBitcoin.Key; + +namespace Stratis.SmartContracts.IntegrationTests +{ + public class ECRecoverTests + { + // 2 things to test: + + // 1) That we have the ECDSA code and can make it available. + + [Fact] + public void CanSignAndRetrieveSender() + { + var network = new SmartContractsRegTest(); + var privateKey = new Key(); + Address address = privateKey.PubKey.GetAddress(network).ToString().ToAddress(network); + byte[] message = new byte[] { 0x69, 0x76, 0xAA }; + + // Sign a message + byte[] offChainSignature = SignMessage(privateKey, message); + + // Get the address out of the signature + EcRecoverProvider.TryGetSigner(message, offChainSignature, out Address recoveredAddress); + + // Check that the address matches that generated from the private key. + Assert.Equal(address, recoveredAddress); + } + + [Fact] + public void GetSigner_Returns_Address_Zero_When_Message_Or_Signature_Null() + { + var network = new SmartContractsRegTest(); + var privateKey = new Key(); + Address address = privateKey.PubKey.GetAddress(network).ToString().ToAddress(network); + byte[] message = new byte[] { 0x69, 0x76, 0xAA }; + + // Sign a message + byte[] offChainSignature = SignMessage(privateKey, message); + + // Get the address out of the signature + Assert.False(EcRecoverProvider.TryGetSigner(null, offChainSignature, out Address recoveredAddress)); + + Assert.Equal(Address.Zero, recoveredAddress); + + Assert.False(EcRecoverProvider.TryGetSigner(message, null, out Address recoveredAddress2)); + + Assert.Equal(Address.Zero, recoveredAddress2); + } + + /// + /// Signs a message, returning an ECDSA signature. + /// + /// The private key used to sign the message. + /// The complete message to be signed. + /// The ECDSA signature prepended with header information specifying the correct value of recId. + private static byte[] SignMessage(Key privateKey, byte[] message) + { + uint256 hashedUint256 = new uint256(SHA3.Keccak256(message)); + + return privateKey.SignCompact(hashedUint256); + } + + [Fact] + public void CanCallEcRecoverContractWithValidSignatureAsync() + { + using (PoWMockChain chain = new PoWMockChain(2)) + { + var node1 = chain.Nodes[0]; + + node1.MineBlocks(1); + + var network = chain.Nodes[0].CoreNode.FullNode.Network; + + var privateKey = new Key(); + string address = privateKey.PubKey.GetAddress(network).ToString(); + byte[] message = new byte[] { 0x69, 0x76, 0xAA }; + byte[] signature = SignMessage(privateKey, message); + + // TODO: If the incorrect parameters are passed to the constructor, the contract does not get properly created ('Method does not exist on contract'), but a success response is still returned? + + byte[] contract = ContractCompiler.CompileFile("SmartContracts/EcRecoverContract.cs").Compilation; + string[] createParameters = new string[] { string.Format("{0}#{1}", (int)MethodParameterDataType.Address, address) }; + BuildCreateContractTransactionResponse createResult = node1.SendCreateContractTransaction(contract, 1, createParameters); + + Assert.NotNull(createResult); + Assert.True(createResult.Success); + + node1.WaitMempoolCount(1); + node1.MineBlocks(1); + + string[] callParameters = new string[] + { + string.Format("{0}#{1}", (int)MethodParameterDataType.ByteArray, message.ToHexString()), + string.Format("{0}#{1}", (int)MethodParameterDataType.ByteArray, signature.ToHexString()) + }; + + BuildCallContractTransactionResponse response = node1.SendCallContractTransaction("CheckThirdPartySignature", createResult.NewContractAddress, 1, callParameters); + Assert.NotNull(response); + Assert.True(response.Success); + + node1.WaitMempoolCount(1); + node1.MineBlocks(1); + + ReceiptResponse receipt = node1.GetReceipt(response.TransactionId.ToString()); + + Assert.NotNull(receipt); + Assert.True(receipt.Success); + Assert.Equal("True", receipt.ReturnValue); + } + } + + [Fact] + public void CanCallEcRecoverContractWithInvalidSignatureAsync() + { + using (PoWMockChain chain = new PoWMockChain(2)) + { + var node1 = chain.Nodes[0]; + + node1.MineBlocks(1); + + var network = chain.Nodes[0].CoreNode.FullNode.Network; + + var privateKey = new Key(); + string address = privateKey.PubKey.GetAddress(network).ToString(); + byte[] message = new byte[] { 0x69, 0x76, 0xAA }; + + // Make the signature with a key unrelated to the third party signer for the contract. + byte[] signature = SignMessage(new Key(), message); + + // TODO: If the incorrect parameters are passed to the constructor, the contract does not get properly created ('Method does not exist on contract'), but a success response is still returned? + + byte[] contract = ContractCompiler.CompileFile("SmartContracts/EcRecoverContract.cs").Compilation; + string[] createParameters = new string[] { string.Format("{0}#{1}", (int)MethodParameterDataType.Address, address) }; + BuildCreateContractTransactionResponse createResult = node1.SendCreateContractTransaction(contract, 1, createParameters); + + Assert.NotNull(createResult); + Assert.True(createResult.Success); + + node1.WaitMempoolCount(1); + node1.MineBlocks(1); + + string[] callParameters = new string[] + { + string.Format("{0}#{1}", (int)MethodParameterDataType.ByteArray, message.ToHexString()), + string.Format("{0}#{1}", (int)MethodParameterDataType.ByteArray, signature.ToHexString()) + }; + + BuildCallContractTransactionResponse response = node1.SendCallContractTransaction("CheckThirdPartySignature", createResult.NewContractAddress, 1, callParameters); + Assert.NotNull(response); + Assert.True(response.Success); + + node1.WaitMempoolCount(1); + node1.MineBlocks(1); + + ReceiptResponse receipt = node1.GetReceipt(response.TransactionId.ToString()); + + Assert.NotNull(receipt); + Assert.True(receipt.Success); + Assert.Equal("False", receipt.ReturnValue); + } + } + + // 2) That we can enable the method in new contracts without affecting the older contracts + + // TODO + } +} \ No newline at end of file diff --git a/src/Stratis.SmartContracts.IntegrationTests/SmartContracts/EcRecoverContract.cs b/src/Stratis.SmartContracts.IntegrationTests/SmartContracts/EcRecoverContract.cs new file mode 100644 index 0000000000..2e40d6ef2f --- /dev/null +++ b/src/Stratis.SmartContracts.IntegrationTests/SmartContracts/EcRecoverContract.cs @@ -0,0 +1,28 @@ +using Stratis.SmartContracts; +using EcRecover = Stratis.SCL.Crypto.ECRecover; + +public class EcRecoverContract : SmartContract +{ + public Address ThirdPartySigner + { + get + { + return this.State.GetAddress(nameof(this.ThirdPartySigner)); + } + set + { + this.State.SetAddress(nameof(this.ThirdPartySigner), value); + } + } + + public EcRecoverContract(ISmartContractState state, Address thirdPartySigner) : base(state) + { + this.ThirdPartySigner = thirdPartySigner; + } + + public bool CheckThirdPartySignature(byte[] message, byte[] signature) + { + EcRecover.TryGetSigner(message, signature, out Address signerOfMessage); + return (signerOfMessage == this.ThirdPartySigner); + } +} \ No newline at end of file diff --git a/src/Stratis.SmartContracts.IntegrationTests/Stratis.SmartContracts.IntegrationTests.csproj b/src/Stratis.SmartContracts.IntegrationTests/Stratis.SmartContracts.IntegrationTests.csproj index 3910ccb5ad..8cd489c9e0 100644 --- a/src/Stratis.SmartContracts.IntegrationTests/Stratis.SmartContracts.IntegrationTests.csproj +++ b/src/Stratis.SmartContracts.IntegrationTests/Stratis.SmartContracts.IntegrationTests.csproj @@ -93,6 +93,9 @@ PreserveNewest + + Always + PreserveNewest From b85e5a1824c9e61764075463fc7372f2f420650a Mon Sep 17 00:00:00 2001 From: Kevin Loubser Date: Mon, 19 Dec 2022 08:46:20 +0200 Subject: [PATCH 16/17] Recheck CI --- src/Stratis.SmartContracts.CLR/SCL/ECRecover.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Stratis.SmartContracts.CLR/SCL/ECRecover.cs b/src/Stratis.SmartContracts.CLR/SCL/ECRecover.cs index bbb74ee33b..5627fb9eb9 100644 --- a/src/Stratis.SmartContracts.CLR/SCL/ECRecover.cs +++ b/src/Stratis.SmartContracts.CLR/SCL/ECRecover.cs @@ -9,7 +9,7 @@ public static class ECRecover /// /// Retrieves the address of the signer of an ECDSA signature. /// - /// + /// The message the signature relates to. /// The ECDSA signature prepended with header information specifying the correct value of recId. /// The Address for the signer of a signature. /// A bool representing whether or not the signer was retrieved successfully. From 2b5ee2637a2249efec95d5174786a07dbd23ccf6 Mon Sep 17 00:00:00 2001 From: Kevin Loubser Date: Mon, 19 Dec 2022 10:52:28 +0200 Subject: [PATCH 17/17] Bump CI again --- src/Stratis.SmartContracts.CLR/SCL/Operations.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Stratis.SmartContracts.CLR/SCL/Operations.cs b/src/Stratis.SmartContracts.CLR/SCL/Operations.cs index 464fa45320..ef8267ed60 100644 --- a/src/Stratis.SmartContracts.CLR/SCL/Operations.cs +++ b/src/Stratis.SmartContracts.CLR/SCL/Operations.cs @@ -4,6 +4,9 @@ namespace Stratis.SCL.Base { public static class Operations { + /// + /// A no-operation, i.e. it does nothing. + /// public static void Noop() { } } }