From 15b4905eaad2f9766042592fb44909a904d5a63e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Novo?= <34069419+TeknoPT@users.noreply.github.com> Date: Tue, 12 Dec 2023 12:10:57 +0000 Subject: [PATCH] Added new ExtCalls for Get Nexus and Validate an Address. - Added Tests to The Validate Address and to the Get Nexus - Added Special Calls - Added new method to convert the address without checking contracts. --- .../src/Blockchain/VM/EVMContext.cs | 14 ++ .../src/Blockchain/VM/ExtCalls.cs | 199 +++++++++++++++++- .../tests/Blockchain/ExtCallsTests.cs | 158 +++++++++++++- 3 files changed, 366 insertions(+), 5 deletions(-) diff --git a/Phantasma.Business/src/Blockchain/VM/EVMContext.cs b/Phantasma.Business/src/Blockchain/VM/EVMContext.cs index d1d9d473..1b17d8a4 100644 --- a/Phantasma.Business/src/Blockchain/VM/EVMContext.cs +++ b/Phantasma.Business/src/Blockchain/VM/EVMContext.cs @@ -80,6 +80,20 @@ public static Address AddressConvertEthereumToPhantasma(string addressText, INex pubKey[0] = (byte)kind; return Address.FromBytes(pubKey); } + + public static Address AddressConvertEthereumToPhantasmaNoNexus(string addressText) + { + Throw.If(!IsValidAddress(addressText), "invalid ethereum address"); + var input = addressText.Substring(2); + var decodedInput = input.Decode(); + + AddressKind kind = AddressKind.User; + + var pubKey = new byte[Address.LengthInBytes]; + ByteArrayUtils.CopyBytes(decodedInput, 0, pubKey, 1, decodedInput.Length); + pubKey[0] = (byte)kind; + return Address.FromBytes(pubKey); + } public static string AddressConvertPhantasmaToEthereum(Address addr) { diff --git a/Phantasma.Business/src/Blockchain/VM/ExtCalls.cs b/Phantasma.Business/src/Blockchain/VM/ExtCalls.cs index 2bc1779e..22dd16c8 100644 --- a/Phantasma.Business/src/Blockchain/VM/ExtCalls.cs +++ b/Phantasma.Business/src/Blockchain/VM/ExtCalls.cs @@ -52,6 +52,8 @@ internal static void IterateExtcalls(uint ProtocolVersion, Action + /// Logs a message to the chain. + /// + /// + /// private static ExecutionState Runtime_Log(RuntimeVM vm) { var text = vm.Stack.Pop().AsString(); @@ -242,6 +249,11 @@ private static ExecutionState Runtime_Log(RuntimeVM vm) return ExecutionState.Running; } + /// + /// Notifies an event to the chain. + /// + /// + /// private static ExecutionState Runtime_Notify(RuntimeVM vm) { vm.Expect(vm.CurrentContext.Name != VirtualMachine.EntryContextName, "cannot notify in current context"); @@ -339,6 +351,11 @@ private static ExecutionState Oracle_List(RuntimeVM vm) #endregion + /// + /// Returns the time of the chain. + /// + /// + /// private static ExecutionState Runtime_Time(RuntimeVM vm) { var result = new VMObject(); @@ -347,6 +364,11 @@ private static ExecutionState Runtime_Time(RuntimeVM vm) return ExecutionState.Running; } + /// + /// Returns the version of the chain. + /// + /// + /// private static ExecutionState Runtime_Version(RuntimeVM vm) { var result = new VMObject(); @@ -354,7 +376,53 @@ private static ExecutionState Runtime_Version(RuntimeVM vm) vm.Stack.Push(result); return ExecutionState.Running; } + + /// + /// Returns the Nexus Name + /// + /// + /// + private static ExecutionState Runtime_GetNexus(RuntimeVM vm) + { + var vm_obj = new VMObject(); + vm_obj.SetValue(vm.NexusName); + vm.Stack.Push(vm_obj); + + return ExecutionState.Running; + } + + /// + /// This method validate's a signed data using the address, signedData, random and data + /// It returns a boolean value + /// It will validate if the address is the owner of the signed data. + /// + /// + /// + private static ExecutionState Runtime_ValidateAddress(RuntimeVM vm) + { + vm.ExpectStackSize(4); + var address = vm.PopAddress(); + var signedData = vm.PopString("signedData"); + var random = vm.PopString("random"); + var data = vm.PopString("data"); + + vm.Expect(address != Address.Null, "invalid address"); + + var resultSignature = address.ValidateSignedData(signedData, random, data); + + var result = new VMObject(); + result.SetValue(resultSignature); + vm.Stack.Push(result); + + return ExecutionState.Running; + } + + /// + /// Returns the hash of the current transaction. + /// + /// + /// private static ExecutionState Runtime_TransactionHash(RuntimeVM vm) { vm.Expect(vm.Transaction != null, "transaction hash not available here"); @@ -367,6 +435,12 @@ private static ExecutionState Runtime_TransactionHash(RuntimeVM vm) return ExecutionState.Running; } + /// + /// Returns if the given address is a minter for the given token symbol. + /// + /// + /// + /// private static ExecutionState Runtime_IsMinter(RuntimeVM vm) { try @@ -392,7 +466,13 @@ private static ExecutionState Runtime_IsMinter(RuntimeVM vm) return ExecutionState.Running; } - + + /// + /// Returns the GasTarget of the current transaction. + /// + /// + /// + /// private static ExecutionState Runtime_GasTarget(RuntimeVM vm) { if (vm.GasTarget.IsNull) @@ -407,6 +487,11 @@ private static ExecutionState Runtime_GasTarget(RuntimeVM vm) return ExecutionState.Running; } + /// + /// Returns the validator of the current transaction. + /// + /// + /// private static ExecutionState Runtime_Validator(RuntimeVM vm) { var result = new VMObject(); @@ -416,6 +501,11 @@ private static ExecutionState Runtime_Validator(RuntimeVM vm) return ExecutionState.Running; } + /// + /// Returns the context of the current transaction. + /// + /// + /// private static ExecutionState Runtime_Context(RuntimeVM vm) { var result = new VMObject(); @@ -425,6 +515,11 @@ private static ExecutionState Runtime_Context(RuntimeVM vm) return ExecutionState.Running; } + /// + /// Returns the previous context of the current transaction. + /// + /// + /// private static ExecutionState Runtime_PreviousContext(RuntimeVM vm) { var result = new VMObject(); @@ -443,6 +538,12 @@ private static ExecutionState Runtime_PreviousContext(RuntimeVM vm) return ExecutionState.Running; } + /// + /// Returns a random UID (stands for Unique Identifier). + /// + /// + /// + /// private static ExecutionState Runtime_GenerateUID(RuntimeVM vm) { try @@ -461,6 +562,12 @@ private static ExecutionState Runtime_GenerateUID(RuntimeVM vm) return ExecutionState.Running; } + /// + /// Returns if the given address is a witness for the current transaction. + /// + /// + /// + /// private static ExecutionState Runtime_IsWitness(RuntimeVM vm) { try @@ -486,6 +593,12 @@ private static ExecutionState Runtime_IsWitness(RuntimeVM vm) return ExecutionState.Running; } + /// + /// Returns if the current call is a trigger call. + /// + /// + /// + /// private static ExecutionState Runtime_IsTrigger(RuntimeVM vm) { try @@ -508,6 +621,11 @@ private static ExecutionState Runtime_IsTrigger(RuntimeVM vm) } #region DATA + /// + /// Get the value of a field in the current contract + /// + /// + /// private static ExecutionState Data_Get(RuntimeVM vm) { // NOTE: having this check here prevents NFT properties from working @@ -626,6 +744,11 @@ private static ExecutionState Data_Delete(RuntimeVM vm) #endregion #region MAP + /// + /// Returns if the given key exists in the given map. + /// + /// + /// private static ExecutionState Map_Has(RuntimeVM vm) { //vm.Expect(!vm.IsEntryContext(vm.CurrentContext), $"Not allowed from this context"); @@ -658,7 +781,11 @@ private static ExecutionState Map_Has(RuntimeVM vm) return ExecutionState.Running; } - + /// + /// Returns the value of the given key in the given map. + /// + /// + /// private static ExecutionState Map_Get(RuntimeVM vm) { //vm.Expect(!vm.IsEntryContext(vm.CurrentContext), $"Not allowed from this context"); @@ -698,7 +825,12 @@ private static ExecutionState Map_Get(RuntimeVM vm) return ExecutionState.Running; } - + + /// + /// Sets the value of the given key in the given map. + /// + /// + /// private static ExecutionState Map_Set(RuntimeVM vm) { vm.Expect(!vm.IsEntryContext(vm.CurrentContext), "Not allowed from this context"); @@ -733,6 +865,11 @@ private static ExecutionState Map_Set(RuntimeVM vm) return ExecutionState.Running; } + /// + /// Removes the given key from the given map. + /// + /// + /// private static ExecutionState Map_Remove(RuntimeVM vm) { var contextName = vm.CurrentContext.Name; @@ -764,6 +901,11 @@ private static ExecutionState Map_Remove(RuntimeVM vm) return ExecutionState.Running; } + /// + /// Clears the given map. + /// + /// + /// private static ExecutionState Map_Clear(RuntimeVM vm) { vm.Expect(!vm.IsEntryContext(vm.CurrentContext), "Not allowed from this context"); @@ -789,6 +931,11 @@ private static ExecutionState Map_Clear(RuntimeVM vm) return ExecutionState.Running; } + /// + /// Returns the keys of the given map. + /// + /// + /// private static ExecutionState Map_Keys(RuntimeVM vm) { var contextName = vm.CurrentContext.Name; @@ -817,6 +964,11 @@ private static ExecutionState Map_Keys(RuntimeVM vm) return ExecutionState.Running; } + /// + /// Returns the values of the given map. + /// + /// + /// private static ExecutionState Map_Values(RuntimeVM vm) { var contextName = vm.CurrentContext.Name; @@ -845,6 +997,11 @@ private static ExecutionState Map_Values(RuntimeVM vm) return ExecutionState.Running; } + /// + /// Returns the number of entries in the given map. + /// + /// + /// private static ExecutionState Map_Count(RuntimeVM vm) { //vm.Expect(!vm.IsEntryContext(vm.CurrentContext), $"Not allowed from this context"); @@ -871,6 +1028,11 @@ private static ExecutionState Map_Count(RuntimeVM vm) #endregion #region LIST + /// + /// Returns the value of the given index in the given list. + /// + /// + /// private static ExecutionState List_Get(RuntimeVM vm) { //vm.Expect(!vm.IsEntryContext(vm.CurrentContext), $"Not allowed from this context"); @@ -911,6 +1073,11 @@ private static ExecutionState List_Get(RuntimeVM vm) return ExecutionState.Running; } + /// + /// Adds a value to the given list. + /// + /// + /// private static ExecutionState List_Add(RuntimeVM vm) { vm.Expect(!vm.IsEntryContext(vm.CurrentContext), "Not allowed from this context"); @@ -941,6 +1108,11 @@ private static ExecutionState List_Add(RuntimeVM vm) return ExecutionState.Running; } + /// + /// Replace a value in the given list. + /// + /// + /// private static ExecutionState List_Replace(RuntimeVM vm) { vm.Expect(!vm.IsEntryContext(vm.CurrentContext), "Not allowed from this context"); @@ -974,6 +1146,11 @@ private static ExecutionState List_Replace(RuntimeVM vm) return ExecutionState.Running; } + /// + /// Removes a value from the given list at the given index. + /// + /// + /// private static ExecutionState List_RemoveAt(RuntimeVM vm) { vm.Expect(!vm.IsEntryContext(vm.CurrentContext), "Not allowed from this context"); @@ -1004,6 +1181,11 @@ private static ExecutionState List_RemoveAt(RuntimeVM vm) return ExecutionState.Running; } + /// + /// Clears the given list. + /// + /// + /// private static ExecutionState List_Clear(RuntimeVM vm) { var contextName = vm.CurrentContext.Name; @@ -1029,7 +1211,12 @@ private static ExecutionState List_Clear(RuntimeVM vm) return ExecutionState.Running; } - + + /// + /// Returns the amount of entries in the given list. + /// + /// + /// private static ExecutionState List_Count(RuntimeVM vm) { //vm.Expect(!vm.IsEntryContext(vm.CurrentContext), $"Not allowed from this context"); @@ -2083,6 +2270,8 @@ private static ExecutionState Nexus_EndInit(RuntimeVM vm) return ExecutionState.Running; } + + //private static ExecutionState Nexus_CreateChain(RuntimeVM vm) //{ // vm.ExpectStackSize(4); @@ -2325,6 +2514,8 @@ private static ExecutionState Account_Transactions(RuntimeVM vm) } #endregion + + } } diff --git a/Phantasma.Business/tests/Blockchain/ExtCallsTests.cs b/Phantasma.Business/tests/Blockchain/ExtCallsTests.cs index 41233a74..a9ede82c 100644 --- a/Phantasma.Business/tests/Blockchain/ExtCallsTests.cs +++ b/Phantasma.Business/tests/Blockchain/ExtCallsTests.cs @@ -1,10 +1,16 @@ using System; +using System.IO; using System.Numerics; +using System.Text; using Phantasma.Business.Blockchain; using Phantasma.Business.Blockchain.Contracts.Native; +using Phantasma.Business.Blockchain.VM; using Phantasma.Business.Tests.Simulator; using Phantasma.Business.VM.Utils; using Phantasma.Core.Cryptography; +using Phantasma.Core.Cryptography.ECDsa; +using Phantasma.Core.Cryptography.ECDsa.Enums; +using Phantasma.Core.Cryptography.EdDSA; using Phantasma.Core.Cryptography.Enums; using Phantasma.Core.Cryptography.Structs; using Phantasma.Core.Domain; @@ -13,6 +19,8 @@ using Phantasma.Core.Domain.Token.Enums; using Phantasma.Core.Numerics; using Phantasma.Core.Types.Structs; +using Phantasma.Core.Utils; +using Phantasma.Node.Chains.Ethereum; using Xunit; namespace Phantasma.Business.Tests.Blockchain; @@ -57,7 +65,7 @@ private void Initialize() protected void InitializeSimulator() { - simulator = new NexusSimulator(new []{owner}, 17); + simulator = new NexusSimulator(new []{owner}, 18); nexus = simulator.Nexus; nexus.SetOracleReader(new OracleSimulator(nexus)); SetInitialBalance(user.Address); @@ -164,7 +172,155 @@ public void TestDeployToken() // List_Replace // List_RemoveAt // List_Count + + [Fact] + public void TestRuntimeGetNexus() + { + var script = new ScriptBuilder() + .CallInterop("Runtime.Nexus") + .EndScript(); + + var nexusFromInvoke = simulator.InvokeScript( script ); + + Assert.Equal(simulator.Nexus.Name, nexusFromInvoke.AsString()); + } + + + [Fact] + public void TestValidateSignatureEd25519() + { + var address = user.Address; + var dataToSign = Encoding.UTF8.GetBytes("Test");// Base16.Encode(); + byte[] signedDataOut = new byte[0]; + + var randomValue = Random.Shared.Next(0, int.MaxValue); + var randomBytes = BitConverter.GetBytes(randomValue); + + var msg = ByteArrayUtils.ConcatBytes(randomBytes, dataToSign); + + Signature signature = null; + var wif = user.ToWIF(); + var kind = SignatureKind.Ed25519; + switch (kind) + { + case SignatureKind.Ed25519: + var phantasmaKeys = PhantasmaKeys.FromWIF(wif); + signature = phantasmaKeys.Sign(msg); + break; + + case SignatureKind.ECDSA: + var ethKeys = EthereumKey.FromWIF(wif); + + /*var signatureBytes = ethKeys.Sign(msg, (priv, smth, other) => + { + //ethKeys.PrivateKey, ethKeys.PublicKey, ECDsaCurve.Secp256k1 + });*/ + signature = ECDsaSignature.Generate(ethKeys, msg, ECDsaCurve.Secp256k1); + break; + } + + byte[] sigBytes = null; + + using (var stream = new MemoryStream()) + { + using (var writer = new BinaryWriter(stream)) + { + writer.WriteSignature(signature); + } + + sigBytes = stream.ToArray(); + } + + var hexSig = Base16.Encode(sigBytes); + var hexRand = Base16.Encode(randomBytes); + var hexMsg = Base16.Encode(dataToSign); + + Assert.Equal(signedDataOut, new byte[0]); + var script = new ScriptBuilder() + .CallInterop("Runtime.ValidateAddress", address, hexSig, hexRand, hexMsg) + .EndScript(); + + var validateAddressInvoke = simulator.InvokeScript( script ); + + Assert.True(validateAddressInvoke.AsBool()); + + var script2 = new ScriptBuilder() + .CallInterop("Runtime.ValidateAddress", owner.Address, hexSig, hexRand, hexMsg) + .EndScript(); + + var validateAddressInvoke2 = simulator.InvokeScript( script2 ); + + Assert.False(validateAddressInvoke2.AsBool()); + } + [Fact(Skip = "TODO, not working, this is a test for ECDSA")] + // TODO: What is missing is the ability to sign with ECDSA properly and validate + // This was only done using Phantasma Ed25519 + public void TestValidateSignatureECDSA() + { + var address = user.Address; + var dataToSign = Encoding.UTF8.GetBytes("Test"); + byte[] signedDataOut = new byte[0]; + + var randomValue = Random.Shared.Next(0, int.MaxValue); + var randomBytes = BitConverter.GetBytes(randomValue); + + var msg = ByteArrayUtils.ConcatBytes(randomBytes, dataToSign); + + Signature signature = null; + var wif = user.ToWIF(); + var kind = SignatureKind.ECDSA; + switch (kind) + { + case SignatureKind.ECDSA: + var ethKeys = EthereumKey.FromWIF(wif); + var sign = ethKeys.Sign(msg); + var signatureBytes = ethKeys.Sign(msg, (priv, smth, other) => + { + + //ethKeys.PrivateKey, ethKeys.PublicKey, ECDsaCurve.Secp256k1 + return new byte[0]; + }); + + //address = Address.FromInterop(0x4, ethKeys.PublicKey); + address = Address.FromInterop(0x4, ethKeys.PublicKey); + signature = new ECDsaSignature(signatureBytes.ToByteArray(), ECDsaCurve.Secp256k1); + break; + } + + byte[] sigBytes = null; + + using (var stream = new MemoryStream()) + { + using (var writer = new BinaryWriter(stream)) + { + writer.WriteSignature(signature); + } + + sigBytes = stream.ToArray(); + } + + var hexSig = Base16.Encode(sigBytes); + var hexRand = Base16.Encode(randomBytes); + var hexMsg = Base16.Encode(dataToSign); + + Assert.Equal(signedDataOut, new byte[0]); + var script = new ScriptBuilder() + .CallInterop("Runtime.ValidateAddress", address, hexSig, hexRand, hexMsg) + .EndScript(); + + var validateAddressInvoke = simulator.InvokeScript( script ); + + Assert.True(validateAddressInvoke.AsBool()); + + var script2 = new ScriptBuilder() + .CallInterop("Runtime.ValidateAddress", owner.Address, hexSig, hexRand, hexMsg) + .EndScript(); + + var validateAddressInvoke2 = simulator.InvokeScript( script2 ); + + Assert.False(validateAddressInvoke2.AsBool()); + } private BigInteger CreateToken(PhantasmaKeys _user, Address gasAddress, Address userAddress, string contractName, byte[] contractPVM, byte[] contractABI, bool shouldFail = false)