# How Witness Rules and Signature Verification Work in Neo's Transaction Validation Pipeline

> Discover how Neo's transaction validation pipeline uses witness rules and signature verification to secure your assets. Learn about ECDSA signatures and complex authorization checks.

- Repository: [The Neo Project/neo](https://github.com/neo-project/neo)
- Tags: internals
- Published: 2026-03-08

---

**Neo validates transactions through a two-stage pipeline where state-independent checks verify simple ECDSA signatures in Transaction.cs, while state-dependent execution evaluates complex witness rules via Helper.VerifyWitness and ApplicationEngine.Runtime.CheckWitness to determine if a signer is authorized to spend funds.**

Neo employs a sophisticated dual-phase approach to witness rules and signature verification that balances performance with flexibility. When a node receives a transaction, it first performs lightweight syntactic checks and fast-path ECDSA validation without accessing blockchain state. If these checks pass, the system executes the Neo Virtual Machine to evaluate complex smart contract logic and flexible witness policies defined by signers. Understanding these mechanisms requires examining the core implementation files in the neo-project/neo repository, particularly [`Transaction.cs`](https://github.com/neo-project/neo/blob/main/Transaction.cs), [`Helper.cs`](https://github.com/neo-project/neo/blob/main/Helper.cs), and the witness condition system under `src/Neo/Network/P2P/Payloads/Conditions/`.

## State-Independent Verification: Fast-Path Signature Checks

The first validation stage, implemented in `Transaction.VerifyStateIndependent` within [`src/Neo/Network/P2P/Payloads/Transaction.cs`](https://github.com/neo-project/neo/blob/main/src/Neo/Network/P2P/Payloads/Transaction.cs), performs syntactic validation and optimized signature verification without accessing blockchain state or executing the Virtual Machine.

### Detecting Simple Signature Contracts

Neo distinguishes between simple signature contracts and complex verification scripts using pattern matching in the state-independent phase:

- **`IsSignatureContract`** detects standard 35-byte verification scripts (`0x21 <pubkey> 0xac`)
- **`IsSingleSignatureInvocationScript`** validates 66-byte push-data scripts carrying the signature

When both patterns match, the engine bypasses VM execution and verifies the **ECDSA signature** directly against the transaction hash. This optimization eliminates GAS costs for standard transfers and significantly improves node throughput.

### ECDSA Verification with Crypto.VerifySignature

For simple contracts, the validation pipeline calls `Crypto.VerifySignature` from [`src/Neo/Cryptography/Crypto.cs`](https://github.com/neo-project/neo/blob/main/src/Neo/Cryptography/Crypto.cs):

```csharp
var pubkey = witness.VerificationScript.Span[2..35];
if (!Crypto.VerifySignature(this.GetSignData(settings.Network), signature.Span, pubkey, ECCurve.Secp256r1))
    return VerifyResult.InvalidSignature;

```

This fast-path verification uses the **secp256r1** elliptic curve and returns immediately if the signature is invalid. If a witness does not match the simple contract pattern, verification falls back to the state-dependent step.

## State-Dependent Verification: VM Execution and Witness Rules

When witnesses contain complex scripts, multi-signature requirements, or empty verification scripts (indicating contract-based verification), the pipeline enters the state-dependent phase via `Transaction.VerifyStateDependent`. This stage executes the Neo Virtual Machine and evaluates witness rules against the current blockchain state.

### Helper.VerifyWitness Implementation

The core logic resides in `Helper.VerifyWitness` within [`src/Neo/SmartContract/Helper.cs`](https://github.com/neo-project/neo/blob/main/src/Neo/SmartContract/Helper.cs):

```csharp
internal static bool VerifyWitness(this IVerifiable verifiable,
    ProtocolSettings settings, DataCache snapshot, UInt160 hash,
    Witness witness, long datoshi, out long fee)
{
    // Load verification script (contract or built-in verification method)
    if (witness.VerificationScript.Length == 0)
    {
        // Call contract's `verify` method if the witness is empty
        engine.LoadContract(cs, md, CallFlags.ReadOnly);
    }
    else
    {
        // Load custom verification script
        if (NativeContract.IsNative(hash)) return false;
        if (hash != witness.ScriptHash) return false;
        engine.LoadScript(new Script(witness.VerificationScript, true), ...);
    }

    // Load the invocation script (the signature or parameters)
    engine.LoadScript(invocationScript, configureState: p => p.CallFlags = CallFlags.None);

    // Execute VM and expect a Boolean result
    if (engine.Execute() == VMState.FAULT) return false;
    if (engine.ResultStack.Count != 1) return false;
    if (!engine.ResultStack.Peek().GetBoolean()) return false;

    fee = engine.FeeConsumed;
    return true;
}

```

### Empty vs. Custom Verification Scripts

- **Empty verification scripts** trigger contract-based verification. The system locates the contract corresponding to the witness hash and invokes its `verify` method directly through `engine.LoadContract`. This allows smart contracts to implement dynamic authorization logic, such as multi-factor authentication or time-locked spending, without requiring the verification script to be attached to the transaction.

- **Custom verification scripts** execute directly in the VM. The script must return a single Boolean `true` to succeed. The VM execution consumes GAS proportional to the script's computational complexity, with the consumed amount returned via the `fee` parameter and deducted from the transaction's network fee.

## Witness Rules and Condition Evaluation

Neo N3 introduces flexible **witness rules** that enable granular access control beyond simple signature verification. These rules allow signers to define complex authorization policies that the runtime evaluates during `CheckWitness` calls.

### Signer.GetAllRules and Scope-Based Rules

The `Signer` class in [`src/Neo/Network/P2P/Payloads/Signer.cs`](https://github.com/neo-project/neo/blob/main/src/Neo/Network/P2P/Payloads/Signer.cs) generates rules based on scope flags via `GetAllRules`:

```csharp
public IEnumerable<WitnessRule> GetAllRules()
{
    if (Scopes == WitnessScope.Global)
        yield return new WitnessRule { Action = WitnessRuleAction.Allow,
                                        Condition = new BooleanCondition { Expression = true } };
    else
    {
        if (Scopes.HasFlag(WitnessScope.CalledByEntry))
            yield return new WitnessRule { Action = WitnessRuleAction.Allow,
                                            Condition = new CalledByEntryCondition() };
        if (Scopes.HasFlag(WitnessScope.CustomContracts))
            foreach (var hash in AllowedContracts!)
                yield return new WitnessRule { Action = WitnessRuleAction.Allow,
                                                Condition = new ScriptHashCondition { Hash = hash } };
        if (Scopes.HasFlag(WitnessScope.CustomGroups))
            foreach (var group in AllowedGroups!)
                yield return new WitnessRule { Action = WitnessRuleAction.Allow,
                                                Condition = new GroupCondition { Group = group } };
        if (Scopes.HasFlag(WitnessScope.WitnessRules))
            foreach (var rule in Rules!)
                yield return rule;   // explicit user-defined rules
    }
}

```

**Scope flags** automatically generate corresponding `WitnessRule` objects:
- **`CalledByEntry`**: Yields `CalledByEntryCondition`, allowing calls only from the transaction's entry script
- **`CustomContracts`**: Generates `ScriptHashCondition` rules for each allowed contract hash
- **`CustomGroups`**: Creates `GroupCondition` rules for cryptographic group membership
- **`WitnessRules`**: Embeds explicit user-defined `WitnessRule` objects with custom logic
- **`Global`**: Creates a `BooleanCondition` with `Expression = true`, allowing all calls

### The WitnessCondition Hierarchy

All conditions derive from `WitnessCondition` in `src/Neo/Network/P2P/Payloads/Conditions/` and implement `bool Match(ApplicationEngine engine)`:

| Condition | Evaluation Logic |
|-----------|------------------|
| **BooleanCondition** | Returns constant `true` or `false` |
| **CalledByEntryCondition** | Validates `engine.EntryScriptHash` matches the transaction entry |
| **ScriptHashCondition** | Compares `engine.CallingScriptHash` against allowed hashes |
| **GroupCondition** | Checks `engine.CallingScriptGroup` against group public keys |
| **AndCondition** | Recursively evaluates all nested conditions with logical AND |
| **OrCondition** | Recursively evaluates nested conditions with logical OR |
| **NotCondition** | Negates the result of a nested condition |

### Runtime Evaluation via CheckWitness

The evaluation trigger resides in [`ApplicationEngine.Runtime.cs`](https://github.com/neo-project/neo/blob/main/ApplicationEngine.Runtime.cs):

```csharp
Signer? signer = signers.FirstOrDefault(p => p.Account.Equals(hash));
if (signer is null) return false;
foreach (WitnessRule rule in signer.GetAllRules())
{
    if (rule.Condition.Match(this))          // evaluate condition against engine context
        return rule.Action == WitnessRuleAction.Allow;
}
return false;

```

When a contract calls `System.Runtime.CheckWitness`, the engine locates the corresponding signer, iterates through rules yielded by `GetAllRules()`, and returns immediately upon the first match. This short-circuit evaluation allows efficient policy enforcement while supporting complex, nested conditional logic.

## Practical Implementation Examples

### Single-Signature Transactions

For standard single-signature accounts, Neo provides optimized verification that bypasses VM execution:

```csharp
var tx = new Transaction
{
    Version = 0,
    Signers = new[] { new Signer { Account = senderHash } },
    Script = someScript,
    NetworkFee = 0,
    SystemFee = 0,
    ValidUntilBlock = 500000
};

var witness = new Witness
{
    VerificationScript = Contract.CreateSignatureRedeemScript(senderPubKey),
    InvocationScript   = Contract.CreateSignatureInvocationScript(signature)
};

tx.Witnesses = new[] { witness };

```

During `VerifyStateIndependent`, the engine detects the standard 35-byte verification script pattern and calls `Crypto.VerifySignature` directly, consuming no GAS for VM execution.

### Multi-Signature Contracts

Multi-signature contracts require multiple signatures but follow similar verification patterns:

```csharp
var multiSigScript = Contract.CreateMultiSignatureRedeemScript(2, publicKeys);
var witness = new Witness
{
    VerificationScript = multiSigScript,
    InvocationScript   = Contract.CreateMultiSignatureInvocationScript(
                            new[] { sig1, sig2 })   // two signatures
};

tx.Witnesses = new[] { witness };

```

The state-independent verification detects multi-sig contracts via `IsMultiSigContract`, validates the signature count against the threshold, and deducts the appropriate `MultiSignatureContractCost` before falling back to generic verification if needed.

### Custom Witness Rules with CalledByEntry

For scenarios requiring contextual authorization, developers can define explicit witness rules:

```csharp
var signer = new Signer
{
    Account = senderHash,
    Scopes  = WitnessScope.CalledByEntry | WitnessScope.WitnessRules,
    Rules   = new[]
    {
        new WitnessRule
        {
            Action    = WitnessRuleAction.Allow,
            Condition = new CalledByEntryCondition()
        }
    }
};

tx.Signers = new[] { signer };

```

When the transaction's verification script invokes `CheckWitness(senderHash)`, the runtime iterates over `signer.GetAllRules()`, matches the `CalledByEntryCondition` against `engine.EntryScriptHash`, and returns **Allow** only if the call originates from the transaction's entry point.

## Summary

- **State-independent verification** in `Transaction.VerifyStateIndependent` provides a fast path for simple ECDSA signatures, directly calling `Crypto.VerifySignature` without VM overhead.
- **State-dependent verification** handles complex logic through `Helper.VerifyWitness`, executing verification scripts in the Neo Virtual Machine and deducting consumed GAS from network fees.
- **Witness rules** enable granular access control through the `Signer.GetAllRules` iterator, supporting built-in scopes (`CalledByEntry`, `CustomContracts`, `CustomGroups`, `Global`) and user-defined `WitnessRule` objects with logical composition.
- **Runtime evaluation** occurs via `ApplicationEngine.Runtime.CheckWitness`, which matches conditions against the current execution context and returns boolean authorization results based on the first matching rule.

## Frequently Asked Questions

### How does Neo optimize signature verification for simple single-sig transactions?

Neo bypasses the Virtual Machine for standard single-signature contracts by detecting specific script patterns in `Transaction.VerifyStateIndependent`. When the verification script matches the 35-byte standard format (`0x21 <pubkey> 0xac`) and the invocation script contains a 66-byte signature push, the system calls `Crypto.VerifySignature` directly using the secp256r1 curve. This fast-path verification consumes no GAS and executes immediately, optimizing throughput for common transfer operations.

### What is the difference between witness scopes and explicit witness rules?

**Witness scopes** are convenience flags that automatically generate standard `WitnessRule` objects through `Signer.GetAllRules`. For example, `WitnessScope.CalledByEntry` yields a rule with `CalledByEntryCondition`, while `WitnessScope.CustomContracts` generates `ScriptHashCondition` rules for each allowed hash.

**Explicit witness rules** provide granular control through the `WitnessScope.WitnessRules` flag, allowing developers to define arbitrary `WitnessRule` objects with complex boolean logic using `AndCondition`, `OrCondition`, and `NotCondition`. While scopes cover common authorization patterns, explicit rules enable sophisticated policies like "allow if called by contract A AND (contract B OR group C)".

### How does the CheckWitness system handle rule evaluation during contract execution?

When a contract calls `System.Runtime.CheckWitness`, the runtime locates the signer matching the requested hash and iterates through rules yielded by `GetAllRules()`. Each rule's `Condition.Match(ApplicationEngine)` method evaluates against the current execution context, checking properties like `EntryScriptHash`, `CallingScriptHash`, or `CallingScriptGroup`. The system returns immediately upon the first matching condition with its associated **Allow** or **Deny** action, implementing short-circuit evaluation. If no rules match, the witness check returns false, preventing the transaction from proceeding.