Skip to content

Commit

Permalink
Optimize VerifyWitness and tx.verify (#2054)
Browse files Browse the repository at this point in the history
  • Loading branch information
Tommo-L authored Dec 7, 2020
1 parent e835631 commit 5b48ee8
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 54 deletions.
39 changes: 35 additions & 4 deletions src/neo/Network/P2P/Payloads/Transaction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ public class Transaction : IEquatable<Transaction>, 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
Expand Down Expand Up @@ -295,17 +307,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 -= 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;
}

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;
}

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
62 changes: 31 additions & 31 deletions src/neo/SmartContract/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -139,7 +139,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 +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;
}
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);
}
}
}
}

0 comments on commit 5b48ee8

Please sign in to comment.