pub struct Builder { /* private fields */ }Expand description
High-level builder for constructing FHE circuits as IR graphs.
A Builder accumulates IR instructions through its methods, using interior mutability
so that all operations take &self. The typical lifecycle is: create a builder, declare
inputs, emit block-level or vector-level operations, declare outputs, and finally call
into_ir to obtain the optimized IR.
Every builder is parameterized by a single CiphertextBlockSpec that defines the
message/carry bit layout shared by all ciphertext blocks in the circuit. This spec is
set at construction time and accessible via spec.
§Input / Output Ordering
Inputs and outputs are positional: they are recorded in the order they are
declared. The first call to ciphertext_input
or plaintext_input becomes input 0, the
second becomes input 1, and so on — both kinds share the same index space. Likewise,
the first ciphertext_output becomes
output 0. This ordering defines the circuit’s signature and must
match the order of values passed to eval.
§Comments
The builder maintains a comment stack that annotates IR instructions for debugging and
readability. When the stack is non-empty, every emitted instruction is tagged with the
full stack joined by /. Use with_comment for scoped
annotations, or push_comment /
pop_comment for manual control. Comments nest naturally: a
comment pushed inside a with_comment closure appends to the
existing stack.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let input = builder.ciphertext_input(8);
let blocks = builder.ciphertext_split(&input);
// ... operate on blocks ...
let output = builder.ciphertext_join(&blocks, None);
builder.ciphertext_output(&output);
let ir = builder.into_ir();Implementations§
Source§impl Builder
impl Builder
Sourcepub fn new(spec: CiphertextBlockSpec) -> Self
pub fn new(spec: CiphertextBlockSpec) -> Self
Creates a new builder with the given block specification.
The spec defines the message and carry bit sizes for every ciphertext block
produced by this builder. The builder starts with an empty IR and no declared
inputs or outputs.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));Sourcepub fn into_ir(self) -> IR<IopLang>
pub fn into_ir(self) -> IR<IopLang>
Consumes the builder and returns the optimized IR graph.
Finalizes the circuit by running optimization passes — alias elimination, dead-code elimination, and common subexpression elimination — then returns the resulting IR. This is typically the last step after declaring all inputs, emitting operations, and declaring outputs.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let input = builder.ciphertext_input(8);
// ... build circuit ...
builder.ciphertext_output(&input);
let ir = builder.into_ir();Sourcepub fn spec(&self) -> &CiphertextBlockSpec
pub fn spec(&self) -> &CiphertextBlockSpec
Returns the block specification shared by all ciphertext blocks in this circuit.
Sourcepub fn signature(&self) -> Signature<Type>
pub fn signature(&self) -> Signature<Type>
Returns a clone of the circuit’s current I/O signature.
The signature records every input and output declared so far, in declaration order,
as Type values.
Sourcepub fn ir(&self) -> Ref<'_, IR<IopLang>>
pub fn ir(&self) -> Ref<'_, IR<IopLang>>
Borrows the current (unoptimized) IR graph.
Unlike into_ir, this does not consume the builder and does not
apply any optimization passes. Useful for debugging and inspection mid-construction.
Sourcepub fn dump_and_panic(&self) -> !
pub fn dump_and_panic(&self) -> !
Prints the current IR to stdout and panics.
This is a debugging helper intended for use during circuit development. It dumps a human-readable representation of the unoptimized IR graph, then unconditionally panics to halt execution.
§Panics
Always panics after printing.
Sourcepub fn dump_eval_and_panic(&self, inputs: impl AsRef<[IopValue]>) -> !
pub fn dump_eval_and_panic(&self, inputs: impl AsRef<[IopValue]>) -> !
Interprets the current IR with the given inputs, prints the annotated result, and panics.
Like dump_and_panic, but first runs the IR interpreter so
each node is annotated with its computed value. The inputs slice must match the
declared input signature in order and length. The ciphertext spec used for
interpretation is inferred from the maximum int_size among the ciphertext inputs.
§Panics
Always panics after printing, regardless of whether interpretation succeeded.
Sourcepub fn eval(&self, inputs: impl AsRef<[IopValue]>) -> Vec<IopValue>
pub fn eval(&self, inputs: impl AsRef<[IopValue]>) -> Vec<IopValue>
Interprets the current IR with the given inputs and returns the output values.
Runs the IR interpreter on the unoptimized graph with the provided inputs, which
must match the declared input signature in order and length. Returns the computed
output values in declaration order. The ciphertext spec used for interpretation is
inferred from the maximum int_size among the ciphertext inputs.
This is useful for validating circuit correctness without running actual FHE
operations. Construct input values with the make_value
methods on the handle types.
§Panics
Panics if interpretation fails (e.g. due to a malformed graph).
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let a = builder.ciphertext_input(8);
let b = builder.ciphertext_input(8);
// ... build circuit ...
let outputs = builder.eval(&[a.make_value(42), b.make_value(7)]);Sourcepub fn comment(&self, comment: impl Into<String>) -> Builder
pub fn comment(&self, comment: impl Into<String>) -> Builder
Returns a new builder handle with the given comment appended to the annotation stack.
Unlike push_comment which mutates the current builder, this
method returns a new Builder sharing the same underlying IR but with an
independent comment stack containing the new comment. All instructions emitted
through the returned builder are annotated with the full stack including the new
comment; instructions emitted through the original builder remain unaffected. This
is useful for forking annotation contexts without manual push/pop management.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let commented = builder.comment("add phase");
let ct = commented.ciphertext_input(4);
// Instructions through `commented` carry the "add phase" annotation.Sourcepub fn push_comment(&self, comment: impl Into<String>)
pub fn push_comment(&self, comment: impl Into<String>)
Pushes a comment onto the annotation stack.
All IR instructions emitted while this comment is on the stack will be annotated
with the full stack joined by /. Use pop_comment to
remove it, or prefer the RAII-style with_comment.
Sourcepub fn pop_comment(&self)
pub fn pop_comment(&self)
Pops the most recent comment from the annotation stack.
Sourcepub fn with_comment<R>(
&self,
comment: impl Into<String>,
f: impl FnOnce() -> R,
) -> R
pub fn with_comment<R>( &self, comment: impl Into<String>, f: impl FnOnce() -> R, ) -> R
Executes a closure with a temporary comment pushed onto the annotation stack.
The comment is pushed before calling f and popped after it returns, ensuring
proper nesting even if f itself pushes additional comments. Returns whatever
f returns.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(4);
let blocks = builder.ciphertext_split(&ct);
let result = builder.with_comment("carry propagation", || {
builder.block_add(&blocks[0], &blocks[1])
});Sourcepub fn ciphertext_input(&self, int_size: u16) -> Ciphertext
pub fn ciphertext_input(&self, int_size: u16) -> Ciphertext
Declares an encrypted integer input of the given bit-width.
Registers a new ciphertext input in the circuit signature and emits the
corresponding IR input instruction. The input is assigned the next positional index
(see Input / Output Ordering). The int_size
specifies the total number of message bits across all blocks (e.g. 8 for an 8-bit
integer). The resulting ciphertext is a radix-decomposed integer with
int_size / message_size blocks.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let input = builder.ciphertext_input(8);
let blocks = builder.ciphertext_split(&input);Sourcepub fn ciphertext_split(
&self,
inp: impl AsRef<Ciphertext>,
) -> Vec<CiphertextBlock>
pub fn ciphertext_split( &self, inp: impl AsRef<Ciphertext>, ) -> Vec<CiphertextBlock>
Decomposes a Ciphertext into its individual radix blocks.
Returns one CiphertextBlock per block in the radix-decomposed
representation, ordered from least-significant to most-significant digit. The
length of the returned vector is int_size / message_size.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(8);
let blocks = builder.ciphertext_split(&ct);
assert_eq!(blocks.len(), 4); // 8 bits / 2-bit message = 4 blocksSourcepub fn plaintext_input(&self, int_size: u16) -> Plaintext
pub fn plaintext_input(&self, int_size: u16) -> Plaintext
Declares a plaintext integer input of the given bit-width.
Registers a new plaintext input in the circuit signature and emits the corresponding IR input instruction. The input is assigned the next positional index, shared with ciphertext inputs (see Input / Output Ordering). The plaintext block spec is derived from the builder’s ciphertext block spec (matching message size, no carry bits).
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(8);
let pt = builder.plaintext_input(8);
let ct_blocks = builder.ciphertext_split(&ct);
let pt_blocks = builder.plaintext_split(&pt);
let sum = builder.block_add_plaintext(&ct_blocks[0], &pt_blocks[0]);Sourcepub fn plaintext_split(&self, inp: impl AsRef<Plaintext>) -> Vec<PlaintextBlock>
pub fn plaintext_split(&self, inp: impl AsRef<Plaintext>) -> Vec<PlaintextBlock>
Decomposes a Plaintext into its individual radix blocks.
Returns one PlaintextBlock per digit in the radix-decomposed
representation, ordered from least-significant to most-significant digit. The
length of the returned vector is int_size / message_size.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let pt = builder.plaintext_input(8);
let blocks = builder.plaintext_split(&pt);
assert_eq!(blocks.len(), 4); // 8 bits / 2-bit message = 4 blocksSourcepub fn ciphertext_join(
&self,
blocks: impl AsRef<[CiphertextBlock]>,
int_size: Option<u16>,
) -> Ciphertext
pub fn ciphertext_join( &self, blocks: impl AsRef<[CiphertextBlock]>, int_size: Option<u16>, ) -> Ciphertext
Reassembles a slice of radix blocks into a single Ciphertext.
The blocks are stored in order, with block 0 as the least-significant radix block.
When int_size is None, the total bit-width is inferred as
blocks.len() * message_size. When int_size is Some, it overrides the
bit-width explicitly. This is useful if the expected bit-width is not a multiple of
the message size.
§Panics
Panics if int_size is Some and the number of blocks exceeds the
capacity implied by the given bit-width.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let input = builder.ciphertext_input(8);
let blocks = builder.ciphertext_split(&input);
// ... operate on blocks ...
let ct = builder.ciphertext_join(&blocks, None);
builder.ciphertext_output(&ct);Sourcepub fn ciphertext_inspect(&self, src: impl AsRef<Ciphertext>) -> Ciphertext
pub fn ciphertext_inspect(&self, src: impl AsRef<Ciphertext>) -> Ciphertext
Creates a new IR node that aliases an existing ciphertext.
The returned ciphertext references the same underlying value but has a distinct IR node identity. This is useful for debugging, as the node appears separately in IR dumps and can be annotated with the current comment stack.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let input = builder.ciphertext_input(8);
let labeled = builder.comment("after input").ciphertext_inspect(&input);Sourcepub fn ciphertext_output(&self, ct: impl AsRef<Ciphertext>)
pub fn ciphertext_output(&self, ct: impl AsRef<Ciphertext>)
Declares an encrypted integer output for the circuit.
Registers the ciphertext as a circuit output in the signature and emits the corresponding IR output instruction. The output is assigned the next positional index (see Input / Output Ordering).
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let input = builder.ciphertext_input(8);
builder.ciphertext_output(&input);Sourcepub fn block_let_plaintext(&self, value: u8) -> PlaintextBlock
pub fn block_let_plaintext(&self, value: u8) -> PlaintextBlock
Creates a constant PlaintextBlock with the given message value.
The value is stored as a message-only plaintext block. Its bit-width must fit
within the builder’s message size.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(4);
let blocks = builder.ciphertext_split(&ct);
let one = builder.block_let_plaintext(1);
let incremented = builder.block_add_plaintext(&blocks[0], &one);Sourcepub fn block_add(
&self,
src_a: impl AsRef<CiphertextBlock>,
src_b: impl AsRef<CiphertextBlock>,
) -> CiphertextBlock
pub fn block_add( &self, src_a: impl AsRef<CiphertextBlock>, src_b: impl AsRef<CiphertextBlock>, ) -> CiphertextBlock
Adds two ciphertext blocks (protect flavor).
Computes src_a + src_b at the block level. Uses protect semantics — see
Operation Flavors.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(4);
let blocks = builder.ciphertext_split(&ct);
let sum = builder.block_add(&blocks[0], &blocks[1]);Sourcepub fn block_inspect(&self, src: impl AsRef<CiphertextBlock>) -> CiphertextBlock
pub fn block_inspect(&self, src: impl AsRef<CiphertextBlock>) -> CiphertextBlock
Creates a new IR node that aliases an existing ciphertext block.
The returned block references the same underlying value but has a distinct IR node identity. This is useful for debugging purposes.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(4);
let blocks = builder.ciphertext_split(&ct);
let labeled = builder.comment("lsb").block_inspect(&blocks[0]);Sourcepub fn block_temper_add(
&self,
src_a: impl AsRef<CiphertextBlock>,
src_b: impl AsRef<CiphertextBlock>,
) -> CiphertextBlock
pub fn block_temper_add( &self, src_a: impl AsRef<CiphertextBlock>, src_b: impl AsRef<CiphertextBlock>, ) -> CiphertextBlock
Adds two ciphertext blocks (temper flavor).
Computes src_a + src_b at the block level. Uses temper semantics — see
Operation Flavors.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(4);
let blocks = builder.ciphertext_split(&ct);
let sum = builder.block_temper_add(&blocks[0], &blocks[1]);Sourcepub fn block_wrapping_add(
&self,
src_a: impl AsRef<CiphertextBlock>,
src_b: impl AsRef<CiphertextBlock>,
) -> CiphertextBlock
pub fn block_wrapping_add( &self, src_a: impl AsRef<CiphertextBlock>, src_b: impl AsRef<CiphertextBlock>, ) -> CiphertextBlock
Adds two ciphertext blocks (wrapping flavor).
Computes src_a + src_b at the block level. Uses wrapping semantics — see
Operation Flavors.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(4);
let blocks = builder.ciphertext_split(&ct);
let sum = builder.block_wrapping_add(&blocks[0], &blocks[1]);Sourcepub fn block_add_plaintext(
&self,
src_a: impl AsRef<CiphertextBlock>,
src_b: impl AsRef<PlaintextBlock>,
) -> CiphertextBlock
pub fn block_add_plaintext( &self, src_a: impl AsRef<CiphertextBlock>, src_b: impl AsRef<PlaintextBlock>, ) -> CiphertextBlock
Adds a plaintext block to a ciphertext block (protect flavor).
Computes src_a + src_b where src_a is encrypted and src_b is plaintext.
Uses protect semantics — see Operation Flavors.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(4);
let blocks = builder.ciphertext_split(&ct);
let one = builder.block_let_plaintext(1);
let incremented = builder.block_add_plaintext(&blocks[0], &one);Sourcepub fn block_wrapping_add_plaintext(
&self,
src_a: impl AsRef<CiphertextBlock>,
src_b: impl AsRef<PlaintextBlock>,
) -> CiphertextBlock
pub fn block_wrapping_add_plaintext( &self, src_a: impl AsRef<CiphertextBlock>, src_b: impl AsRef<PlaintextBlock>, ) -> CiphertextBlock
Adds a plaintext block to a ciphertext block (wrapping flavor).
Computes src_a + src_b where src_a is encrypted and src_b is plaintext.
Uses wrapping semantics — see Operation Flavors.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(4);
let blocks = builder.ciphertext_split(&ct);
let one = builder.block_let_plaintext(1);
let incremented = builder.block_wrapping_add_plaintext(&blocks[0], &one);Sourcepub fn block_sub(
&self,
src_a: impl AsRef<CiphertextBlock>,
src_b: impl AsRef<CiphertextBlock>,
) -> CiphertextBlock
pub fn block_sub( &self, src_a: impl AsRef<CiphertextBlock>, src_b: impl AsRef<CiphertextBlock>, ) -> CiphertextBlock
Subtracts two ciphertext blocks (protect flavor).
Computes src_a - src_b at the block level. Uses protect semantics — see
Operation Flavors.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(4);
let blocks = builder.ciphertext_split(&ct);
let diff = builder.block_sub(&blocks[1], &blocks[0]);Sourcepub fn block_sub_plaintext(
&self,
src_a: impl AsRef<CiphertextBlock>,
src_b: impl AsRef<PlaintextBlock>,
) -> CiphertextBlock
pub fn block_sub_plaintext( &self, src_a: impl AsRef<CiphertextBlock>, src_b: impl AsRef<PlaintextBlock>, ) -> CiphertextBlock
Subtracts a plaintext block from a ciphertext block (protect flavor).
Computes src_a - src_b where src_a is encrypted and src_b is plaintext.
Uses protect semantics — see Operation Flavors.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(4);
let blocks = builder.ciphertext_split(&ct);
let one = builder.block_let_plaintext(1);
let decremented = builder.block_sub_plaintext(&blocks[0], &one);Sourcepub fn block_plaintext_sub(
&self,
src_a: impl AsRef<PlaintextBlock>,
src_b: impl AsRef<CiphertextBlock>,
) -> CiphertextBlock
pub fn block_plaintext_sub( &self, src_a: impl AsRef<PlaintextBlock>, src_b: impl AsRef<CiphertextBlock>, ) -> CiphertextBlock
Subtracts a ciphertext block from a plaintext block (protect flavor).
Computes src_a - src_b where src_a is plaintext and src_b is encrypted.
The result is a ciphertext block. Uses protect semantics — see
Operation Flavors. Note the reversed operand order
compared to block_sub_plaintext.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(4);
let blocks = builder.ciphertext_split(&ct);
let three = builder.block_let_plaintext(3);
let result = builder.block_plaintext_sub(&three, &blocks[0]); // 3 - blocks[0]Sourcepub fn block_pack(
&self,
src_a: impl AsRef<CiphertextBlock>,
src_b: impl AsRef<CiphertextBlock>,
) -> CiphertextBlock
pub fn block_pack( &self, src_a: impl AsRef<CiphertextBlock>, src_b: impl AsRef<CiphertextBlock>, ) -> CiphertextBlock
Packs two ciphertext blocks into one.
Computes src_a * 2^message_size + src_b, placing src_a in the high (carry)
bits and src_b in the low (message) bits of the resulting block. This is the
standard way to pack two blocks to be processed within a single programmable
bootstrapping (PBS) lookup.
§Panics
Panics if the builder’s carry_size != message_size, since packing requires
equal-width carry and message fields.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(4);
let blocks = builder.ciphertext_split(&ct);
let packed = builder.block_pack(&blocks[1], &blocks[0]);
let result = builder.block_lookup(&packed, Lut1Def::MsgOnly);Sourcepub fn block_pack_then_lookup(
&self,
src_a: impl AsRef<CiphertextBlock>,
src_b: impl AsRef<CiphertextBlock>,
lut: Lut1Def,
) -> CiphertextBlock
pub fn block_pack_then_lookup( &self, src_a: impl AsRef<CiphertextBlock>, src_b: impl AsRef<CiphertextBlock>, lut: Lut1Def, ) -> CiphertextBlock
Packs two ciphertext blocks and applies a single-output PBS lookup.
Equivalent to calling block_pack followed by
block_lookup. This is a convenience for the common
pack-then-lookup pattern.
§Panics
Panics if carry_size != message_size (see block_pack).
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(4);
let blocks = builder.ciphertext_split(&ct);
let result = builder.block_pack_then_lookup(&blocks[1], &blocks[0], Lut1Def::MsgOnly);Sourcepub fn block_lookup(
&self,
src: impl AsRef<CiphertextBlock>,
lut: Lut1Def,
) -> CiphertextBlock
pub fn block_lookup( &self, src: impl AsRef<CiphertextBlock>, lut: Lut1Def, ) -> CiphertextBlock
Applies a single-output programmable bootstrapping (PBS) lookup to a block.
The lut defines the function computed by the bootstrapping. The input block’s
full data bits (carry + message) index into the lookup table, and the result is a
fresh ciphertext block with clean noise.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(4);
let blocks = builder.ciphertext_split(&ct);
// Extract only the message bits, clearing the carry.
let clean = builder.block_lookup(&blocks[0], Lut1Def::MsgOnly);Sourcepub fn block_padding_lookup(
&self,
src: impl AsRef<CiphertextBlock>,
lut: Lut1Def,
) -> CiphertextBlock
pub fn block_padding_lookup( &self, src: impl AsRef<CiphertextBlock>, lut: Lut1Def, ) -> CiphertextBlock
Applies a single-output PBS lookup allowing output padding overflow.
Like block_lookup, but the bootstrapping allows the result
to overflow into the output padding bit. The input padding bit must still be clear
(protect semantics on the input side). This is useful when a subsequent operation
will consume the padding bit before the next lookup.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(4);
let blocks = builder.ciphertext_split(&ct);
let result = builder.block_padding_lookup(&blocks[0], Lut1Def::MsgOnly);Sourcepub fn block_wrapping_lookup(
&self,
src: impl AsRef<CiphertextBlock>,
lut: Lut1Def,
) -> CiphertextBlock
pub fn block_wrapping_lookup( &self, src: impl AsRef<CiphertextBlock>, lut: Lut1Def, ) -> CiphertextBlock
Applies a single-output PBS lookup using wrapping (negacyclic) semantics.
Like block_lookup, but uses wrapping semantics for the
lookup — see Operation Flavors. This is appropriate
when the input block’s padding bit may be set, enabling negacyclic lookup
behavior.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(4);
let blocks = builder.ciphertext_split(&ct);
let result = builder.block_wrapping_lookup(&blocks[0], Lut1Def::MsgOnly);Sourcepub fn block_lookup2(
&self,
src: impl AsRef<CiphertextBlock>,
lut: Lut2Def,
) -> (CiphertextBlock, CiphertextBlock)
pub fn block_lookup2( &self, src: impl AsRef<CiphertextBlock>, lut: Lut2Def, ) -> (CiphertextBlock, CiphertextBlock)
Applies a dual-output programmable bootstrapping (PBS) lookup to a block.
Like block_lookup, but the bootstrapping produces two
output blocks from a single input. The two lookup functions are defined by the
Lut2Def variant. This amortizes the cost of a PBS when two related values
need to be extracted simultaneously.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(4);
let blocks = builder.ciphertext_split(&ct);
let packed = builder.block_pack(&blocks[1], &blocks[0]);
let (msg, carry) = builder.block_lookup2(&packed, Lut2Def::ManyCarryMsg);Sourcepub fn block_let_ciphertext(&self, value: u8) -> CiphertextBlock
pub fn block_let_ciphertext(&self, value: u8) -> CiphertextBlock
Creates a constant CiphertextBlock with the given value.
The value is stored as a trivially-encrypted block (zero noise). This is useful
for initializing accumulators or providing constant operands in arithmetic. The
value’s bit-width must fit within the block’s data bits (carry + message).
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let zero = builder.block_let_ciphertext(0);
let ct = builder.ciphertext_input(4);
let blocks = builder.ciphertext_split(&ct);
let sum = builder.block_add(&zero, &blocks[0]); // 0 + blocks[0]Source§impl Builder
impl Builder
Sourcepub fn vector_pack(
&self,
blocks: impl AsRef<[CiphertextBlock]>,
) -> Vec<CiphertextBlock>
pub fn vector_pack( &self, blocks: impl AsRef<[CiphertextBlock]>, ) -> Vec<CiphertextBlock>
Packs consecutive pairs of blocks in a slice.
Iterates over blocks in chunks of two, calling block_pack
on each pair. Within each pair, the second element (blocks[2i+1]) goes to the
high bits and the first (blocks[2i]) to the low bits. If the slice has an odd
number of elements, the trailing block is passed through unchanged.
The output has length ceil(blocks.len() / 2).
§Panics
Panics if carry_size != message_size (see block_pack).
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(8);
let blocks = builder.ciphertext_split(&ct); // 4 blocks
let packed = builder.vector_pack(&blocks); // 2 packed blocksSourcepub fn vector_pack_then_clean(
&self,
blocks: impl AsRef<[CiphertextBlock]>,
) -> Vec<CiphertextBlock>
pub fn vector_pack_then_clean( &self, blocks: impl AsRef<[CiphertextBlock]>, ) -> Vec<CiphertextBlock>
Packs consecutive pairs and applies an identity PBS to clean noise.
Equivalent to calling vector_pack_then_lookup
with Lut1Def::None. The PBS acts as a noise-refresh: each packed pair is
bootstrapped through the identity function, producing a clean block. Trailing
odd blocks are passed through without bootstrapping.
§Panics
Panics if carry_size != message_size (see block_pack).
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(8);
let blocks = builder.ciphertext_split(&ct);
let cleaned = builder.vector_pack_then_clean(&blocks);Sourcepub fn vector_pack_then_lookup(
&self,
blocks: impl AsRef<[CiphertextBlock]>,
lut: Lut1Def,
) -> Vec<CiphertextBlock>
pub fn vector_pack_then_lookup( &self, blocks: impl AsRef<[CiphertextBlock]>, lut: Lut1Def, ) -> Vec<CiphertextBlock>
Packs consecutive pairs and applies a single-output PBS lookup to each.
Iterates over blocks in chunks of two: each pair is
block_packed and then passed through
block_lookup with the given lut. If the slice has an odd
number of elements, the trailing block is passed through unchanged (no PBS).
The output has length ceil(blocks.len() / 2).
§Panics
Panics if carry_size != message_size (see block_pack).
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(8);
let blocks = builder.ciphertext_split(&ct);
let results = builder.vector_pack_then_lookup(&blocks, Lut1Def::MsgOnly);Sourcepub fn vector_zip_then_lookup(
&self,
lhs: impl AsRef<[CiphertextBlock]>,
rhs: impl AsRef<[CiphertextBlock]>,
lut: Lut1Def,
extension: ExtensionBehavior,
) -> Vec<CiphertextBlock>
pub fn vector_zip_then_lookup( &self, lhs: impl AsRef<[CiphertextBlock]>, rhs: impl AsRef<[CiphertextBlock]>, lut: Lut1Def, extension: ExtensionBehavior, ) -> Vec<CiphertextBlock>
Zips two block slices, packs each pair, and applies a PBS lookup.
For each position, packs lhs[i] into the high bits and rhs[i] into the low
bits via block_pack, then passes the result through
block_lookup with the given lut. When the two slices
have different lengths, extension controls the behavior (see
ExtensionBehavior).
§Panics
Panics if carry_size != message_size (see block_pack), or
if the slices differ in length and extension is
Panic.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let a = builder.ciphertext_input(8);
let b = builder.ciphertext_input(8);
let a_blocks = builder.ciphertext_split(&a);
let b_blocks = builder.ciphertext_split(&b);
let results = builder.vector_zip_then_lookup(
&a_blocks,
&b_blocks,
Lut1Def::MsgOnly,
ExtensionBehavior::Panic,
);Sourcepub fn vector_lookup(
&self,
blocks: impl AsRef<[CiphertextBlock]>,
lut: Lut1Def,
) -> Vec<CiphertextBlock>
pub fn vector_lookup( &self, blocks: impl AsRef<[CiphertextBlock]>, lut: Lut1Def, ) -> Vec<CiphertextBlock>
Applies a single-output PBS lookup to every block in a slice.
Maps block_lookup over each element. Unlike
vector_pack_then_lookup, no packing is
performed — each block is bootstrapped independently.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(8);
let blocks = builder.ciphertext_split(&ct);
let cleaned = builder.vector_lookup(&blocks, Lut1Def::MsgOnly);Sourcepub fn vector_lookup2(
&self,
blocks: impl AsRef<[CiphertextBlock]>,
lut: Lut2Def,
) -> Vec<(CiphertextBlock, CiphertextBlock)>
pub fn vector_lookup2( &self, blocks: impl AsRef<[CiphertextBlock]>, lut: Lut2Def, ) -> Vec<(CiphertextBlock, CiphertextBlock)>
Applies a dual-output PBS lookup to every block in a slice.
Maps block_lookup2 over each element, returning a pair of
output blocks per input block.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(8);
let blocks = builder.ciphertext_split(&ct);
let packed = builder.vector_pack(&blocks);
let pairs = builder.vector_lookup2(&packed, Lut2Def::ManyCarryMsg);Sourcepub fn vector_add(
&self,
lhs: impl AsRef<[CiphertextBlock]>,
rhs: impl AsRef<[CiphertextBlock]>,
extension: ExtensionBehavior,
) -> Vec<CiphertextBlock>
pub fn vector_add( &self, lhs: impl AsRef<[CiphertextBlock]>, rhs: impl AsRef<[CiphertextBlock]>, extension: ExtensionBehavior, ) -> Vec<CiphertextBlock>
Adds two block slices element-wise.
For each position, calls block_add on the corresponding pair.
When the two slices have different lengths, extension controls the behavior (see
ExtensionBehavior).
§Panics
Panics if the slices differ in length and extension is
Panic.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let a = builder.ciphertext_input(8);
let b = builder.ciphertext_input(8);
let a_blocks = builder.ciphertext_split(&a);
let b_blocks = builder.ciphertext_split(&b);
let sums = builder.vector_add(&a_blocks, &b_blocks, ExtensionBehavior::Panic);Sourcepub fn vector_unsigned_extension(
&self,
inp: impl AsRef<[CiphertextBlock]>,
size: usize,
) -> Vec<CiphertextBlock>
pub fn vector_unsigned_extension( &self, inp: impl AsRef<[CiphertextBlock]>, size: usize, ) -> Vec<CiphertextBlock>
Zero-extends a block slice to a given length.
Pads inp with zero-valued constant ciphertext blocks
(block_let_ciphertext(0)) until the result
has size elements. This implements unsigned integer extension: the original
blocks represent the low-order radix digits, and the appended zeros represent
high-order digits.
§Panics
Panics if inp.len() > size.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(4); // 2 blocks
let blocks = builder.ciphertext_split(&ct);
let extended = builder.vector_unsigned_extension(&blocks, 4); // now 4 blocksSourcepub fn vector_add_reduce(
&self,
inp: impl AsRef<[CiphertextBlock]>,
) -> CiphertextBlock
pub fn vector_add_reduce( &self, inp: impl AsRef<[CiphertextBlock]>, ) -> CiphertextBlock
Reduces a block slice to a single block by summing all elements (protect flavor).
Folds block_add across every element, returning their
cumulative sum. This is useful for combining partial results from parallel
computations into a single accumulator block.
§Panics
Panics if inp is empty.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(8);
let blocks = builder.ciphertext_split(&ct);
let total = builder.vector_add_reduce(&blocks);Sourcepub fn vector_inspect(
&self,
inp: impl AsRef<[CiphertextBlock]>,
) -> Vec<CiphertextBlock>
pub fn vector_inspect( &self, inp: impl AsRef<[CiphertextBlock]>, ) -> Vec<CiphertextBlock>
Applies block_inspect to every block in a slice.
Each block is inspected with an index-based comment ("0", "1", …) appended to
the current comment stack. This is useful for labeling block positions in IR dumps.
§Examples
let builder = Builder::new(CiphertextBlockSpec(2, 2));
let ct = builder.ciphertext_input(8);
let blocks = builder.ciphertext_split(&ct);
let labeled = builder.comment("radix digits").vector_inspect(&blocks);Source§impl Builder
impl Builder
Sourcepub fn iop_add_hillis_steele(
&mut self,
lhs: &Ciphertext,
rhs: &Ciphertext,
) -> Ciphertext
pub fn iop_add_hillis_steele( &mut self, lhs: &Ciphertext, rhs: &Ciphertext, ) -> Ciphertext
Adds two encrypted integers with Hillis-Steele carry propagation.
The operation computes lhs + rhs (wrapping) using a parallel-prefix
carry-propagation scheme with 4-block grouping. Both operands must
share the same block decomposition as determined by the
CiphertextSpec used to create the builder. The result is a fresh
Ciphertext whose message bits have been cleaned of any residual
carry via a final programmable bootstrapping pass.
Non-power-of-two and non-multiple-of-four block counts are handled transparently: the computation is extended to a favourable size and dead-code elimination trims the unused portion downstream.
§Examples
let sum = builder.iop_add_hillis_steele(&a, &b);Source§impl Builder
impl Builder
Sourcepub fn iop_bitwise(
&mut self,
lhs: &Ciphertext,
rhs: &Ciphertext,
kind: BwKind,
) -> Ciphertext
pub fn iop_bitwise( &mut self, lhs: &Ciphertext, rhs: &Ciphertext, kind: BwKind, ) -> Ciphertext
Source§impl Builder
impl Builder
Sourcepub fn iop_cmp(
&self,
src_a: &Ciphertext,
src_b: &Ciphertext,
kind: CmpKind,
) -> Ciphertext
pub fn iop_cmp( &self, src_a: &Ciphertext, src_b: &Ciphertext, kind: CmpKind, ) -> Ciphertext
Compares two encrypted integers under the given relation.
The comparison proceeds in three phases: block-wise subtraction with
sign extraction, tree-based reduction of the per-block results, and a
final merge PBS that produces the boolean output. The kind parameter
selects which relation is evaluated (see CmpKind).
Both src_a and src_b must have the same block decomposition. The
returned Ciphertext is a single-block integer encoding the boolean
result: 1 when the relation holds, 0 otherwise.
§Examples
let is_eq = builder.iop_cmp(&a, &b, CmpKind::Equal);Source§impl Builder
impl Builder
Sourcepub fn iop_count(&mut self, inp: &Ciphertext, kind: BitType) -> Ciphertext
pub fn iop_count(&mut self, inp: &Ciphertext, kind: BitType) -> Ciphertext
Counts the number of zero or one bits in an encrypted integer.
The operation splits inp into individual bits, then performs a
recursive column-based reduction that sums them with carry
propagation. When kind is BitType::Zero, the first
reduction pass uses inverted look-up tables so that each bit
contributes 1 when it is zero. The returned Ciphertext has a
width of ⌈log₂(int_size + 1)⌉ bits, just enough to represent
every possible count from 0 to int_size.
§Examples
let pop = builder.iop_count(&a, BitType::One);Source§impl Builder
impl Builder
Sourcepub fn iop_if_then_else(
&self,
src_a: &Ciphertext,
src_b: &Ciphertext,
cond: &Ciphertext,
) -> Ciphertext
pub fn iop_if_then_else( &self, src_a: &Ciphertext, src_b: &Ciphertext, cond: &Ciphertext, ) -> Ciphertext
Selects between two encrypted integers based on an encrypted condition.
When cond is zero (false) the result equals src_a; when cond is
non-zero (true) the result equals src_b. The selection is performed
block-wise: each block of src_a is zeroed when the condition is true
and each block of src_b is zeroed when it is false, then the two
are added together.
Both src_a and src_b must have the same block decomposition, and
cond must be a single-block ciphertext (typically the output of a
comparison operation such as iop_cmp).
§Examples
let selected = builder.iop_if_then_else(&a, &b, &cond);Source§impl Builder
impl Builder
Sourcepub fn iop_if_then_zero(
&self,
src: &Ciphertext,
cond: &Ciphertext,
) -> Ciphertext
pub fn iop_if_then_zero( &self, src: &Ciphertext, cond: &Ciphertext, ) -> Ciphertext
Zeroes an encrypted integer when a condition is true.
When cond is zero (false) the result equals src; when cond is
non-zero (true) the result is zero. The operation applies a single
programmable bootstrapping per block, making it cheaper than a full
iop_if_then_else.
The cond operand must be a single-block ciphertext (typically the
output of a comparison operation such as
iop_cmp).
§Examples
let zeroed = builder.iop_if_then_zero(&src, &cond);Source§impl Builder
impl Builder
pub fn iop_ilog2(&self, src: impl AsRef<Ciphertext>) -> Ciphertext
pub fn iop_trail0(&self, src: impl AsRef<Ciphertext>) -> Ciphertext
pub fn iop_trail1(&self, src: impl AsRef<Ciphertext>) -> Ciphertext
pub fn iop_lead0(&self, src: impl AsRef<Ciphertext>) -> Ciphertext
pub fn iop_lead1(&self, src: impl AsRef<Ciphertext>) -> Ciphertext
Source§impl Builder
impl Builder
Sourcepub fn iop_mul_raw(
&self,
src_a_blocks: &Vec<CiphertextBlock>,
src_b_blocks: &Vec<CiphertextBlock>,
cut_off_block: u8,
) -> (CiphertextBlock, Vec<CiphertextBlock>)
pub fn iop_mul_raw( &self, src_a_blocks: &Vec<CiphertextBlock>, src_b_blocks: &Vec<CiphertextBlock>, cut_off_block: u8, ) -> (CiphertextBlock, Vec<CiphertextBlock>)
Multiply two ciphertext in a raw fashion. I.e. Compute all output up to cut-off point then only overflow flag status. This function should be wrapped specialized instances that select the desired output information and use the deadcode analysis to remove useless part
The muliplication is done in two phases:
- Expansion: generate all the partial product
- Reduction: sum partial product and propagate the carry
Overflow computation also uses same phases, whith slight differences:
- Expansion: only compute NonNull flag of the product
- Reduction: sum NonNull flag (no carry propagation)
§Examples
let (flag, res) = builder.iop_mul_raw(&a, &b, spec.block_count());