diff --git a/src/neo/Network/P2P/Payloads/Transaction.cs b/src/neo/Network/P2P/Payloads/Transaction.cs index fabb005c9a..11b5e0a3ef 100644 --- a/src/neo/Network/P2P/Payloads/Transaction.cs +++ b/src/neo/Network/P2P/Payloads/Transaction.cs @@ -35,6 +35,18 @@ public class Transaction : IEquatable, IInventory, IInteroperable private byte[] script; private Witness[] witnesses; + private static readonly long SignatureContractCost = + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * 2 + + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + + ApplicationEngine.OpCodePrices[OpCode.SYSCALL] + + ApplicationEngine.ECDsaVerifyPrice; + private static long MultiSignatureContractCost(int m, int n) => + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * (m + n) + + ApplicationEngine.OpCodePrices[OpCode.PUSHINT8] * 2 + + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + + ApplicationEngine.OpCodePrices[OpCode.SYSCALL] + + ApplicationEngine.ECDsaVerifyPrice * n; + public const int HeaderSize = sizeof(byte) + //Version sizeof(uint) + //Nonce @@ -295,8 +307,23 @@ public virtual VerifyResult VerifyStateDependent(StoreView snapshot, Transaction if (!attribute.Verify(snapshot, this)) return VerifyResult.Invalid; long net_fee = NetworkFee - Size * NativeContract.Policy.GetFeePerByte(snapshot); - if (!this.VerifyWitnesses(snapshot, net_fee, WitnessFlag.StateDependent)) - return VerifyResult.Invalid; + + UInt160[] hashes = GetScriptHashesForVerifying(snapshot); + if (hashes.Length != witnesses.Length) return VerifyResult.Invalid; + for (int i = 0; i < hashes.Length; i++) + { + if (witnesses[i].VerificationScript.IsSignatureContract()) + net_fee -= SignatureContractCost; + else if (witnesses[i].VerificationScript.IsMultiSigContract(out int m, out int n)) + net_fee -= MultiSignatureContractCost(m, n); + else + { + if (!this.VerifyWitness(null, hashes[i], witnesses[i], net_fee, out long fee)) + return VerifyResult.Invalid; + net_fee -= fee; + } + if (net_fee < 0) return VerifyResult.InsufficientFunds; + } return VerifyResult.Succeed; } @@ -304,8 +331,12 @@ public virtual VerifyResult VerifyStateIndependent() { if (Size > MaxTransactionSize) return VerifyResult.Invalid; - if (!this.VerifyWitnesses(null, NetworkFee, WitnessFlag.StateIndependent)) - return VerifyResult.Invalid; + UInt160[] hashes = GetScriptHashesForVerifying(null); + if (hashes.Length != witnesses.Length) return VerifyResult.Invalid; + for (int i = 0; i < hashes.Length; i++) + if (witnesses[i].VerificationScript.IsStandardContract()) + if (!this.VerifyWitness(null, hashes[i], witnesses[i], SmartContract.Helper.MaxVerificationGas, out _)) + return VerifyResult.Invalid; return VerifyResult.Succeed; } diff --git a/src/neo/Network/P2P/Payloads/Witness.cs b/src/neo/Network/P2P/Payloads/Witness.cs index 7ae2d51d70..d6cfcf7470 100644 --- a/src/neo/Network/P2P/Payloads/Witness.cs +++ b/src/neo/Network/P2P/Payloads/Witness.cs @@ -22,8 +22,6 @@ public class Witness : ISerializable public byte[] InvocationScript; public byte[] VerificationScript; - internal long GasConsumed { get; set; } - private UInt160 _scriptHash; public virtual UInt160 ScriptHash { @@ -37,8 +35,6 @@ public virtual UInt160 ScriptHash } } - public bool StateDependent => VerificationScript.Length == 0; - public int Size => InvocationScript.GetVarSize() + VerificationScript.GetVarSize(); void ISerializable.Deserialize(BinaryReader reader) diff --git a/src/neo/SmartContract/Helper.cs b/src/neo/SmartContract/Helper.cs index 617e5f1b39..5a33a8a8db 100644 --- a/src/neo/SmartContract/Helper.cs +++ b/src/neo/SmartContract/Helper.cs @@ -13,7 +13,7 @@ namespace Neo.SmartContract { public static class Helper { - private const long MaxVerificationGas = 0_50000000; + public const long MaxVerificationGas = 0_50000000; public static UInt160 GetContractHash(UInt160 sender, byte[] script) { @@ -139,7 +139,7 @@ public static UInt160 ToScriptHash(this ReadOnlySpan script) return new UInt160(Crypto.Hash160(script)); } - internal static bool VerifyWitnesses(this IVerifiable verifiable, StoreView snapshot, long gas, WitnessFlag filter = WitnessFlag.All) + internal static bool VerifyWitnesses(this IVerifiable verifiable, StoreView snapshot, long gas) { if (gas < 0) return false; if (gas > MaxVerificationGas) gas = MaxVerificationGas; @@ -156,39 +156,39 @@ internal static bool VerifyWitnesses(this IVerifiable verifiable, StoreView snap if (hashes.Length != verifiable.Witnesses.Length) return false; for (int i = 0; i < hashes.Length; i++) { - WitnessFlag flag = verifiable.Witnesses[i].StateDependent ? WitnessFlag.StateDependent : WitnessFlag.StateIndependent; - if (!filter.HasFlag(flag)) + if (!verifiable.VerifyWitness(snapshot, hashes[i], verifiable.Witnesses[i], gas, out long fee)) + return false; + gas -= fee; + } + return true; + } + + internal static bool VerifyWitness(this IVerifiable verifiable, StoreView snapshot, UInt160 hash, Witness witness, long gas, out long fee) + { + fee = 0; + using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, verifiable, snapshot?.Clone(), gas)) + { + CallFlags callFlags = !witness.VerificationScript.IsStandardContract() ? CallFlags.AllowStates : CallFlags.None; + byte[] verification = witness.VerificationScript; + + if (verification.Length == 0) { - gas -= verifiable.Witnesses[i].GasConsumed; - if (gas < 0) return false; - continue; + ContractState cs = snapshot.Contracts.TryGet(hash); + if (cs is null) return false; + if (engine.LoadContract(cs, "verify", callFlags, true) is null) + return false; } - - using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, verifiable, snapshot?.Clone(), gas)) + else { - CallFlags callFlags = verifiable.Witnesses[i].StateDependent ? CallFlags.AllowStates : CallFlags.None; - byte[] verification = verifiable.Witnesses[i].VerificationScript; - - if (verification.Length == 0) - { - ContractState cs = snapshot.Contracts.TryGet(hashes[i]); - if (cs is null) return false; - if (engine.LoadContract(cs, "verify", callFlags, true) is null) - return false; - } - else - { - if (NativeContract.IsNative(hashes[i])) return false; - if (hashes[i] != verifiable.Witnesses[i].ScriptHash) return false; - engine.LoadScript(verification, callFlags, hashes[i], 0); - } - - engine.LoadScript(verifiable.Witnesses[i].InvocationScript, CallFlags.None); - if (engine.Execute() == VMState.FAULT) return false; - if (engine.ResultStack.Count != 1 || !engine.ResultStack.Pop().GetBoolean()) return false; - gas -= engine.GasConsumed; - verifiable.Witnesses[i].GasConsumed = engine.GasConsumed; + if (NativeContract.IsNative(hash)) return false; + if (hash != witness.ScriptHash) return false; + engine.LoadScript(verification, callFlags, hash, 0); } + + engine.LoadScript(witness.InvocationScript, CallFlags.None); + if (engine.Execute() == VMState.FAULT) return false; + if (engine.ResultStack.Count != 1 || !engine.ResultStack.Peek().GetBoolean()) return false; + fee = engine.GasConsumed; } return true; } diff --git a/src/neo/SmartContract/WitnessFlag.cs b/src/neo/SmartContract/WitnessFlag.cs deleted file mode 100644 index 528562a70f..0000000000 --- a/src/neo/SmartContract/WitnessFlag.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; - -namespace Neo.SmartContract -{ - [Flags] - internal enum WitnessFlag : byte - { - None = 0, - - StateIndependent = 0b00000001, - StateDependent = 0b00000010, - - All = StateIndependent | StateDependent - } -} diff --git a/tests/neo.UnitTests/SmartContract/UT_Contract.cs b/tests/neo.UnitTests/SmartContract/UT_Contract.cs index 249018f69b..6c2f4b04f2 100644 --- a/tests/neo.UnitTests/SmartContract/UT_Contract.cs +++ b/tests/neo.UnitTests/SmartContract/UT_Contract.cs @@ -1,5 +1,6 @@ using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; +using Neo.Network.P2P.Payloads; using Neo.SmartContract; using Neo.VM; using Neo.Wallets; @@ -163,5 +164,53 @@ public void TestCreateSignatureRedeemScript() Array.Copy(BitConverter.GetBytes(ApplicationEngine.Neo_Crypto_VerifyWithECDsaSecp256r1), 0, expectedArray, 37, 4); CollectionAssert.AreEqual(expectedArray, script); } + + [TestMethod] + public void TestSignatureRedeemScriptFee() + { + byte[] privateKey = new byte[32]; + RandomNumberGenerator rng = RandomNumberGenerator.Create(); + rng.GetBytes(privateKey); + KeyPair key = new KeyPair(privateKey); + byte[] verification = Contract.CreateSignatureRedeemScript(key.PublicKey); + byte[] invocation = new ScriptBuilder().EmitPush(UInt160.Zero).ToArray(); + + var fee = ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * 2 + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + ApplicationEngine.OpCodePrices[OpCode.SYSCALL] + ApplicationEngine.ECDsaVerifyPrice; + + using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, new Transaction { Signers = Array.Empty(), Attributes = Array.Empty() }, null)) + { + engine.LoadScript(invocation.Concat(verification).ToArray(), CallFlags.None); + engine.Execute(); + engine.GasConsumed.Should().Be(fee); + } + } + + [TestMethod] + public void TestCreateMultiSigRedeemScriptFee() + { + byte[] privateKey1 = new byte[32]; + RandomNumberGenerator rng1 = RandomNumberGenerator.Create(); + rng1.GetBytes(privateKey1); + KeyPair key1 = new KeyPair(privateKey1); + byte[] privateKey2 = new byte[32]; + RandomNumberGenerator rng2 = RandomNumberGenerator.Create(); + rng2.GetBytes(privateKey2); + KeyPair key2 = new KeyPair(privateKey2); + Neo.Cryptography.ECC.ECPoint[] publicKeys = new Neo.Cryptography.ECC.ECPoint[2]; + publicKeys[0] = key1.PublicKey; + publicKeys[1] = key2.PublicKey; + publicKeys = publicKeys.OrderBy(p => p).ToArray(); + byte[] verification = Contract.CreateMultiSigRedeemScript(2, publicKeys); + byte[] invocation = new ScriptBuilder().EmitPush(UInt160.Zero).EmitPush(UInt160.Zero).ToArray(); + + long fee = ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * (2 + 2) + ApplicationEngine.OpCodePrices[OpCode.PUSHINT8] * 2 + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + ApplicationEngine.OpCodePrices[OpCode.SYSCALL] + ApplicationEngine.ECDsaVerifyPrice * 2; + + using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, new Transaction { Signers = Array.Empty(), Attributes = Array.Empty() }, null)) + { + engine.LoadScript(invocation.Concat(verification).ToArray(), CallFlags.None); + engine.Execute(); + engine.GasConsumed.Should().Be(fee); + } + } } }