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

Allow smart contract verification #1800

Merged
merged 40 commits into from
Aug 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
0ce0b58
Allow sc verify
shargon Jul 28, 2020
83e2850
Create DeployedContract
shargon Jul 29, 2020
19a8c94
Merge branch 'master' into allow-sc-verify
shargon Jul 29, 2020
4668955
Fix fee
shargon Jul 29, 2020
21536a4
Fix
shargon Jul 29, 2020
26905d4
Change to exceptions
shargon Jul 29, 2020
4ce99fb
Allow static variables during verification
shargon Jul 29, 2020
37f6670
Simplify DeployedContract
erikzhang Jul 29, 2020
898f1c6
Change verify call flags
shargon Jul 29, 2020
d541f80
Change to ReadOnly
shargon Jul 29, 2020
b1c9772
Merge remote-tracking branch 'origin/allow-sc-verify' into allow-sc-v…
shargon Jul 29, 2020
467c47b
Add error description
shargon Jul 29, 2020
51b8352
Allow cosigners in makeTransaction
shargon Jul 30, 2020
d8bf588
Verify as APPCALL
shargon Jul 30, 2020
e8d59a9
Fix fee
shargon Jul 30, 2020
817b552
Revert "Fix fee"
shargon Jul 30, 2020
29f1ad2
Revert "Verify as APPCALL"
shargon Jul 30, 2020
aa83e12
Auto stash before revert of "Allow cosigners in makeTransaction"
shargon Jul 30, 2020
71cc3f6
None
shargon Jul 30, 2020
6b7ce69
Add comment
shargon Jul 30, 2020
ea03ae6
Merge pull request #16 from shargon/other-version
shargon Jul 30, 2020
211fd52
Merge branch 'master' into allow-sc-verify
shargon Jul 30, 2020
d2b94bc
Fix CustomGroups
shargon Jul 31, 2020
4caa025
Merge branch 'master' into allow-sc-verify
shargon Jul 31, 2020
b26bf2e
Fix UT
shargon Jul 31, 2020
c59d474
Merge branch 'allow-sc-verify' of github.com:shargon/neo into allow-s…
shargon Jul 31, 2020
0f93d89
Update DeployedContract.cs
erikzhang Jul 31, 2020
94bd4c2
Optimize MakeTransaction()
erikzhang Jul 31, 2020
2d6d7cd
Update Wallet.cs
erikzhang Jul 31, 2020
62bbfad
Fix
erikzhang Jul 31, 2020
55426f4
Fix fee calculation
shargon Jul 31, 2020
06e8fd6
Optimize CalculateNetworkFee()
erikzhang Aug 1, 2020
ab971ce
Try sc verification when the account it's null
shargon Aug 1, 2020
c98d61d
Update Wallet.cs
erikzhang Aug 2, 2020
ce30cd7
Update Wallet.cs
shargon Aug 2, 2020
df0d29c
Add true verify test
shargon Aug 2, 2020
47e8b52
Update Wallet.cs
erikzhang Aug 3, 2020
d972416
Merge branch 'master' into allow-sc-verify
shargon Aug 4, 2020
b495fc4
Remove check account in wallet
shargon Aug 4, 2020
c1c2fa4
Fix UT
shargon Aug 4, 2020
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
14 changes: 12 additions & 2 deletions src/neo/SmartContract/ApplicationEngine.Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ partial class ApplicationEngine
public static readonly InteropDescriptor System_Runtime_GetExecutingScriptHash = Register("System.Runtime.GetExecutingScriptHash", nameof(CurrentScriptHash), 0_00000400, CallFlags.None, true);
public static readonly InteropDescriptor System_Runtime_GetCallingScriptHash = Register("System.Runtime.GetCallingScriptHash", nameof(CallingScriptHash), 0_00000400, CallFlags.None, true);
public static readonly InteropDescriptor System_Runtime_GetEntryScriptHash = Register("System.Runtime.GetEntryScriptHash", nameof(EntryScriptHash), 0_00000400, CallFlags.None, true);
public static readonly InteropDescriptor System_Runtime_CheckWitness = Register("System.Runtime.CheckWitness", nameof(CheckWitness), 0_00030000, CallFlags.AllowStates, true);
public static readonly InteropDescriptor System_Runtime_CheckWitness = Register("System.Runtime.CheckWitness", nameof(CheckWitness), 0_00030000, CallFlags.None, true);
public static readonly InteropDescriptor System_Runtime_GetInvocationCounter = Register("System.Runtime.GetInvocationCounter", nameof(GetInvocationCounter), 0_00000400, CallFlags.None, true);
public static readonly InteropDescriptor System_Runtime_Log = Register("System.Runtime.Log", nameof(RuntimeLog), 0_01000000, CallFlags.AllowNotify, false);
public static readonly InteropDescriptor System_Runtime_Notify = Register("System.Runtime.Notify", nameof(RuntimeNotify), 0_01000000, CallFlags.AllowNotify, false);
Expand Down Expand Up @@ -109,7 +109,7 @@ internal bool CheckWitnessInternal(UInt160 hash)
if (signer.Scopes == WitnessScope.Global) return true;
if (signer.Scopes.HasFlag(WitnessScope.CalledByEntry))
{
if (CallingScriptHash == EntryScriptHash)
if (CallingScriptHash == null || CallingScriptHash == EntryScriptHash)
return true;
}
if (signer.Scopes.HasFlag(WitnessScope.CustomContracts))
Expand All @@ -119,6 +119,11 @@ internal bool CheckWitnessInternal(UInt160 hash)
}
if (signer.Scopes.HasFlag(WitnessScope.CustomGroups))
{
// Check allow state callflag

if (!CurrentContext.GetState<ExecutionContextState>().CallFlags.HasFlag(CallFlags.AllowStates))
throw new InvalidOperationException($"Cannot call this SYSCALL without the flag AllowStates.");

var contract = Snapshot.Contracts[CallingScriptHash];
// check if current group is the required one
if (contract.Manifest.Groups.Select(p => p.PubKey).Intersect(signer.AllowedGroups).Any())
Expand All @@ -127,6 +132,11 @@ internal bool CheckWitnessInternal(UInt160 hash)
return false;
}

// Check allow state callflag

if (!CurrentContext.GetState<ExecutionContextState>().CallFlags.HasFlag(CallFlags.AllowStates))
throw new InvalidOperationException($"Cannot call this SYSCALL without the flag AllowStates.");

// only for non-Transaction types (Block, etc)

var hashes_for_verifying = ScriptContainer.GetScriptHashesForVerifying(Snapshot);
Expand Down
1 change: 0 additions & 1 deletion src/neo/SmartContract/ContractParametersContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

namespace Neo.SmartContract
{
Expand Down
24 changes: 24 additions & 0 deletions src/neo/SmartContract/DeployedContract.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using Neo.Ledger;
using Neo.SmartContract.Manifest;
using System;
using System.Linq;

namespace Neo.SmartContract
{
public class DeployedContract : Contract
{
public override UInt160 ScriptHash { get; }

public DeployedContract(ContractState contract)
{
if (contract is null) throw new ArgumentNullException(nameof(contract));

Script = null;
ScriptHash = contract.ScriptHash;
ContractMethodDescriptor descriptor = contract.Manifest.Abi.GetMethod("verify");
if (descriptor is null) throw new NotSupportedException("The smart contract haven't got verify method.");

ParameterList = descriptor.Parameters.Select(u => u.Type).ToArray();
}
}
}
3 changes: 3 additions & 0 deletions src/neo/SmartContract/Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ internal static bool VerifyWitnesses(this IVerifiable verifiable, StoreView snap
for (int i = 0; i < hashes.Length; i++)
{
int offset;
ContractMethodDescriptor init = null;
byte[] verification = verifiable.Witnesses[i].VerificationScript;
if (verification.Length == 0)
{
Expand All @@ -156,6 +157,7 @@ internal static bool VerifyWitnesses(this IVerifiable verifiable, StoreView snap
if (md is null) return false;
verification = cs.Script;
offset = md.Offset;
init = cs.Manifest.Abi.GetMethod("_initialize");
}
else
{
Expand All @@ -165,6 +167,7 @@ internal static bool VerifyWitnesses(this IVerifiable verifiable, StoreView snap
using (ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, verifiable, snapshot.Clone(), gas))
{
engine.LoadScript(verification, CallFlags.None).InstructionPointer = offset;
if (init != null) engine.LoadClonedContext(init.Offset);
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;
Expand Down
171 changes: 104 additions & 67 deletions src/neo/Wallets/Wallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Neo.Network.P2P.Payloads;
using Neo.Persistence;
using Neo.SmartContract;
using Neo.SmartContract.Manifest;
using Neo.SmartContract.Native;
using Neo.VM;
using System;
Expand Down Expand Up @@ -240,7 +241,7 @@ public virtual WalletAccount Import(string nep2, string passphrase, int N = 1638
return account;
}

public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null)
public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null, Signer[] cosigners = null)
{
UInt160[] accounts;
if (from is null)
Expand All @@ -249,13 +250,11 @@ public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null
}
else
{
if (!Contains(from))
throw new ArgumentException($"The address {from.ToString()} was not found in the wallet");
accounts = new[] { from };
}
using (SnapshotView snapshot = Blockchain.Singleton.GetSnapshot())
{
HashSet<UInt160> cosignerList = new HashSet<UInt160>();
Dictionary<UInt160, Signer> cosignerList = cosigners?.ToDictionary(p => p.Account) ?? new Dictionary<UInt160, Signer>();
byte[] script;
List<(UInt160 Account, BigInteger Value)> balances_gas = null;
using (ScriptBuilder sb = new ScriptBuilder())
Expand All @@ -282,9 +281,21 @@ public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null
{
balances = balances.OrderBy(p => p.Value).ToList();
var balances_used = FindPayingAccounts(balances, output.Value.Value);
cosignerList.UnionWith(balances_used.Select(p => p.Account));
foreach (var (account, value) in balances_used)
{
if (cosignerList.TryGetValue(account, out Signer signer))
{
if (signer.Scopes != WitnessScope.Global)
signer.Scopes |= WitnessScope.CalledByEntry;
}
else
{
cosignerList.Add(account, new Signer
{
Account = account,
Scopes = WitnessScope.CalledByEntry
});
}
sb.EmitAppCall(output.AssetId, "transfer", account, output.ScriptHash, value);
sb.Emit(OpCode.ASSERT);
}
Expand All @@ -297,14 +308,7 @@ public Transaction MakeTransaction(TransferOutput[] outputs, UInt160 from = null
if (balances_gas is null)
balances_gas = accounts.Select(p => (Account: p, Value: NativeContract.GAS.BalanceOf(snapshot, p))).Where(p => p.Value.Sign > 0).ToList();

var cosigners = cosignerList.Select(p => new Signer()
{
// default access for transfers should be valid only for first invocation
Scopes = WitnessScope.CalledByEntry,
Account = p
}).ToArray();

return MakeTransaction(snapshot, script, cosigners, Array.Empty<TransactionAttribute>(), balances_gas);
return MakeTransaction(snapshot, script, cosignerList.Values.ToArray(), Array.Empty<TransactionAttribute>(), balances_gas);
}
}

Expand All @@ -317,8 +321,6 @@ public Transaction MakeTransaction(byte[] script, UInt160 sender = null, Signer[
}
else
{
if (!Contains(sender))
throw new ArgumentException($"The address {sender} was not found in the wallet");
accounts = new[] { sender };
}
using (SnapshotView snapshot = Blockchain.Singleton.GetSnapshot())
Expand Down Expand Up @@ -353,49 +355,66 @@ private Transaction MakeTransaction(StoreView snapshot, byte[] script, Signer[]
tx.SystemFee = engine.GasConsumed;
}

UInt160[] hashes = tx.GetScriptHashesForVerifying(snapshot);

// base size for transaction: includes const_header + signers + attributes + script + hashes
int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Attributes.GetVarSize() + script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length);

foreach (UInt160 hash in hashes)
{
byte[] witness_script = GetAccount(hash)?.Contract?.Script ?? snapshot.Contracts.TryGet(hash)?.Script;
if (witness_script is null) continue;
tx.NetworkFee += CalculateNetworkFee(witness_script, ref size);
}
tx.NetworkFee += size * NativeContract.Policy.GetFeePerByte(snapshot);
tx.NetworkFee = CalculateNetworkFee(snapshot, tx);
if (value >= tx.SystemFee + tx.NetworkFee) return tx;
}
throw new InvalidOperationException("Insufficient GAS");
}

public static long CalculateNetworkFee(byte[] witness_script, ref int size)
public long CalculateNetworkFee(StoreView snapshot, Transaction tx)
{
long networkFee = 0;
UInt160[] hashes = tx.GetScriptHashesForVerifying(snapshot);

if (witness_script.IsSignatureContract())
{
size += 67 + witness_script.GetVarSize();
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + ApplicationEngine.ECDsaVerifyPrice;
}
else if (witness_script.IsMultiSigContract(out int m, out int n))
{
int size_inv = 66 * m;
size += IO.Helper.GetVarSize(size_inv) + size_inv + witness_script.GetVarSize();
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * m;
using (ScriptBuilder sb = new ScriptBuilder())
networkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(m).ToArray()[0]];
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * n;
using (ScriptBuilder sb = new ScriptBuilder())
networkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]];
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + ApplicationEngine.ECDsaVerifyPrice * n;
}
else
// base size for transaction: includes const_header + signers + attributes + script + hashes
int size = Transaction.HeaderSize + tx.Signers.GetVarSize() + tx.Attributes.GetVarSize() + tx.Script.GetVarSize() + IO.Helper.GetVarSize(hashes.Length);
long networkFee = 0;
foreach (UInt160 hash in hashes)
{
//We can support more contract types in the future.
byte[] witness_script = GetAccount(hash)?.Contract?.Script;
if (witness_script is null)
{
var contract = snapshot.Contracts.TryGet(hash);
if (contract is null) continue;

// Empty invocation and verification scripts
size += Array.Empty<byte>().GetVarSize() * 2;

// Check verify cost
ContractMethodDescriptor verify = contract.Manifest.Abi.GetMethod("verify");
if (verify is null) throw new ArgumentException($"The smart contract {contract.ScriptHash} haven't got verify method");
ContractMethodDescriptor init = contract.Manifest.Abi.GetMethod("_initialize");
using ApplicationEngine engine = ApplicationEngine.Create(TriggerType.Verification, tx, snapshot.Clone(), 0, testMode: true);
engine.LoadScript(contract.Script, CallFlags.None).InstructionPointer = verify.Offset;
if (init != null) engine.LoadClonedContext(init.Offset);
engine.LoadScript(Array.Empty<byte>(), CallFlags.None);
if (engine.Execute() == VMState.FAULT) throw new ArgumentException($"Smart contract {contract.ScriptHash} verification fault.");
if (engine.ResultStack.Count != 1 || !engine.ResultStack.Pop().GetBoolean()) throw new ArgumentException($"Smart contract {contract.ScriptHash} returns false.");

networkFee += engine.GasConsumed;
}
else if (witness_script.IsSignatureContract())
{
size += 67 + witness_script.GetVarSize();
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] + ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + ApplicationEngine.ECDsaVerifyPrice;
}
else if (witness_script.IsMultiSigContract(out int m, out int n))
{
int size_inv = 66 * m;
size += IO.Helper.GetVarSize(size_inv) + size_inv + witness_script.GetVarSize();
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * m;
using (ScriptBuilder sb = new ScriptBuilder())
networkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(m).ToArray()[0]];
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHDATA1] * n;
using (ScriptBuilder sb = new ScriptBuilder())
networkFee += ApplicationEngine.OpCodePrices[(OpCode)sb.EmitPush(n).ToArray()[0]];
networkFee += ApplicationEngine.OpCodePrices[OpCode.PUSHNULL] + ApplicationEngine.ECDsaVerifyPrice * n;
}
else
{
//We can support more contract types in the future.
}
}

networkFee += size * NativeContract.Policy.GetFeePerByte(snapshot);
return networkFee;
}

Expand All @@ -405,38 +424,56 @@ public bool Sign(ContractParametersContext context)
foreach (UInt160 scriptHash in context.ScriptHashes)
{
WalletAccount account = GetAccount(scriptHash);
if (account is null) continue;

// Try to sign self-contained multiSig
if (account != null)
{
// Try to sign self-contained multiSig

Contract multiSigContract = account.Contract;
Contract multiSigContract = account.Contract;

if (multiSigContract != null &&
multiSigContract.Script.IsMultiSigContract(out int m, out ECPoint[] points))
{
foreach (var point in points)
if (multiSigContract != null &&
multiSigContract.Script.IsMultiSigContract(out int m, out ECPoint[] points))
{
foreach (var point in points)
{
account = GetAccount(point);
if (account?.HasKey != true) continue;
KeyPair key = account.GetKey();
byte[] signature = context.Verifiable.Sign(key);
fSuccess |= context.AddSignature(multiSigContract, key.PublicKey, signature);
if (fSuccess) m--;
if (context.Completed || m <= 0) break;
}
continue;
}
else if (account.HasKey)
shargon marked this conversation as resolved.
Show resolved Hide resolved
{
account = GetAccount(point);
if (account?.HasKey != true) continue;
// Try to sign with regular accounts
KeyPair key = account.GetKey();
byte[] signature = context.Verifiable.Sign(key);
fSuccess |= context.AddSignature(multiSigContract, key.PublicKey, signature);
if (fSuccess) m--;
if (context.Completed || m <= 0) break;
fSuccess |= context.AddSignature(account.Contract, key.PublicKey, signature);
continue;
}
}
else

// Try Smart contract verification

using var snapshot = Blockchain.Singleton.GetSnapshot();
var contract = snapshot.Contracts.TryGet(scriptHash);

if (contract != null)
{
// Try to sign with regular accounts
var deployed = new DeployedContract(contract);

if (account.HasKey)
// Only works with verify without parameters

if (deployed.ParameterList.Length == 0)
{
KeyPair key = account.GetKey();
byte[] signature = context.Verifiable.Sign(key);
fSuccess |= context.AddSignature(account.Contract, key.PublicKey, signature);
fSuccess |= context.Add(deployed);
}
}
}

return fSuccess;
}

Expand Down
Loading