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

Question about verify for native contracts #1995

Closed
fyrchik opened this issue Oct 9, 2020 · 3 comments · Fixed by #1998
Closed

Question about verify for native contracts #1995

fyrchik opened this issue Oct 9, 2020 · 3 comments · Fixed by #1998
Labels
Question Used in questions

Comments

@fyrchik
Copy link
Contributor

fyrchik commented Oct 9, 2020

How does verify method for native contracts work?
By default invocation script contains arguments and verification script start with some offset in contract script.
https://github.com/neo-project/neo/blob/master/src/neo/SmartContract/Helper.cs#L175

When we have native contract, verify has ho offset in the script, because it is simply Native.Call.
In the oracle PR there is custom logic with pushing method name together with parameters.
https://github.com/neo-project/neo-modules/pull/326/files#diff-cb9c5c742827ddb7719d684b62664515R275
But I don't see this in this repo.

So how do oracle transactions pass verification?

@shargon
Copy link
Member

shargon commented Oct 9, 2020

So how do oracle transactions pass verification?

It use OracleResponse for verify

public override bool Verify(StoreView snapshot, Transaction tx)
{
if (tx.Signers.Any(p => p.Scopes != WitnessScope.None)) return false;
if (!tx.Script.AsSpan().SequenceEqual(FixedScript)) return false;
OracleRequest request = NativeContract.Oracle.GetRequest(snapshot, Id);
if (request is null) return false;
if (tx.NetworkFee + tx.SystemFee != request.GasForResponse) return false;
UInt160 oracleAccount = Blockchain.GetConsensusAddress(NativeContract.Designate.GetDesignatedByRole(snapshot, Role.Oracle));
return tx.Signers.Any(p => p.Account.Equals(oracleAccount));

private bool Verify(ApplicationEngine engine)
{
Transaction tx = (Transaction)engine.ScriptContainer;
return tx?.GetAttribute<OracleResponse>() != null;

@roman-khimov
Copy link
Contributor

It use OracleResponse for verify

That's attribute, OK, but witnesses are still being verified, aren't they? And we have the following witness at the moment:

            witnessDict[NativeContract.Oracle.Hash] = new Witness
            {
                InvocationScript = Array.Empty<byte>(),
                VerificationScript = NativeContract.Oracle.Script,
            };

Nothing in invocation and something like this in verification:

           using (ScriptBuilder sb = new ScriptBuilder())
            {
                sb.EmitPush(Name);
                sb.EmitSysCall(ApplicationEngine.Neo_Native_Call);
                this.Script = sb.ToArray();
            }

Which matches oracle contract's hash, but it effectively invokes this:

        public static readonly InteropDescriptor Neo_Native_Call = Register("Neo.Native.Call", nameof(CallNativeContract), 0, CallFlags.None, false);
...
        protected internal void CallNativeContract(string name)
        {
            NativeContract.GetContract(name).Invoke(this);
        }

And it doesn't have any "name" to use. Or am I missing something?

Then we wanted to do something like

            witnessDict[NativeContract.Oracle.Hash] = new Witness
            {
                InvocationScript = Array.Empty<byte>(),
                VerificationScript = Array.Empty<byte>(),
            };

To leverage this snippet from VerifyWitnesses:

               if (verification.Length == 0)
                {
                    ContractState cs = snapshot.Contracts.TryGet(hashes[i]);
                    if (cs is null) return false;
                    ContractMethodDescriptor md = cs.Manifest.Abi.GetMethod("verify");
                    if (md is null) return false;
                    verification = cs.Script;
                    offset = md.Offset;
                    init = cs.Manifest.Abi.GetMethod("_initialize");
                }

But it effectively creates the same situation as the script is the same and verify has zero offset.

So what we have at the moment in nspcc-dev/neo-go#1427 is this:

	emit.Int(w.BinWriter, 0)
	emit.Opcodes(w.BinWriter, opcode.PACK)
	emit.String(w.BinWriter, manifest.MethodVerify)
...
	oracleInvoc = w.Bytes()
...
	{
			InvocationScript: oracleInvoc,
		},

Meaning zero verification script and an invocation script that pushes the method name onto the stack. This approach works (in that transaction passes verification), but something tells me it's not the way it's supposed to work.

@fyrchik
Copy link
Contributor Author

fyrchik commented Oct 9, 2020

We can also reuse our CallContractInternal (the one that accepts contract state) which handles difference between simple and native contracts itself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question Used in questions
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants