3 releases
Uses new Rust 2024
| new 0.1.4 | Mar 11, 2026 |
|---|---|
| 0.1.2 | Mar 10, 2026 |
| 0.1.0 | Feb 17, 2026 |
#1194 in Algorithms
Used in 2 crates
1MB
23K
SLoC
ZHC — Zama HPU Compiler
A compiler infrastructure for FHE integer computation targeting the HPU (Homomorphic Processing Unit).
Crates
| Crate | Description |
|---|---|
zhc |
Toplevel crate |
zhc_ir |
Generic graph-based IR framework with dialect support |
zhc_langs |
Language dialects: IOP, HPU, DOP |
zhc_pipeline |
Compilation pipeline orchestration |
zhc_builder |
High-level operation builders |
zhc_sim |
Hardware simulation and configuration |
zhc_crypto |
Cryptographic primitives |
zhc_utils |
Shared utilities |
lib.rs:
Circuit builder for fully homomorphic encryption (FHE) programs.
This crate exposes the Builder type, a high-level interface for constructing FHE
circuits as intermediate representation (IR) graphs. A circuit takes encrypted and plaintext
integer inputs, applies arithmetic operations and programmable bootstrapping (PBS) lookups
on individual blocks, and produces encrypted outputs.
The four value types — Ciphertext, CiphertextBlock, Plaintext, and
PlaintextBlock — are opaque handles into the IR graph. They cannot be inspected
directly; instead, they are passed to Builder methods that emit the corresponding IR
instructions.
Radix Decomposition
Large encrypted integers are represented using a radix decomposition: an integer of
int_size message bits is split into int_size / message_size blocks, each carrying
message_size bits of payload. For example, with
message_size = 2, an 8-bit integer is decomposed into 4 blocks, each encoding a
base-4 digit.
Each CiphertextBlock also reserves carry_size extra bits above the message to
absorb carries from arithmetic operations. A programmable bootstrapping (PBS) lookup
can then be used to propagate carries and extract the message, restoring the block to a
canonical state. The bit layout of a block, from MSB to LSB, is:
┌─────────┬────────────┬─────────┐
│ padding │ carry │ message │
│ (1 bit) │ (c bits) │ (m bits)│
└─────────┴────────────┴─────────┘
MSB LSB
The CiphertextBlockSpec captures the (carry_size, message_size) pair and is shared
by every block in a circuit. Plaintext blocks follow the same radix but have no carry or
padding bits — only the message_size message bits.
All block-level operations (block_* methods) work on individual blocks, while
multi-block integers must first be split into their radix
digits and later joined back.
Operation Flavors
Depending on the integer-level operation being implemented, different flavors of block-level arithmetic may be needed:
- The user may want to protect the padding bit, ensuring a swift (non-negacyclic) lookup in PBSes.
- The user may want to set the padding bit, when executing a negacyclic lookup.
- The user may want to rely on the overflow/underflow of the whole block, to implement signed integer semantics for instance.
To accommodate these use cases, block-level operations come in three flavors:
protect— operand padding bits must be zero, and the result must not overflow into the padding bit. This is the default and most common flavor.temper— operand padding bits may be arbitrary, but the result must not overflow/underflow past the padding bit.wrapping— operand padding bits may be arbitrary, and overflow/underflow is unrestricted. Similar to Rust'swrapping_add/wrapping_subon integers.
Unless explicited in their name, Builder arithmetic methods use the protect flavor.
Methods that use a different flavor are explicitly marked (e.g.
block_wrapping_add_plaintext).
Typical Workflow
// 1. Create a builder for a given block spec.
let builder = Builder::new(CiphertextBlockSpec(2, 2));
// 2. Declare circuit inputs.
let a = builder.ciphertext_input(8);
let b = builder.ciphertext_input(8);
// 3. Decompose into blocks and operate.
let a_blocks = builder.ciphertext_split(&a);
let b_blocks = builder.ciphertext_split(&b);
let sum_blocks: Vec<_> = a_blocks.iter().zip(b_blocks.iter())
.map(|(ab, bb)| builder.block_add(ab, bb))
.collect();
// 4. Reassemble and declare the output.
let result = builder.ciphertext_join(&sum_blocks, None);
builder.ciphertext_output(&result);
// 5. Finalize — this runs dead-code elimination and CSE.
let ir = builder.into_ir();
Dependencies
~0.9–1.8MB
~36K SLoC