Skip to content

feat(zkevm): Refactor stateless executor#10629

Open
rubo wants to merge 157 commits intomasterfrom
feature/stateless-zisk
Open

feat(zkevm): Refactor stateless executor#10629
rubo wants to merge 157 commits intomasterfrom
feature/stateless-zisk

Conversation

@rubo
Copy link
Copy Markdown
Contributor

@rubo rubo commented Feb 24, 2026

Changes

  • Introduced two projects: the Nethermind.Stateless.ZiskGuest as a guest for Zisk, and the Nethermind.Stateless.Executor with the core execution and serialization logic. The idea is to have a separate guest app for each supported zkVM that contains all host-specific functionality. The stateless executor itself is a regular library that can be compiled and run on any platform.
  • Implemented input serialization.
  • Added block validation.
  • Refactored and optimized Zisk I/O API.
  • Added Zisk cryptographic API bindings for precompiles, hashing, etc.
    • Precompiles are implemented as 3 partial classes to avoid runtime costs and are conditionally compiled. See below.
  • Conditional compilation is implemented based on a file or directory name: std for standard implementations and zkevm for zkEVM implementations. The .std.cs files and std/*.cs files are excluded from build when EnableZkEvm = true, while the .zkevm.cs files and zkevm/*.cs files are excluded from build when EnableZkEvm != true. This vastly eliminates the need for #if ZK_EVM directives in the codebase.
  • Added a tool to generate input files for Zisk.
  • Added testing workflow with Mainnet blocks.
  • Removed the old StatelessExecution project.

See the README

Types of changes

What types of changes does your code introduce?

  • Bugfix (a non-breaking change that fixes an issue)
  • New feature (a non-breaking change that adds functionality)
  • Breaking change (a change that causes existing functionality not to work as expected)
  • Optimization
  • Refactoring
  • Documentation update
  • Build-related changes
  • Other: Description

Testing

Requires testing

  • Yes
  • No

If yes, did you write tests?

  • Yes
  • No

Notes on testing

Requires manual testing

@rubo rubo force-pushed the feature/stateless-zisk branch from 1759bcc to 1ecab29 Compare February 27, 2026 19:53
@rubo rubo marked this pull request as ready for review April 5, 2026 19:16
@NethermindEth NethermindEth deleted a comment from github-actions bot Apr 5, 2026
@rubo rubo force-pushed the feature/stateless-zisk branch from 1d23e79 to fa307d2 Compare April 6, 2026 20:50
@NethermindEth NethermindEth deleted a comment from github-actions bot Apr 6, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 6, 2026

EVM Opcode Benchmark Diff

Aggregated runs: base=1, pr=1

No significant regressions or improvements detected.

Comment on lines +25 to +27
<packageSource key="nugettest.org">
<package pattern="Nethermind.ZiskBindings" />
</packageSource>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have stable package to merge to master?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not yet. Things are changing, and this package will likely be gone soon.


namespace Nethermind.StatelessInputGen;

public class OwnedReadOnlyListConverter : JsonConverterFactory
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we potentially have a separate Witness classes to avoid pooled list in this project?
Or maybe we should just make IOwnedReadOnlyList<> equivalent to normal List under ZK_EVM?
This pooling isn't really netting us anything here right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, that was my initial approach because of the lack of a serializer. Then I decided to have the same class everywhere, but I don't have strong preferences here.

/// <summary>
/// Provides methods for zkVM input serialization. <see cref="Witness.Keys"/> are ignored.
/// </summary>
public static class InputSerializer
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there any point of using this over standarized IRlpDecoders?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what you mean. Please elaborate.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we have a standard how to design RLP serialization/deserialization and interfaces for it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please point to it then

@LukaszRozmej
Copy link
Copy Markdown
Member

There are still places with ZK_EVM - can we try changing them to go with .zkevm files?

@LukaszRozmej
Copy link
Copy Markdown
Member

@claude re-review please focus on changes in shared code like precompiles or Hash - are they 100% correct?
@codex please review

@claude
Copy link
Copy Markdown

claude bot commented Apr 7, 2026

Claude finished @LukaszRozmej's task in 6m 40s —— View job


Code Review — Shared Code Correctness (Precompiles & Hash)

  • Post initial comment
  • Read rule files
  • Review KeccakHash changes
  • Review precompile std/zkevm split correctness
  • Review ExecutionRequestExtensions
  • Compile and post findings

Summary

The shared code changes are correct. No consensus-breaking bugs found. Two findings below.


KeccakHash.HASH_SIZE Changed from public const to private const

src/Nethermind/Nethermind.Core/Crypto/KeccakHash.cs:16

In master, HASH_SIZE = 32 is public const. This PR changes it to private const. No callers of KeccakHash.HASH_SIZE exist in the tree (confirmed via grep), so this is safe in practice, but it is a breaking public API change for any downstream consumers of the assembly. If this is intentional, no action needed — just noting it.


Remaining #if ZK_EVM Directives in Shared Files

Per LukaszRozmej's comment, the following shared files still use #if directives rather than the .zkevm.cs/.std.cs file convention:

KeccakHash.cs:77-83ComputeHash fast path:

#if ZK_EVM
if (output.Length == HASH_SIZE)
{
    ComputeHash256(input, output);
    return;
}
#endif

This fast path calls ComputeHash256, which only exists in KeccakHash.zkevm.cs. Moving it requires overriding more of ComputeHash as a partial method — the current approach is the practical choice since only this early-return differs. Functionally correct as-is.

KeccakHash.cs:267-276UpdateFinalTo fast path:

#if ZK_EVM
if (_state.Length == 0 && _roundSize == HASH_DATA_AREA && output.Length == HASH_SIZE)
{
    ComputeHash256(_remainderBuffer.AsSpan(0, _remainderLength), output);
    Pool.ReturnRemainder(ref _remainderBuffer);
    _remainderLength = 0;
    return;
}
#endif

Same reasoning — ComputeHash256 only exists in the zkevm partial. Functionally correct.

ECRecoverPrecompile.cs:31-33, BN254AddPrecompile.cs:34-36, BN254MulPrecompile.cs:34-36 — metrics guards:

#if !ZK_EVM
Metrics.XxxPrecompile++;
#endif

These three Run methods could each become a partial method to eliminate the directives — KzgPointEvaluationPrecompile already demonstrates the pattern. The std partial increments metrics; the zkevm partial omits it. The current approach works, but is inconsistent with the declared convention.

ExecutionRequestExtensions.cs:11-16, 91-103using SHA256 alias + inner Sha256 class:

using SHA256 =
#if ZK_EVM
    ExecutionRequestExtensions.Sha256;
#else
    System.Security.Cryptography.SHA256;
#endif

This can't be cleanly split into .std.cs/.zkevm.cs without duplicating the entire CalculateHashFromFlatEncodedRequests method. The current pattern is pragmatic. Functionally correct.


No Other Correctness Issues

  • KeccakF algorithm (KeccakHash.std.cs): The permutation logic was moved from KeccakHash.cs to .std.cs unchanged. Constants (RoundConstants, ROUNDS, etc.) moved alongside it. Verified identical.
  • ECRecoverPrecompile zkevm path: stackalloc byte[32] is zero-initialized by default in C#; the first 12 bytes of the 32-byte ABI-encoded address output are zero. Correct.
  • BN254AddPrecompile zkevm path: input[..64] / input[64..] split is correct for two 64-byte G1 points.
  • BN254MulPrecompile zkevm path: input[..64] (G1 point) / input[64..] (32-byte scalar) split is correct.
  • BN254PairingPrecompile zkevm path: Empty-input case handled explicitly (output[31] = 1); result <= 1 correctly distinguishes pairing equality from inequality.
  • KzgPolynomialCommitments.zkevm.cs: sha256_c writes 32-byte hash into a 32-byte-validated buffer; hashBuffer[0] = KzgBlobHashVersionV1 override matches std behaviour. Correct.
  • Bls12381G1AddPrecompile zkevm path: TryDecodeG1 strips 16-byte padding; native lib handles curve/subgroup validation. Status 1 = infinity (all-zero output). Correct.
  • Bls12381PairingCheckPrecompile zkevm path: Field element range check (fp < _baseFieldOrder) is omitted in managed code and delegated to the native library (returns error codes 2–5). Consensus result is identical since all these cases resolve to precompile failure.

@rubo rubo requested a review from maximmenshikov April 7, 2026 15:30
@rubo
Copy link
Copy Markdown
Contributor Author

rubo commented Apr 7, 2026

There are still places with ZK_EVM - can we try changing them to go with .zkevm files?

@LukaszRozmej Don't be like Claude :) Please see my answer to it. I have no intention to replace every #if ZK_EVM with .zkevm.cs file. Only when it makes sense. For small code parts (like a line or two), it's more convenient to use a preprocessor directive than to jump between files, since it's easier to spot.

Copy link
Copy Markdown
Member

@benaadams benaadams left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably you could also swap out (most of) UInt256 by just changing the opcode registration? 🤔

@rubo
Copy link
Copy Markdown
Contributor Author

rubo commented Apr 7, 2026

Presumably you could also swap out (most of) UInt256 by just changing the opcode registration? 🤔

@benaadams Could you please elaborate? We might switch to native UInt256 in the near future.

@benaadams
Copy link
Copy Markdown
Member

Presumably you could also swap out (most of) UInt256 by just changing the opcode registration? 🤔

@benaadams Could you please elaborate? We might switch to native UInt256 in the near future.

Generate/PrepareOpcodes decides implementation src/Nethermind/Nethermind.Evm/VirtualMachine.zkevm.cs

For example Arbitrum overrides some in its GenerateOpCodes https://github.com/NethermindEth/nethermind-arbitrum/blob/a98faba4d5a064a3e7fc0c20b974e6e0e864eb82/src/Nethermind.Arbitrum/Evm/ArbitrumVirtualMachine.cs#L422-L434

@rubo rubo requested a review from maximmenshikov April 7, 2026 19:51
@rubo rubo requested a review from flcl42 April 7, 2026 20:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

5 participants