Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize VerifyWitness and tx.verify #2054

Merged
merged 24 commits into from
Dec 7, 2020
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 23 additions & 4 deletions src/neo/Network/P2P/Payloads/Transaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -295,17 +295,36 @@ 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 -= SmartContract.Helper.SignatureContractCost;
else if (witnesses[i].VerificationScript.IsMultiSigContract(out int m, out int n))
net_fee -= SmartContract.Helper.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;
}

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())
erikzhang marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can cache if it's standar inside the witness, otherwise we check it at least twice.

if (!this.VerifyWitness(null, hashes[i], witnesses[i], SmartContract.Helper.MaxVerificationGas, out _))
erikzhang marked this conversation as resolved.
Show resolved Hide resolved
return VerifyResult.Invalid;
return VerifyResult.Succeed;
}

Expand Down
4 changes: 0 additions & 4 deletions src/neo/Network/P2P/Payloads/Witness.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -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)
Expand Down
73 changes: 42 additions & 31 deletions src/neo/SmartContract/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,18 @@ namespace Neo.SmartContract
{
public static class Helper
{
private const long MaxVerificationGas = 0_50000000;
public const long MaxVerificationGas = 0_50000000;
internal static readonly long SignatureContractCost =
ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * 2 +
ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] +
ApplicationEngine.OpCodePrices[OpCode.SYSCALL] +
ApplicationEngine.ECDsaVerifyPrice;
internal 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;
erikzhang marked this conversation as resolved.
Show resolved Hide resolved

public static UInt160 GetContractHash(UInt160 sender, byte[] script)
{
Expand Down Expand Up @@ -139,7 +150,7 @@ public static UInt160 ToScriptHash(this ReadOnlySpan<byte> 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;
Expand All @@ -156,39 +167,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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fee it's only computed if it return true, it works for our current model, but we should take care.

{
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;
}
Expand Down
15 changes: 0 additions & 15 deletions src/neo/SmartContract/WitnessFlag.cs

This file was deleted.

49 changes: 49 additions & 0 deletions tests/neo.UnitTests/SmartContract/UT_Contract.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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<Signer>(), Attributes = Array.Empty<TransactionAttribute>() }, 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<Signer>(), Attributes = Array.Empty<TransactionAttribute>() }, null))
{
engine.LoadScript(invocation.Concat(verification).ToArray(), CallFlags.None);
engine.Execute();
engine.GasConsumed.Should().Be(fee);
}
}
}
}