Functional encryption lets the holder of a key learn one function of encrypted data, such as an inner product, and nothing else. RustPFE implements eight published pairing-based schemes in safe Rust, six function-hiding inner-product functional encryption (IPFE) schemes and two quadratic functional encryption (QFE) schemes, generic over any arkworks pairing curve, tested on BLS12-381 and BN254, with a PyO3 Python binding.
use ark_bls12_381::Bls12_381;
use pfe::group::DlogTable;
use pfe::ipfe::opt;
use pfe::rand::rngs::OsRng;
let rng = &mut OsRng;
let msk = opt::setup::<Bls12_381, _>(3, rng)?;
let table = DlogTable::new(opt::base::<Bls12_381>(), -1000, 1000);
let sk = opt::keygen(&msk, &[1, 2, 3], rng)?;
let ct = opt::encrypt(&msk, &[4, -5, 6], rng)?;
assert_eq!(opt::decrypt(&table, &sk, &ct), Some(12));from pfe.ipfe import opt
import pfe
msk = opt.setup(3)
table = pfe.DlogTable(opt.base(), -1000, 1000)
assert opt.decrypt(table, opt.keygen(msk, [1, 2, 3]), opt.encrypt(msk, [4, -5, 6])) == 12In ordinary encryption a key decrypts everything or nothing. In functional encryption the holder of the master key
issues keys for specific functions: a key for the vector y, applied to an encryption of x, yields ⟨x, y⟩ and nothing
else about x. That is enough for weighted sums, statistics or a linear classifier over data the computing party never
sees; quadratic FE extends it to degree-2 polynomials such as xᵀFy, and a function-hiding scheme also hides y, so
neither side learns the other's vector. The inner-product schemes here are secret-key (encrypt takes the master key),
the quadratic schemes public-key, and all rely on bilinear pairings, hence pairing-friendly curves such as BLS12-381.
- Eight schemes from the literature, tested together on BLS12-381 and BN254 through one generic test suite.
- Fast and parallel. A
parallelfeature, on by default, spreads the work over all cores with results identical to the sequential build. With Ojaswi et al.'s scheme on BLS12-381, a prepared key decrypts a length-10 inner product in about 1 ms, and 16 threads sharing one key reach about 4,300 decryptions per second. - Safe, thread-safe and hardened. The crate is
#![forbid(unsafe_code)], every key and ciphertext isSend + Sync, a regression test pins a deadlock the parallel work once exposed, and decoding rejects truncated, trailing, out-of-range and off-curve input without allocating from untrusted lengths. - Python binding with PyO3: the same API as
pfe.ipfe.optand friends, bytes and pickle support, and the GIL released so Python threads decrypt in parallel. - 100% line coverage in CI, clippy with warnings as errors, and tests with and without the
parallelfeature.
Inner-product FE, where a key for y decrypts a ciphertext of x to ⟨x, y⟩:
| Scheme | Module | Function hiding | Security | Paper |
|---|---|---|---|---|
| Bishop et al. | pfe::ipfe::bjk |
weak | SXDH | ASIACRYPT 2015 |
| Tomida et al. | pfe::ipfe::tao |
full | XDLIN | ISC 2016 |
| Kim et al. | pfe::ipfe::kim |
full | generic group model | SCN 2018 |
| Lin | pfe::ipfe::lin |
full | SXDH | CRYPTO 2017 |
| Kim, Kim and Seo | pfe::ipfe::kks |
full | SXDH | TCS 2019 |
| Ojaswi et al. | pfe::ipfe::opt |
full | generic group model | CiC 2025 |
Quadratic FE, where a key for a matrix F decrypts a ciphertext of (x, y) to xᵀFy and anyone with the public key can encrypt:
| Scheme | Module | Security | Paper |
|---|---|---|---|
| Baltico et al. | pfe::qfe::bcfg |
adaptive, generic group model | CRYPTO 2017 |
| Dufour-Sans et al. | pfe::qfe::sgp |
generic group model | NeurIPS 2019 |
Add pfe = { git = "https://github.com/WeiqiNs/RustPFE" } and a curve such as ark-bls12-381 = "0.6".
- Every scheme has
setup,keygen,encrypt,prepareanddecrypt; randomized functions take the RNG, andsetup,keygenandencryptreturnErr(ShapeError)for a bad length or shape. decryptreturnsNoneoutside the searched range and accepts the key or the prepared key fromprepare(&sk), which precomputes its pairing work once. Bishop et al. and Kim et al. take the range directly; the others share oneDlogTablebuilt overbase()(Tomida et al.:msk.base()).MasterKey,PublicKey,KeyandCiphertextimplementpfe::group::Encoding: n as a 4-byte big-endian integer, then the fields in declaration order in arkworks' compressed form, with every length derived from n;decodereturns apfe::group::DecodeErrorfor malformed input.default-features = falsegives a sequential build;RAYON_NUM_THREADSbounds the parallel one.
The Python binding in python/ (Python 3.11+) covers BLS12-381 with the same names, pfe.ipfe.{bjk,tao,kim,lin,kks,opt},
pfe.qfe.{bcfg,sgp}, pfe.DlogTable and pfe.DecodeError; bad input raises ValueError, and values support
bytes(), Type.from_bytes() and pickle. It ships type stubs (py.typed), which mypy's stubtest checks against the
built extension in CI.
cargo test
cargo bench
cd python && pip install . && pytest tests
pip install mypy && python -m mypy.stubtest pfeLibPFE, GoPFE, RustPFE and CudaPFE implement the same eight inner-product and quadratic functional encryption schemes. LibPFE, GoPFE and RustPFE share test cases and benchmark inputs, and CudaPFE batches the schemes on CPUs and NVIDIA GPUs.
| Repository | Language | Pairing backend | Role |
|---|---|---|---|
| LibPFE | C++20 | LibRBP | The eight schemes as header-only templates over any LibRBP curve |
| GoPFE | Go | gnark-crypto | The eight schemes on BLS12-381, parallel across GOMAXPROCS |
| RustPFE (this repo) | Rust, Python | arkworks | The eight schemes in safe Rust over any arkworks pairing curve, with a PyO3 Python binding |
| CudaPFE | C++20, CUDA | Own BLS12-381 arithmetic on sppark fields | The eight schemes batched on a CPU or NVIDIA GPU engine |