Self-hosted service
OpenMined/TenSEAL avatar
OpenMined/TenSEAL

TenSEAL: CKKS and BFV Tensor Operations on Top of Microsoft SEAL

A library for doing homomorphic encryption operations on tensors

1,037 stars175 forksC++Apache-2.0

At a glance

What is it?
TenSEAL wraps Microsoft SEAL in a Python API and adds an N-dimensional encrypted tensor layer. It is a good fit when you already understand SEAL's parameter trade-offs, and a poor fit if you want the library to pick those parameters for you.
Who is it for?
Adopt TenSEAL if your team already reasons in SEAL terms (poly_modulus_degree, coeff_mod_bit_sizes, global_scale) and wants vector and matrix arithmetic without writing C++ bindings by hand. Do not adopt it if you need the library to choose cryptographic parameters for you, if you depend on the published Docker images, or if you are on Xcode 16 or newer and cannot switch toolchains.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly C++, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The gap TenSEAL fills between SEAL and a working tensor pipeline

Microsoft SEAL is a C++ library. It gives you encryption, evaluation and decryption primitives, but the unit of work is a polynomial, not a vector or a matrix. If you want to add two encrypted arrays of floats, multiply them element-wise, or compute a dot product, you write that arithmetic yourself against the SEAL API. TenSEAL exists to remove that step. The README describes it as "a library for doing homomorphic encryption operations on tensors, built on top of Microsoft SEAL," with a Python API in front and most operations implemented in C++.

The target user is fairly narrow. This is not a tool for someone who wants encrypted machine learning as a service. It is for an engineer or researcher who has already decided on homomorphic encryption, has a computation that can be expressed as vector and matrix arithmetic, and wants to prototype that computation in Python before committing to a C++ implementation. The README points at a tutorial section for machine learning applications, which suggests that is the intended audience, but the tutorial content is not reproduced in the material available here.

One design decision is worth naming early, because it shapes everything else. TenSEAL does not hide SEAL's parameters. The README states plainly that the library "is a binding over Microsoft SEAL plus a tensor layer on top, so the encryption parameters below (poly_modulus_degree, coeff_mod_bit_sizes, global_scale) carry exactly their SEAL meaning." That is a deliberate choice, and it means the library inherits both SEAL's flexibility and SEAL's failure modes.

What the CKKS example actually shows about the API surface

The README's usage example is short enough to read as a specification of the core API. It constructs a context with ts.context, passing ts.SCHEME_TYPE.CKKS, poly_modulus_degree=8192 and coeff_mod_bit_sizes=[60, 40, 40, 60]. It then calls context.generate_galois_keys() and sets context.global_scale = 2**40 before any encryption happens.

From there the flow is ordinary Python. ts.ckks_vector(context, v1) produces an encrypted vector from a plaintext list. The + operator on two encrypted vectors returns an encrypted result, and .decrypt() returns something the README writes as "~ [4, 4, 4, 4, 4]". The tilde is not decoration. CKKS is approximate arithmetic, so decrypted values are close to the plaintext result rather than exactly equal to it. The same pattern holds for enc_v1.dot(enc_v2), documented as "~ [10]", and for enc_v1.matmul(matrix), documented as "~ [157, -90, 153]".

The features list confirms the shape of the supported operations: element-wise addition, subtraction and multiplication for both encrypted-encrypted and encrypted-plain pairs, dot product, vector-matrix multiplication, and N-dimensional tensors through CKKSTensor and BFVTensor with reshape, broadcast and transpose. Serialization covers contexts, keys and encrypted tensors. There is also a completeness claim worth noting: the README says the complete SEAL API is exposed under tenseal.sealapi, which means anything the tensor layer does not cover is still reachable.

What the example does not show is equally informative. There is no parameter selection helper, no noise budget estimator, and no guidance in the material on how many multiplications the chosen coeff_mod_bit_sizes will support. The README defers entirely to the SEAL documentation and its CKKS examples for "what the values mean and how to pick them."

Installation paths and the version floors that come with them

The straightforward path is pip install tenseal. The README states that TenSEAL requires Python 3.11 or newer and depends on NumPy. Prebuilt wheels are listed for Linux glibc x86-64 (manylinux_2_28_x86_64), Linux musl x86-64 (musllinux_1_2_x86_64), Linux glibc aarch64 (manylinux_2_28_aarch64), macOS Apple Silicon (macosx_14_0_arm64) and Windows x64 (win_amd64). The Linux wheels split by libc, and pip selects between the manylinux and musllinux variants automatically, including for Alpine-based containers. A source distribution is published too, so pip install tenseal falls back to compiling from source on platforms without a wheel.

Verifying the install is one line: import tenseal as ts, then print(ts.__version__).

If your interpreter is older than 3.11, or you want isolation, the README offers uv as the recommended route. uv venv --python 3.13 creates an environment, and uv pip install tenseal installs into it. For a throwaway session, uv run --python 3.13 --with tenseal python fetches Python 3.13 and TenSEAL and discards the environment afterwards. The README says any version from 3.11 to 3.14 works in place of 3.13. Conda users get an explicit warning: TenSEAL is not on conda-forge, so the documented pattern is conda create -n tenseal python=3.13, conda activate tenseal, then pip install tenseal inside that environment.

Building from source needs a C++17 toolchain and CMake 3.14 or newer: GCC >= 7 or Clang >= 5 on Linux, Xcode 15.x command line tools on macOS, Visual Studio 2017 or newer on Windows. The build command is pip install . Third-party dependencies including Microsoft SEAL and Protocol Buffers are fetched and built by CMake, with no manual installation and no submodules to initialise.

Two documented build failures and one abandoned distribution channel

The README is unusually candid about breakage, and the details matter more than the feature list for anyone planning to build from source.

The first is CMake 4.0 and newer. The build fails with "Compatibility with CMake < 3.5 has been removed" because some vendored dependencies still declare a pre-3.5 minimum. The documented workaround is to set CMAKE_POLICY_VERSION_MINIMUM=3.5 in your environment before building. That is an environment variable, not a project setting, so it has to be present in whatever shell or CI job invokes pip install .

The second is macOS. AppleClang 17 and newer (Xcode 16 and later) currently fail to compile the vendored xtensor. The README's remedy is to use Xcode 15.x or install the published wheel instead. On Apple Silicon the wheel exists, so this is mostly a problem for people who need a source build on a newer toolchain.

The third is Docker. The README marks this section as a TODO and states that the images on Docker Hub are unmaintained, with the newest published in 2021 for v0.3.4. Both openmined/tenseal and openmined/tenseal:dev are described as far behind the current release. The docker-images/ directory targets Python 3.6 through 3.9, all end-of-life and below the supported minimum. The README's instruction is direct: do not rely on these images. Refreshing or retiring them is tracked as future work.

There is also a Bazel TODO. The README says the Bazel build is currently broken and its workflow runs on demand. The material is truncated at that point, so nothing further about the Bazel path can be confirmed.

Where TenSEAL is the wrong tool

The clearest limitation is the one the README creates by omission: parameter selection is entirely on you. poly_modulus_degree, coeff_mod_bit_sizes and global_scale determine both the security level and how many operations you can chain before the noise budget is exhausted, and the README hands that responsibility to the SEAL documentation. If your team does not have someone who can reason about multiplicative depth and modulus chains, TenSEAL will produce results that decrypt correctly in a toy example and drift or fail in a real pipeline. The library gives you no guardrail here.

CKKS is approximate by construction, which the README signals with tilde-prefixed outputs. If your computation needs exact integer results, you want the BFV path, which the features list describes as encryption and decryption of vectors of integers. Choosing CKKS for a workload that needs exactness is a mistake the API will not catch.

Platform support is another boundary. Python 3.11 is the floor, and the wheel matrix covers five platform and libc combinations. Anything else compiles from source, which pulls in the two build failures above. If you are on Xcode 16 or newer and cannot use a wheel, you are currently stuck.

Finally, the distribution channel people reach for first is the one the README tells you to avoid. Anyone whose deployment model assumes a maintained openmined/tenseal image is working against a tag from 2021, four minor versions behind the v0.3.17 release listed for the project.

How TenSEAL differs from using Microsoft SEAL directly

The honest alternative is Microsoft SEAL itself. The difference is not cryptographic. TenSEAL is a binding over SEAL, so the underlying scheme implementations, the parameter semantics and the noise behaviour are SEAL's. What changes is the unit of abstraction and the language you write in.

In raw SEAL, you work with Ciphertext and Plaintext objects and the operations SEAL exposes on them. A vector addition is a loop you write, and a matrix multiplication is a decomposition you design. TenSEAL adds CKKSTensor and BFVTensor on top, with reshape, broadcast and transpose, and exposes dot and matmul as first-class operations. That is the entire value proposition, and it is a real one if your computation is naturally expressed as linear algebra.

The trade-off runs the other way too. Going through a Python binding means the tensor layer is one more thing between you and the evaluator. TenSEAL mitigates this by implementing most operations in C++ rather than Python, and by exposing the complete SEAL API under tenseal.sealapi so you can drop down when the tensor abstraction does not fit. But if your computation is not tensor-shaped, or if you need fine control over relinearization and rescaling decisions that the tensor layer makes for you, the abstraction is overhead rather than help.

A second alternative is to pick a different scheme family entirely. SEAL and therefore TenSEAL cover BFV and CKKS. If your threat model or performance envelope points at a different scheme, TenSEAL is not the layer to build on, because the scheme choice is baked into the context construction.

Release cadence, licence and what to check before adopting

The release history in the material is uneven. v0.3.15 is dated 2024-09-30. v0.3.16 and v0.3.17 both land on 2026-08-03 and 2026-08-04 respectively, roughly ten months later and one day apart. The last push to the default branch is 2026-09-08. Two releases a day apart after a long gap is a pattern worth understanding before you pin a version, because it usually means either a batch of accumulated fixes or a release-process correction. The material does not say which.

On the upgrade side, the concrete costs are the two documented build failures. A CMake 4.0 environment needs CMAKE_POLICY_VERSION_MINIMUM=3.5 set before every source build, which means it belongs in your CI configuration and not just your shell. A macOS build on Xcode 16 or newer needs either a toolchain downgrade or the published wheel. Neither is a one-time cost if you build from source on a rolling toolchain.

The licence is Apache-2.0. That is a permissive licence with an explicit patent grant, which matters for a cryptography library because it reduces patent risk for adopters. It is not legal advice, and the interaction between Apache-2.0 and the licences of the vendored dependencies that CMake fetches automatically is something your own review has to cover. The README does not enumerate those dependency licences.

Before adopting, check whether a wheel exists for your exact platform and libc, confirm your compiler and CMake versions against the documented floors, and run the README's own parameter set through a computation that resembles your real workload to see where the noise budget lands. That last check is the one TenSEAL will not do for you.

Editorial conclusion

Adopt TenSEAL if your team already reasons in SEAL terms (poly_modulus_degree, coeff_mod_bit_sizes, global_scale) and wants vector and matrix arithmetic without writing C++ bindings by hand. Do not adopt it if you need the library to choose cryptographic parameters for you, if you depend on the published Docker images, or if you are on Xcode 16 or newer and cannot switch toolchains. Before committing, verify three things on your own target platform: that a wheel exists for your architecture and libc, that your compiler and CMake versions clear the documented floors, and that the noise budget in your actual computation survives the multiplicative depth you plan to use.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. OpenMined/TenSEAL on GitHub
  4. README
  5. Releases
Community notes

Community notes