zhc_builder/lib.rs
1//! Circuit builder for fully homomorphic encryption (FHE) programs.
2//!
3//! This crate exposes the [`Builder`] type, a high-level interface for constructing FHE
4//! circuits as intermediate representation (IR) graphs. A circuit takes encrypted and plaintext
5//! integer inputs, applies arithmetic operations and programmable bootstrapping (PBS) lookups
6//! on individual blocks, and produces encrypted outputs.
7//!
8//! The four value types — [`Ciphertext`], [`CiphertextBlock`], [`Plaintext`], and
9//! [`PlaintextBlock`] — are opaque handles into the IR graph. They cannot be inspected
10//! directly; instead, they are passed to [`Builder`] methods that emit the corresponding IR
11//! instructions.
12//!
13//! # Radix Decomposition
14//!
15//! Large encrypted integers are represented using a **radix decomposition**: an integer of
16//! `int_size` message bits is split into `int_size / message_size` blocks, each carrying
17//! `message_size` bits of payload. For example, with
18//! `message_size = 2`, an 8-bit integer is decomposed into 4 blocks, each encoding a
19//! base-4 digit.
20//!
21//! Each [`CiphertextBlock`] also reserves `carry_size` extra bits above the message to
22//! absorb carries from arithmetic operations. A programmable bootstrapping (PBS) lookup
23//! can then be used to propagate carries and extract the message, restoring the block to a
24//! canonical state. The bit layout of a block, from MSB to LSB, is:
25//!
26//! ```text
27//! ┌─────────┬────────────┬─────────┐
28//! │ padding │ carry │ message │
29//! │ (1 bit) │ (c bits) │ (m bits)│
30//! └─────────┴────────────┴─────────┘
31//! MSB LSB
32//! ```
33//!
34//! The [`CiphertextBlockSpec`] captures the `(carry_size, message_size)` pair and is shared
35//! by every block in a circuit. Plaintext blocks follow the same radix but have no carry or
36//! padding bits — only the `message_size` message bits.
37//!
38//! All block-level operations (`block_*` methods) work on individual blocks, while
39//! multi-block integers must first be [`split`](Builder::ciphertext_split) into their radix
40//! digits and later [`join`](Builder::ciphertext_join)ed back.
41//!
42//! # Operation Flavors
43//!
44//! Depending on the integer-level operation being implemented, different flavors of
45//! block-level arithmetic may be needed:
46//!
47//! - The user may want to **protect** the padding bit, ensuring a swift (non-negacyclic) lookup in
48//! PBSes.
49//! - The user may want to **set** the padding bit, when executing a negacyclic lookup.
50//! - The user may want to rely on the **overflow/underflow** of the whole block, to implement
51//! signed integer semantics for instance.
52//!
53//! To accommodate these use cases, block-level operations come in three flavors:
54//!
55//! - **`protect`** — operand padding bits must be zero, and the result must not overflow into the
56//! padding bit. This is the default and most common flavor.
57//! - **`temper`** — operand padding bits may be arbitrary, but the result must not
58//! overflow/underflow *past* the padding bit.
59//! - **`wrapping`** — operand padding bits may be arbitrary, and overflow/underflow is
60//! unrestricted. Similar to Rust's `wrapping_add` / `wrapping_sub` on integers.
61//!
62//! Unless explicited in their name, [`Builder`] arithmetic methods use the **protect** flavor.
63//! Methods that use a different flavor are explicitly marked (e.g.
64//! [`block_wrapping_add_plaintext`](Builder::block_wrapping_add_plaintext)).
65//!
66//! # Typical Workflow
67//!
68//! ```rust,no_run
69//! # use zhc_builder::*;
70//! // 1. Create a builder for a given block spec.
71//! let builder = Builder::new(CiphertextBlockSpec(2, 2));
72//!
73//! // 2. Declare circuit inputs.
74//! let a = builder.ciphertext_input(8);
75//! let b = builder.ciphertext_input(8);
76//!
77//! // 3. Decompose into blocks and operate.
78//! let a_blocks = builder.ciphertext_split(&a);
79//! let b_blocks = builder.ciphertext_split(&b);
80//! let sum_blocks: Vec<_> = a_blocks.iter().zip(b_blocks.iter())
81//! .map(|(ab, bb)| builder.block_add(ab, bb))
82//! .collect();
83//!
84//! // 4. Reassemble and declare the output.
85//! let result = builder.ciphertext_join(&sum_blocks, None);
86//! builder.ciphertext_output(&result);
87//!
88//! // 5. Finalize — this runs dead-code elimination and CSE.
89//! let ir = builder.into_ir();
90//! ```
91
92const NU: usize = 5;
93const NU_BOOL: usize = 8;
94
95mod builder;
96mod iops;
97
98pub use builder::*;
99pub use iops::*;