Hypercube IR: The Foundation of Zero-Knowledge Proof System
In this article, we describe how

Hypercube IR: The Foundation of Zero-Knowledge Proof System
In this article, we introduce our ongoing work on zero-knowledge proof (ZKP)-specific hardware. At Cysic, we are developing a custom proof acceleration chip designed specifically for ZKP workloads. This chip leverages a non-traditional spatial architecture to maximize parallelism and boost throughput for polynomial computations. Furthermore, multiple chips can be interconnected at high speed to create a scalable multi-core system, significantly enhancing performance and memory capacity to support larger and more complex proof tasks.
Introduction
ZK is a cryptographic concept that allows one party (the prover) to prove to another party (the verifier) that a statement is true without revealing any additional information beyond the truth of the statement itself. This principle is fundamental in privacy-preserving technologies and secure authentication. In the past decade, we have witnessed significant developments in both theoretical and practical aspects of ZKP technology. One of the major obstacles in developing high-performance ZKP proving systems is the lack of portability of high-performance code. This lack of portability manifests in several ways:
- It prevents the rapid adoption of new designs from the cryptography community due to the high cost of rewriting a high-performance backend.
- It prevents existing proving systems from quickly adapting to different hardware, such as CPUs, GPUs (including Nvidia’s CUDA, Apple’s Metal, and AMD's ROCm), and more importantly, the upcoming ZKP-specific acceleration chips.
- It makes it difficult for ZKP hardware accelerator manufacturers to promote their products, as hardware companies need to bear the huge costs of adapting each proving system’s software backend.
Ideally, the ZKP community should have a public domain-specific intermediate representation (IR) akin to TorchScript in the AI community to serve as a bridge between proving system development teams and high-performance hardware teams. Here we introduce the HyperCube IR, a minimalistic format tailored for ZKP as well as fully homomorphic encryption (FHE).
Design Rationale
The workload in the ZKP process cannot be fully represented by combinations of a limited set of basic kernels (excluding special cases like NTT). Therefore, ZKP requires a programmable IR rather than an operator library primarily built on enumeration (such as linear algebra-based BLAS libraries). For example, certain steps in Plonkish proof systems depend entirely on their constraint polynomials, which are further influenced by the set of enabled custom gate types. Another example is in proof systems using SumCheck, where the polynomials being summed often involve customizable multivariate low-degree polynomials with multiple multi-linear polynomial inputs. Even for polynomial commitment schemes (PCS) based on Merkle trees using fixed hash functions, implementations vary in the hashing order of data across different projects. These examples highlight the critical importance of programmability in ZKP-specific IRs.
On the other hand, general-purpose processor IRs like LLVM or PTX, while highly flexible, contain excessive low-level details tailored to CPU or a single GPU. This not only increases the complexity of upstream programs generating IRs but also creates challenges for other types of hardware (e.g., those targeting ZKP-specific chips or parallel computing systems), often yielding counterproductive results. For instance:
- Native modular operations: Some hardware natively supports modular arithmetic as a primitive data type. However, LLVM/PTX might decompose modular operations into sequences of 32/64-bit integer operations. Downstreaming hardware compilers encounters the difficult task of reverse-engineering these sequences to recognize them as high-level modular operations (e.g., identifying a 256-bit modular multiplication hidden in 100 instructions), which is extremely challenging.
- Parallel architectural optimization: Many hardware platforms employ large-scale hierarchical parallelism and ZKP/FHE-optimized cache/interconnect architectures. This applies across multiple levels: between cores on a chip, chips on a card, cards in a machine, and even across machines. Hardware designers typically optimize their platforms by leveraging regularity in ZKP computation patterns (e.g., NTT or SumCheck) to improve the usage of the data cache and interconnect topologie. However, if LLVM/PTX representations are used in ZKP computation, such regularity becomes obscured by pointer arithmetic details, rendering these optimizations ineffective.
Thus, a clear conclusion is that ZKP IRs must explicitly expose regularity in computational data access patterns, as this represents cross-platform, performance-critical information. This can be achieved by constraining data access patterns to a minimal set of well-defined primitives, on the condition that the IR maintains sufficient flexibility to meet programmability requirements.
The gains from these trade-offs are significant. The more you want, the more you have to assume. These constraints unlock optimization opportunities across software and hardware:
- Hardware Neutrality: The IR resides at the intersection of computational models that existing heterogeneous hardware can be efficiently implemented.
- Compiler Optimization: Compilers can fully automate data layout management, optimizing for hardware and computational context while enabling kernel fusion opportunities.
- Unlocking Efficient Hardware Architectures: Hardware can adopt spatially efficient multi-core parallelism, decoupled access-execute (DAE) memory architectures, and statically scheduled network communication using optimization solvers.
Computational Representation
The primitive operations required in ZKP proof processes can be divided into two categories:
- Category I: Computations with regular data access patterns and massive parallelism;
- Category II: Highly flexible, irregular computations that are relatively small in total workload.
The purpose of establishing such a classification lies in improving the cost-effectiveness of designing high-performance computing systems: If we can demonstrate that the vast majority of computation time is dominated by a small set of regular patterns, then we can focus on optimizing these patterns while relegating the remaining edge-case computations to existing general-purpose systems as a fallback solution. From this perspective, whether designing customizable chips or constructing highly parallel systems using off-the-shelf hardware, identifying such a classification is essential.
Below, we first introduce a taxonomy of primitives required for ZKP computations, then illustrate their usage and coverage completeness for ZKP functionalities through examples, described in a more mathematical rather than code-centric manner. When we identify these primitives, we prioritize minimizing the number of primitive types to cover most ZKP operations. If an operation can be decomposed into combinations of simpler primitives, it will not be classified as a primitive itself. While real-world hardware implementations often benefit from merging multiple primitives into a single step for efficiency (commonly termed kernel fusion), this is considered an internal compiler optimization detail. The primitive abstraction interface should remain conceptually simple.
In the following, we list the primitives in both category I and II. For ease of reading, we use different colors to represent different primitives.
Category I
Pointwise Operations on Multidimensional Arrays
This primitive can be described mathematically as follows: where is a function with inputs and outputs, is a collection of -dimensional array, and is a collection of -dimensional array.
The function here can be
- A set of -arity polynomials over , typically derived from -- constraint polynomials; -- NTT butterfly operations; -- SumCheck on complex polynomials.
- Cryptographic hash functions, such as -- Binary-based hash functions (e.g., Keccak/SHA), where elements are cast to binary data; -- Native hash functions (e.g., Poseidon family), primarily used in Merkle tree construction.
An example that uses this primitive is in the NTT butterfly operations, where is computed as
Regular Data Movement: Hypercube Transformations
This primitive can be described as where can be constant 0 or 1, (direct indexing) or (complement indexing). An example using this primitive is in the first layer of NTT computation where we need to take a all even/odd elements from the input as seperate arrays, as
Regular Data Movement: 1-Dimensional Cyclic Shifts
The next primitive can be described as the following equation: where , and is the integer specifying distance in cyclic shift. Here the input is a array of elements and output is a array of elements. This primitive is used in the permutation constraint in Plonk, as we illustrate below.
Array Concatenation
The concatenation primitive is described as where converts a boolean array into a boolean string . Here the input are arrays of size to be concatenated, and output is a array of elements. This concatenation primitive is used in the fist layers of NTT operation.
Irregular Data Movement: Grouped Gather
The group gather primitive can be expressed as where it holds .
The function maps table of size with value range from to . The input array is the source array to be lookup from, while the output is the destination array.
Category II
Flexible Low-Volume Computations
There is only one primitive in Category II to handle irregular computations. The primitive is described as where is a CPU-executable function, like hash function for Fiat-Shamit transforms or Merkle tree path verification in FRI-IOP. A typical example using this primitive is the Merkle tree construction in FRI,
Running Examples
In this part, we show that the ZKP operations can be constructed using the primitives defined above.
NTT DIF Operation
IN NTT DIF operation, the input is an array of elements in and the output is an array of elements in , plus a parameter table called the twiddle factors. Each layer of NTT computation corresponds to a butterfly operation. During this process, the -sized array is split into two -sized arrays, followed by butterfly operations. The butterfly operation itself can be expressed as element-wise operations, while Hypercube Transformations combined with Array Concatenation provide the necessary data layout conversions missing in these operations.
The first round of NTT operation is described as the following four equations: The first two equations can be expressed using Hypercube Transformations. The third equation, expressed using Pointwise Operations on Multidimensional Arrays, is that and The last equation is expressed using Array Concatenation as
It is worth mentioning that while the above illustrated flow seems not optimal from a hardware execution perspective (especially for GPU programmers familiar): for example, it creates many temporary intermediate arrays solely for data layout adjustments instead of rewriting the index expression in subsequent operations. But those temporarily are not difficult to be eliminated afterward by the platform-specific compiler when suitable.
Merkle Tree
Merkle tree computation is used in polynomial commitment schemes (PCS), where it takes elements in and outputs Merkle root along with sampled path via Fiat-Shamir transformation. In Plonky2, it targets matrix-shaped witness data (with dimensions rows and columns) by first hashing each row into a fixed-size digest (-bit field elements), then generating a conventional Merkle tree from the remaining single column of digests.
First, it hashes the the 12 columns, using Poseidon hash function where this step can be carried out by Pointwise Operations on Multidimensional Arrays. Secondly, we can hash the next 8 columns to obtain as using Pointwise Operations on Multidimensional Arrays.
The hash computation continues using Pointwise Operations on Multidimensional Arrays, until for the last 8 columns, as is computed
Then using Hypercube Transformations, we can fold the length by half as and can be computed similarly. These values are then hashed to obtain as The hash computation continues, until we compute as the Merkle root. At last, Flexible Low-Volume Computations is used to get the sample Merkle path as
SumCheck (Dense Case)
Sumcheck is commonly used in the GKR and HyperPlonk proof systems. For GRK proofs in Data Parallel Circuits or HyperPlonk that use Plonkish-style approaches for irregular circuits, most computations can be represented as dense operations. Although the full SumCheck process is unsuitable for diagrammatic representation due to its large number of rounds, its computational building blocks can be intuitively categorized as follows:
- Weighted folding: First, we use Hypercube Transformations to compute Then Pointwise Operations on Multidimensional Arrays Arrays is used to get
- Interpolation of multivariate polynomial values: First, we use Hypercube Transformations to compute Then Pointwise Operations on Multidimensional Arrays is used to get
- Index reordering: This is used in Hyperplonk to compute The above formula can be implemented by creating three intermediates, using Hypercube Transformations
Sparse Computation
Some proof systems use sparse polynomials to represent irregular circuits. These systems skip zero-element computations during proofs via Grouped Gather that aggregate non-zero elements into dense arrays, enabling subsequent dense-style computations.
For instance, in GKR’s SumCheck phase, weighted folding of a sparse polynomial is is computed on a dense array of size . Let , we have where , and are dense arrays. denotes the dense representation of sparse polynomial . and are auxiliary arrays obtained via Garther Operation from dense representation of , i.e. where the tuple is defined as the (only) value one such that and similar computation can be done for polynomial with . Here and are pre-computed -element arrays mapping sparse indices to dense positions.
Permutation Constraint (Plonk)
In almost every Plonkish proof system, it is required to prove that two arrays contain identical element sets, which is typically transformed into a product-equality proof of array elements. This process often involves cyclic access patterns:
- Prefix-Product Computation: Given array , we compute (exclusive) prefix-product as . In other words, we compute from such that:
where is defined as the only value such that . The array can be computed recursively using logarithmically many rounds of its size: - Step 1: Compute - Step 2: Recursively compute prefix product for with halved sized; we obtained with values where is the only value that . - Step 3: Finish up with:
- Data Rotation: For univariate polynomial expressions like , where iterates over set , we implement cyclic access via first-order rotation when storing and in multi-dimensional arrays: where is the only value that .


