Thou shalt check their witnesses

Neo N3 transaction

First, let’s take a look at what exactly is a Neo N3 transaction.

Neo N3 transaction
Neo N3 transaction
  • script is a NeoVM bytecode, also known as ”entry script”, this is what gets executed when transaction is processed by nodes
  • signers is an array of accounts (Uint160 script hashes) with scopes
  • witnesses is an array (with the number of elements exactly matching the number of signers) of script pairs containing verification script (that hashes to signer script hash) and invocation script (that usually contains signatures checked by verification script)


Unlike most of Legacy transactions Neo N3 transactions are always paid for, there are no free ones and in a system where we legitimately can have a number of signers an obvious question is — who pays the bills? And this ”sender” notion is the answer to that. Structurally it’s just the first signer in a list of signers. It’s his GAS balance that the fees will be deducted from when block with transaction is to be processed.


So what exactly ”CheckWitness” does and what’s so special about it? Way back when in Neo Legacy it allowed to check a hash against a list of script hashes verifying this transaction. These hashes were taken from transaction inputs, attributes and other places depending on transaction type, so ”CheckWitness” basically unified all of these sources.

CheckWitness for calling contract

Signature scopes

Here is a list of scopes that can be used by signers:

  • None
  • CalledByEntry
  • CustomContracts
  • CustomGroups
  • Rules (since 3.1.0)
  • Global


Let’s start exploring scopes with the simplest one — ”Global”. Some might argue that ”None” is even simpler, but that’s not exactly the case. We can say that ”Global” predates it because that’s the way Legacy chain witnesses worked, authorizing any action related to account in question in any contract invoked from a transaction. Sounds perfect, the transaction is signed, we know the signature is correct, and numerous other blockchains use similar model where this signature approves anything done to the account. And yet Neo N3 introduced a number of additional scopes, why is that?

Global witness scope
Global witness misuse


A complete opposite of ”Global” is ”None”, it means that this signer forbids using his signature in any contract. Sounds counterproductive — why would he sign the transaction then? But in fact it’s very helpful when used for signers in sender role, with this scope they only pay the fees, but not do anything else.

None witness scope


This scope is a safe default covering most of the cases, that’s what you’ll be using most of the time when dealing with Neo N3. The permission is given to the entry script and ones it directly calls. Most of invocations do not require going deeper than that and even if some additional contracts are being called they might not require witnessing any actions (like calling ”StdLib” functions). An entry script is created by a user (OK, fancy UI controlled by a user), so user should trust it and it directly contains code that calls some other contract or contracts, so these calls are well known, user is likely to want them to happen and be authorized.

CalledByEntry witness scope


”CustomContracts” allows one to specify exactly the contract or set of contracts the witness is valid for. These allowed contracts are specified as script hashes and if ”CheckWitness” is called from one of them, it succeeds; if some other contract is to call it, it’ll fail.

CustomContracts witness scope
Combined CalledByEntry and CustomContracts scope


Very similar to ”CustomContracts” this scope allows to add witness for a group (or a number of groups) of contracts. Groups are a NEP-15 concept, they’re specified in contract’s manifest and, in general, a contract can belong to a number of groups. Each group is identified by a public key, and this association is confirmed by a signature verifiable using this public key (so Joe Random’s contract can’t be a part of the group unless he can forge a signature for it).

CustomGroups witness scope


It may seem that the set of scopes described above covers everything and in fact that was the set of scopes available at the time of the official Neo 3.0.0 release. But version 3.1.0 (and compatible neo-go 0.98.0) has introduced a new one: ”Rules”, and it has an interesting story behind it.

CustomContracts scope reentrancy attack
  • Boolean: true or false, mostly useful for testing or emulating Global/None scopes
  • Not: inverting nested condition
  • And: matching a whole set of nested conditions (up to 16 of them)
  • Or: matching one of conditions from nested set (also up to 16)
  • ScriptHash: contains a hash to compare with script that is being currently executed (similar to CustomContracts)
  • Group: contains a key identifying a group to compare with groups of the currently executing script (similar to CustomGroups)
  • CalledByEntry: evaluates to true if the script is an entry script or one called directly by it (like CalledByEntry scope)
  • CalledByContract: contains a hash to compare with the caller script hash
  • CalledByGroup: contains a key identifying a group to compare with the groups of the caller script
Rules witness scope example


Scoped witnesses is one of the distinct core features of Neo N3. Deployed contracts can contain any code and invoke other contracts as they want, but with scoped signatures users now have control over what they approve to be done in their transactions and what not. Remember that the power of scopes is there, check witnesses correctly contractside, control what witnesses you give to whom on the user side — and the system will make your interactions much safer.




Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store