Skip to main content

🔐 concrete-fhe-toolkit

Bounded math helpers for compiling common Zama Concrete FHE circuits.

Compare, sort, and run real math — GCD, factorial, sin, sqrt, division — directly on encrypted integers, without ever decrypting them.

PyPI version Python versions CI License: MIT Concrete Status Downloads

Installation · Quick Start · Documentation · Examples · Public API


✨ Highlights

  • Batteries-included math — arithmetic, comparisons, GCD/LCM, factorial, primality, isqrt, and combinatorics on encrypted integers.
  • Fixed-point transcendentals — sin, cos, log, sqrt, erf, tanh, and sigmoid via lookup tables over a declared domain.
  • Array circuits — bitonic sort, min/max, and argmin/argmax with deterministic tie handling.
  • Real division — bounded floor division and bit-level restoring division that scales with bit width instead of a giant lookup table.
  • Friendly, three-layer API — call fhe_math.gcd(...) to compile, .make(...) to compose, or .compile(...) to be explicit.
  • Cost guardrails — static lookup-size estimates warn (or block) before you accidentally compile an enormous, memory-hungry circuit.

📑 Table of Contents

What it does

The package focuses on explicit, bounded FHE circuits:

  • compare two encrypted integers
  • add, subtract, multiply, negate, square, and compare encrypted integers
  • compute encrypted integer math such as abs, clamp, modulo, GCD, LCM, isqrt, primality, factorial, Fibonacci, combinations, and permutations
  • approximate fixed-point math such as floor, ceil, round, rescale, sin, cos, log, sqrt, erf, tanh, and sigmoid with lookup tables
  • compare-swap two encrypted integers
  • sort encrypted integer arrays
  • find encrypted-array min/max values
  • find encrypted-array argmin/argmax indices
  • perform bounded encrypted floor division with a table lookup
  • perform bounded numerator // (left * right)

Installation

pip install concrete-fhe-toolkit

To install the latest source directly from GitHub:

pip install git+https://github.com/ErkanIsikB/concrete-fhe-toolkit.git

Or clone the repository and install it locally:

git clone https://github.com/ErkanIsikB/concrete-fhe-toolkit.git
cd concrete-fhe-toolkit
pip install .

Development setup

For contributing or running the test suite, install the pinned dependencies and the package in editable mode:

git clone https://github.com/ErkanIsikB/concrete-fhe-toolkit.git
cd concrete-fhe-toolkit
python -m pip install -r requirements-dev.txt
python -m pip install -e ".[dev]"
python -m pytest -q

Two convenience requirements files are provided:

File Contents
requirements.txt Runtime dependencies (concrete-python, numpy)
requirements-dev.txt Runtime plus build, pytest, and twine

Important Concept

Most users can start with the plain operation names in concrete_fhe_toolkit.math. Calling one of these operations compiles a Concrete circuit by default:

from concrete_fhe_toolkit import math as fhe_math

gcd = fhe_math.gcd(min_value=0, max_value=8)
print(gcd.encrypt_run_decrypt(6, 4))
# 2

These operation objects also expose the lower-level builders:

  • fhe_math.gcd.compile(...) compiles a ready-to-run encrypted circuit.
  • fhe_math.gcd.make(...) returns a traceable function that can be composed into a larger Concrete program.

The explicit compile_* and make_* names remain available for advanced users and backwards compatibility. For example, fhe_math.compile_gcd(...) is the same compiler used by fhe_math.gcd(...), and fhe_math.make_gcd(...) is the same builder used by fhe_math.gcd.make(...).

Use make_* only when you are manually composing several operations into one larger circuit. For ordinary one-operation usage, prefer the plain operation name or the explicit compile_* helper.

All operations use integer inputs. You must choose fixed input bounds when compiling a circuit, and runtime inputs must stay inside those bounds.

The bounds are not the hidden minimum and maximum of a particular encrypted array. They are public, application-level limits that you know before compilation. For example, if your encrypted scores are always percentages, use min_value=0 and max_value=100. If your private balances are stored in a range from -1_000 to 1_000, use those as the bounds. Wider bounds are more flexible, but they usually make the compiled circuit more expensive.

The concrete_fhe_toolkit.math subpackage is intentionally closer to Python's math module, but every operation still needs explicit bounds because FHE circuits are compiled ahead of time. Fixed-point helpers accept encrypted integers that represent scaled real values. For example, with input_scale=100, the encrypted integer 314 represents 3.14.

Common bound parameters:

Parameter style Meaning
min_value, max_value Inclusive input range for one or two integer inputs
min_input, max_input Inclusive input range for scaled fixed-point values
max_n Encrypted index range 0..max_n, used by factorial/Fibonacci/combinatorics
numerator_width, denominator_width Unsigned scalar bit widths for bit-level division
fractional_bits Number of binary fractional bits in fixed-point division output

Quick Start

import numpy as np

from concrete_fhe_toolkit import compile_argmin, compile_sort

values = np.array([12, 3, 7, 1, 15, 0, 4, 9], dtype=np.int64)

sort_circuit = compile_sort(size=8, min_value=0, max_value=100)
print(sort_circuit.encrypt_run_decrypt(values))
# [ 0  1  3  4  7  9 12 15]

argmin_circuit = compile_argmin(size=8, min_value=0, max_value=100)
print(argmin_circuit.encrypt_run_decrypt(values))
# 5

Math helpers live under concrete_fhe_toolkit.math:

from concrete_fhe_toolkit import math as fhe_math

gcd_circuit = fhe_math.gcd(min_value=0, max_value=8)
print(gcd_circuit.encrypt_run_decrypt(6, 4))
# 2

sin_circuit = fhe_math.sin(
    min_input=0,
    max_input=90,
    input_scale=1,
    output_scale=100,
    angle_unit="degrees",
)
print(sin_circuit.encrypt_run_decrypt(30))
# 50  (represents 0.50)

Documentation

The README is the shared GitHub and PyPI landing page. It gives the core usage model, quick examples, and the public API overview.

For the fuller guide, use the GitHub documentation:

The short version: use fhe_math.gcd(...) or another friendly operation name for a ready-to-run circuit, use .make(...) when composing a larger Concrete program, and choose public bounds that cover all runtime inputs.

Public API

Scalar arithmetic:

  • sign(x, y)
  • compile_sign(min_value=-15, max_value=15, configuration=None)
  • make_floor_divide(zero_result=0)
  • compile_floor_divide(max_numerator, max_denominator, zero_result=0, configuration=None)
  • make_floor_divide_by_product(zero_result=0)
  • compile_floor_divide_by_product(max_numerator, max_left, max_right, zero_result=0, configuration=None)

Array operations:

  • make_compare_swap(min_value=0, max_value=15)
  • compile_compare_swap(min_value=0, max_value=15, configuration=None)
  • make_sort(size, min_value=0, max_value=15, descending=False)
  • compile_sort(size, min_value=0, max_value=15, descending=False, configuration=None)
  • make_minimum(size, min_value=0, max_value=15)
  • compile_minimum(size, min_value=0, max_value=15, configuration=None)
  • make_maximum(size, min_value=0, max_value=15)
  • compile_maximum(size, min_value=0, max_value=15, configuration=None)
  • make_argmin(size, min_value=0, max_value=15, tie_break="first")
  • compile_argmin(size, min_value=0, max_value=15, tie_break="first", configuration=None)
  • make_argmax(size, min_value=0, max_value=15, tie_break="first")
  • compile_argmax(size, min_value=0, max_value=15, tie_break="first", configuration=None)

Math subpackage:

from concrete_fhe_toolkit import math as fhe_math

Friendly operation objects compile by default and expose .make(...) and .compile(...) for explicit control:

  • absolute, clamp, modulo, divmod, is_close
  • factorial, fibonacci, power, comb, perm
  • gcd, lcm, is_coprime, is_divisible, isqrt, is_even, is_odd, is_prime
  • floor, ceil, trunc, round, floor_ceil, rescale
  • sin, cos, tan, exp, expm1, log, log1p, log2, log10, sqrt, erf, erfc, tanh, sinh, cosh, sigmoid
  • unsigned_floor_divide, fixed_point_divide

Basic integer operations:

  • native arithmetic: add, subtract, multiply, negate, square
  • comparisons: equal, not_equal, less, less_equal, greater, greater_equal
  • compilers: compile_add, compile_subtract, compile_multiply, compile_negate, compile_square, compile_scalar_multiply
  • lookup helpers: make_absolute, compile_absolute, make_clamp, compile_clamp, make_modulo, compile_modulo, make_divmod, compile_divmod, make_is_close, compile_is_close

Combinatorics:

  • make_factorial, compile_factorial
  • make_fibonacci, compile_fibonacci
  • make_power, compile_power
  • make_comb, compile_comb
  • make_perm, compile_perm

Number theory:

  • make_gcd, compile_gcd
  • make_lcm, compile_lcm
  • make_is_coprime, compile_is_coprime
  • make_is_divisible, compile_is_divisible
  • make_isqrt, compile_isqrt
  • make_is_even, compile_is_even
  • make_is_odd, compile_is_odd
  • make_is_prime, compile_is_prime

Fixed-point helpers:

  • make_floor, compile_floor
  • make_ceil, compile_ceil
  • make_trunc, compile_trunc
  • make_round, compile_round
  • make_floor_ceil, compile_floor_ceil
  • make_rescale, compile_rescale

Special fixed-point helpers:

  • trigonometric: make_sin, compile_sin, make_cos, compile_cos, make_tan, compile_tan
  • exponential/logarithmic: make_exp, compile_exp, make_expm1, compile_expm1, make_log, compile_log, make_log1p, compile_log1p, make_log2, compile_log2, make_log10, compile_log10
  • roots/special activations: make_sqrt, compile_sqrt, make_erf, compile_erf, make_erfc, compile_erfc, make_tanh, compile_tanh, make_sinh, compile_sinh, make_cosh, compile_cosh, make_sigmoid, compile_sigmoid

Bit-level arithmetic helpers:

  • bit primitives: bit_not, bit_and, bit_or, bit_xor, bit_select, full_adder_bit, full_subtractor_bit
  • conversion helpers: integer_to_bits, bits_to_unsigned, unsigned_to_bits, twos_complement_bits
  • signed bit helpers: sign_magnitude_to_twos_complement_bits, twos_complement_add_bits, twos_complement_multiply_by_constant_bits
  • binary division: unsigned_divide_bits, fixed_point_divide_bits, make_unsigned_floor_divide, compile_unsigned_floor_divide, make_fixed_point_divide, compile_fixed_point_divide

Examples

Sign Comparison

compile_sign returns 1 when x > y, 0 when x == y, and -1 when x < y.

from concrete_fhe_toolkit import compile_sign

circuit = compile_sign(min_value=-20, max_value=20)

print(circuit.encrypt_run_decrypt(15, 3))
# 1

print(circuit.encrypt_run_decrypt(3, 15))
# -1

print(circuit.encrypt_run_decrypt(7, 7))
# 0

Compare-Swap

compile_compare_swap returns two values in ascending order.

from concrete_fhe_toolkit import compile_compare_swap

circuit = compile_compare_swap(min_value=0, max_value=100)

print(circuit.encrypt_run_decrypt(12, 3))
# (3, 12)

Sort

compile_sort sorts a fixed-size encrypted array. The size must be a power of two.

import numpy as np

from concrete_fhe_toolkit import compile_sort

values = np.array([12, 3, 7, 1, 15, 0, 4, 9], dtype=np.int64)

ascending = compile_sort(size=8, min_value=0, max_value=100)
print(ascending.encrypt_run_decrypt(values))
# [ 0  1  3  4  7  9 12 15]

descending = compile_sort(size=8, min_value=0, max_value=100, descending=True)
print(descending.encrypt_run_decrypt(values))
# [15 12  9  7  4  3  1  0]

Minimum and Maximum

compile_minimum and compile_maximum return the smallest or largest value in a fixed-size encrypted array.

import numpy as np

from concrete_fhe_toolkit import compile_maximum, compile_minimum

values = np.array([12, 5, 7, 2, 15, 9, 4, 14], dtype=np.int64)

minimum = compile_minimum(size=8, min_value=0, max_value=100)
print(minimum.encrypt_run_decrypt(values))
# 2

maximum = compile_maximum(size=8, min_value=0, max_value=100)
print(maximum.encrypt_run_decrypt(values))
# 15

Argmin and Argmax

compile_argmin and compile_argmax return the index of the smallest or largest value. By default, ties return the first matching index.

import numpy as np

from concrete_fhe_toolkit import compile_argmax, compile_argmin

values = np.array([12, 5, 7, 1, 15, 9, 4, 14], dtype=np.int64)

argmin = compile_argmin(size=8, min_value=0, max_value=100)
print(argmin.encrypt_run_decrypt(values))
# 3

argmax = compile_argmax(size=8, min_value=0, max_value=100)
print(argmax.encrypt_run_decrypt(values))
# 4

Tie handling is explicit:

import numpy as np

from concrete_fhe_toolkit import compile_argmin

values = np.array([4, 1, 1, 3], dtype=np.int64)

first = compile_argmin(size=4, min_value=0, max_value=10, tie_break="first")
print(first.encrypt_run_decrypt(values))
# 1

last = compile_argmin(size=4, min_value=0, max_value=10, tie_break="last")
print(last.encrypt_run_decrypt(values))
# 2

Floor Division

Direct x // y does not compile when both values are encrypted in Concrete 2.11. This package implements bounded floor division with fhe.multivariate.

from concrete_fhe_toolkit import compile_floor_divide

circuit = compile_floor_divide(
    max_numerator=100,
    max_denominator=10,
    zero_result=-1,
)

print(circuit.encrypt_run_decrypt(15, 3))
# 5

print(circuit.encrypt_run_decrypt(15, 0))
# -1

zero_result is returned when the encrypted denominator is zero.

Division by an Encrypted Product

compile_floor_divide_by_product computes numerator // (left * right).

from concrete_fhe_toolkit import compile_floor_divide_by_product

circuit = compile_floor_divide_by_product(
    max_numerator=100,
    max_left=10,
    max_right=10,
    zero_result=-1,
)

print(circuit.encrypt_run_decrypt(20, 2, 5))
# 2

print(circuit.encrypt_run_decrypt(20, 0, 5))
# -1

Math Subpackage

Use concrete_fhe_toolkit.math for Python-math-style helpers. Some compile to native Concrete arithmetic, while functions such as GCD, factorial, prime testing, and fixed-point sin use lookup tables over the declared domain.

from concrete_fhe_toolkit import math as fhe_math

factorial = fhe_math.factorial(max_n=7)
print(factorial.encrypt_run_decrypt(5))
# 120

modulo = fhe_math.modulo(
    min_numerator=0,
    max_numerator=20,
    min_denominator=0,
    max_denominator=10,
    zero_result=-1,
)
print(modulo.encrypt_run_decrypt(17, 5))
# 2

tanh = fhe_math.tanh(
    min_input=-30,
    max_input=30,
    input_scale=10,
    output_scale=100,
)
print(tanh.encrypt_run_decrypt(20))
# 96  (represents tanh(2.0) ~= 0.96)

The plain operation name compiles by default. If you want to be explicit, use .compile(...):

from concrete_fhe_toolkit import math as fhe_math

gcd = fhe_math.gcd.compile(min_value=0, max_value=8)
print(gcd.simulate(6, 4))
# 2

Use .make(...) when composing a larger circuit yourself:

from concrete import fhe
from concrete_fhe_toolkit import math as fhe_math

absolute = fhe_math.absolute.make(-8, 8)
gcd = fhe_math.gcd.make(0, 8)

def program(x, y):
    return gcd(absolute(x), y)

compiler = fhe.Compiler(program, {"x": "encrypted", "y": "encrypted"})
circuit = compiler.compile([(x, y) for x in range(-8, 9) for y in range(9)])
print(circuit.encrypt_run_decrypt(-6, 4))
# 2

Functions with undefined mathematical inputs require an explicit encrypted fallback if the compiled domain includes invalid values:

from concrete_fhe_toolkit import math as fhe_math

log2 = fhe_math.log2(
    min_input=0,
    max_input=16,
    invalid_result=-1,
)
print(log2.encrypt_run_decrypt(0))
# -1

Large lookup tables are available, but the package asks you to opt in when a static estimate suggests the circuit may need substantial Concrete resources:

from concrete_fhe_toolkit import math as fhe_math

gcd = fhe_math.gcd(
    min_value=0,
    max_value=31,
    allow_large_lookup=True,
)
print(gcd.simulate(24, 18))
# 6

Bit-Level Fixed-Point Division

unsigned_floor_divide and fixed_point_divide use a restoring binary division circuit instead of a full lookup table. They are useful when you want division logic that scales with bit width rather than with every possible (numerator, denominator) value.

from concrete_fhe_toolkit import math as fhe_math

floor_divide = fhe_math.unsigned_floor_divide(
    numerator_width=8,
    denominator_width=8,
    zero_result=0,
)
print(floor_divide.encrypt_run_decrypt(100, 7))
# 14

fixed_divide = fhe_math.fixed_point_divide(
    numerator_width=8,
    denominator_width=8,
    fractional_bits=8,
)
print(fixed_divide.encrypt_run_decrypt(1, 3))
# 85  (represents floor((1 / 3) * 256))

Inputs for these helpers must be unsigned integers that fit in the declared bit widths. Division by zero returns zero_result.

Composing make_* Helpers

Use make_* helpers when you want to build a larger Concrete function yourself. When compiling manually, your inputset must cover the bounds you declared.

import numpy as np
from concrete import fhe

from concrete_fhe_toolkit import make_minimum

minimum_of_four = make_minimum(size=4, min_value=0, max_value=100)
compiler = fhe.Compiler(minimum_of_four, {"x": "encrypted"})

inputset = [
    np.array([0, 0, 0, 0], dtype=np.int64),
    np.array([100, 100, 100, 100], dtype=np.int64),
    np.array([0, 100, 0, 100], dtype=np.int64),
    np.array([100, 0, 100, 0], dtype=np.int64),
]

circuit = compiler.compile(inputset)
print(circuit.encrypt_run_decrypt(np.array([8, 3, 12, 5], dtype=np.int64)))
# 3

For most use cases, prefer the compile_* helpers because they generate boundary-aware inputsets automatically.

Bounds and Limitations

  • Inputs must stay inside the bounds used at compilation.
  • Plain concrete_fhe_toolkit.math operation names compile by default. Use .make(...) only when composing larger circuits manually.
  • Sorting requires a power-of-two array size.
  • Min/max and argmin/argmax support any positive fixed size.
  • Division helpers currently support nonnegative bounded inputs.
  • Bit-level division helpers support unsigned scalar inputs that fit in the declared bit widths and return integer or scaled-integer outputs.
  • Larger bounds increase table-lookup bit width and cost. Some math helpers raise LookupResourceError unless you pass allow_large_lookup=True.
  • concrete_fhe_toolkit.math fixed-point helpers return scaled integers, not Python floats.
  • Lookup-table outputs that require very large integer bit widths may exceed Concrete's available parameter set even if you opt in to large lookups.
  • Concrete supports integer inputs and outputs, not arbitrary floating point.
  • FHE execution is probabilistic; choose Concrete error parameters appropriate for the application's risk.

Notebook provenance and examples

This package was derived from these Kaggle experiments:

Additional usage notebook:

The notebooks are useful background and examples, but the README and docs/ are the canonical documentation for the current package API. Existing make_* and compile_* package APIs were kept for compatibility, while the new concrete_fhe_toolkit.math friendly operation names provide the preferred high-level interface for new code.

Contributing

Contributions are welcome! See CONTRIBUTING.md for how to set up a development environment, run the test suite, and submit changes. In short:

python -m pip install -r requirements-dev.txt
python -m pip install -e ".[dev]"
python -m pytest -q

License and Concrete Terms

The package's original code is available under the MIT License. MIT permits commercial and private use, modification, and redistribution while requiring the copyright and license notice to be preserved.

Concrete is a separate dependency with its own BSD-3-Clause-Clear license and patent terms. This package does not change or grant rights under Concrete's license. Review Zama's current licensing terms before commercial use.

This project is not affiliated with or endorsed by Zama.

Author and Contributors

Author and maintainer

Contributors

Organizational contributor

Thanks

  • Zama and the Concrete team, whose FHE compiler makes this toolkit possible.
  • The wider Zama open-source and FHE research community for tutorials, discussions, and example circuits.
  • Everyone who tested the Kaggle notebooks and reported feedback that shaped the bounded, cost-aware API.

If you use concrete-fhe-toolkit in your project, a ⭐ on the GitHub repository is always appreciated.

References

Background reading on the tools and techniques this package builds on:

Metadata

Release files for concrete-fhe-toolkit 0.2.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for concrete-fhe-toolkit 0.2.2
File Size Uploaded
concrete_fhe_toolkit-0.2.2.tar.gz 52.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for concrete-fhe-toolkit 0.2.2
File Interpreter ABI Platform
concrete_fhe_toolkit-0.2.2-py3-none-any.whl Python 3 none any Details

Total release size: 87.2 kB

Release files / concrete_fhe_toolkit-0.2.2.tar.gz

Download URL concrete_fhe_toolkit-0.2.2.tar.gz
Size 52.6 kB
Tags Source
SHA-256 checksum
How to use checksums
f3260d1cfb41cf3cdb3793c36bf0850c729188f2a24ab05c8c26897e061e359f
BLAKE2b-256 checksum
How to use checksums
ad1930b4cf05399d17ce52dedd375bfb9e9dbb1399319b304efa7203448f0bd2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.12

Release files / concrete_fhe_toolkit-0.2.2-py3-none-any.whl

Download URL concrete_fhe_toolkit-0.2.2-py3-none-any.whl
Size 34.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4087f4a38f713168bc337083849b3ba0fddf013dfd9d8b58eb97f637cc20b13f
BLAKE2b-256 checksum
How to use checksums
73088a504f02ac434ddab75b778d162db4317710cc219bc74586898a42707fe6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.12

Release history Release notifications | RSS feed

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

This release

0.2.2 This release

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page