Native Backend¶
The optional Rust extension pyfe4ai._rust_native (sources in rust/)
runs the Ring-LWE, dense-LWE and threshold hot paths, ristretto255 and the
BLS12-381 pairing group. pip install builds it when cargo is on the
PATH (in a source checkout also python tools/build_rust.py;
PYFE4AI_NO_NATIVE=1 skips it). The Python code stays the reference and
the fallback: the native code computes the same values, and the tests check
it bit for bit. The backend is chosen per process, per block or with the
PYFE4AI_BACKEND environment variable ("auto", "python" or
"native"), and the threads of one native call with PYFE4AI_THREADS:
import pyfe4ai
from pyfe4ai import backend
pyfe4ai.backend_info() # what runs in this thread, the build, threads, sampler, cache directory
backend.describe() # build and selection, for benchmark records
backend.set_backend("python") # the whole process: "auto" (default), "python", "native"
with backend.backend("native"):
... # this thread, inside the block (ImportError if not built)
backend.set_threads(4) # threads per native call (default 1)
Without the extension the schemes run their Python implementation, about 5
to 35 times slower on the lattice schemes (one thread), and the first such fallback gives
one FEWarning per process saying so (not
under an explicit "python" selection). PYFE4AI_BACKEND=native without
the extension still runs Python, with the notice; only
set_backend() and backend()
refuse "native" with ImportError. To silence the notice select
"python" or filter the warning:
import warnings
from pyfe4ai import FEWarning
warnings.filterwarnings("ignore", message="the native extension", category=FEWarning)
Backends: the native (Rust) or the Python implementation of the hot operations.
The scheme and primitive layers do not call the native extension
pyfe4ai._rust_native themselves: they call the operations of
pyfe4ai.backend.interface – NTTs and products, ring contexts and their
linear combinations, label hashes, the Ring-LWE encryption and decryption
kernels, the Gaussian samplers, the dealing and sums of the threshold key
sharings, ristretto255 and the dense-LWE label sums – on the implementation
ops() returns (ring() for the ring of (q, ring_n)). Two
implementations stand behind the interface:
pyfe4ai.backend.python_impl– the reference, in Python;pyfe4ai.backend.native_impl– the extension (Rust and PyO3, sources inrust/; built bypip installwhen a Rust toolchain is available, or in a source checkout bytools/build_rust.py), which computes the same values (same NTT root and tables, the same samplers on the same kind of random bytes, the same hash-to-Z_qreductions) and hands every input it does not handle – moduli of2^253or more, rings without a2 ring_n-th root of unity, Gaussian widths above2^40inside the encryption calls, other value types or layouts – to the reference.tests/backend/test_backend_interface.pyruns every operation through both and requires the same outputs.
The extension runs the Ring-LWE MIFE / MCFE hot paths – NTTs, Gaussian
sampling, label pads, the encryption, decryption and key-derivation loops of
the ndarray helpers – one call per tensor, with the GIL released and
optionally several threads – and of the threshold MCFE
(mcfe/ring_lwe_threshold): the Shamir sharing of the functional key, the
parsing, digests and NTT-domain products of a partial decryption and the
combination. The replicated key sharings of the three LWE threshold layers
("replicated", the default for t < s, and "replicated-seeded") are
dealt and added up natively for any odd q below 2^253, with no NTT
ring (share_replicated(),
share_for_enrolled()). It also samples the wide rounded Gaussians of
discrete_gaussian_bigint() (SIFE FullySec,
ALS16 and the flooding noise of the threshold schemes) with the reference’s
integers, and has the dense-LWE label sums, the ristretto255 group of the
*/ddh_ristretto255 schemes and the BLS12-381 pairing group
(pyfe4ai.primitives.pairing.bls12_381, which has no Python
implementation: both backends use the extension’s).
Selection, per process (set_backend), per context (backend) or with
the PYFE4AI_BACKEND environment variable:
"auto"(default)native where the extension is built and the parameters are supported, Python otherwise;
"python"always the Python implementation;
"native"like
"auto", butset_backend()andbackend()raiseImportErrorif the extension is not built (the environment variable alone does not: without the extension the Python implementation runs, with the notice below).
Without the extension. The first time ops() hands out the Python
implementation because the extension is not built – under "auto" or the
environment variable PYFE4AI_BACKEND=native, not under an explicit
"python" – the library issues one
FEWarning per process
(MISSING_EXTENSION_NOTICE, attributed to the application’s call): the
Python implementation is about 5 to 35 times slower on the lattice schemes
(one thread, security_profile="standard", benchmarks/run.py fl-compare
on an Apple M2 Max, October 2026, each scheme’s default ciphertext format:
MCFE Ring-LWE with 10^4 elements and JSON ciphertexts 341 / 157 ms per
client / aggregator against 18 / 7.5 ms natively; dense-LWE MCFE with 200
elements 541 / 548 ms against 92 / 100 ms; threshold MCFE with 10^4
elements and binary ciphertexts, s = 3, t = 2: 488 ms per client,
275 ms per partial decryption and 684 ms for the aggregator against 26, 8
and 28 ms; benchmarks/PERFORMANCE.md has the binary format of MCFE
Ring-LWE and more; the gap grows with native threads), and the BLS12-381
pairing group has no Python implementation. Build the extension
(python tools/build_rust.py in a
source checkout, or pip install with a Rust toolchain on the PATH);
or select "python" (PYFE4AI_BACKEND=python, set_backend()) or
filter the warning to silence it, e.g.
warnings.filterwarnings("ignore", "the native extension", FEWarning).
backend_info() reports what is in use.
The native path applies to moduli below 2^253 with a 2 ring_n-th root
of unity (all moduli the parameter generators derive, including the flooded
moduli of mcfe/ring_lwe_threshold), in 64-bit words below 2^63,
128-bit words below 2^125 and 256-bit words above – and to the RNS
moduli of ring_modulus="rns": q = q1 q2 (MIFE / MCFE Ring-LWE), in two
64-bit residues per coefficient, and the products of up to four primes of
the threshold MCFE, whose 256-bit ring runs one 64-bit NTT per prime –, and samples natively
Gaussians of widths up to 2^40 inside its encryption calls (wider ones
come from discrete_gaussian_bigint())
with the scheme’s sampler (its recorded gaussian_sampler, FACCT by
default, see scheme_sampler()): the
rounded Gaussian, consuming seven
random bytes per uniform as random.SystemRandom.random, or the exact
FACCT sampler (sampler="facct"), which draws getrandbits in the order
of discrete_gaussian_facct() and makes
its decisions – inside the encryption calls, and for
discrete_gaussian_facct() itself
(gaussian_facct()): the
tables come from Python (facct_table(),
with c = 1 / (2 sigma^2) correct to a 128-bit mantissa), the acceptance
test is decided in fixed point with an error below 2^-110 (about
2^-119 measured), and the tests within 2^-100 of the threshold (about
one in 2^99) are handed to the Python formula, so for a given bit source
the samples are the reference’s (the known-answer tests of
tests/sampling/test_sampling_facct.py hold natively). Building a table compares a
few of its fixed-point thresholds with the Python formula and refuses a
constant off by more than about 2^-102 there (a 53-bit one is off by
about 2^-55).
Random bytes. The extension draws them from a DRBG scoped to one call:
every tensor-level call (encrypt_tensor, precompute_tensor,
encrypt for one matrix, and shamir_share and zq_replicated_deal
for a whole key, which read stream 0 in order and draw each coefficient or
piece value as random.SystemRandom.randrange(0, q) does) reads one 32-byte seed from the operating system
(getrandom / getentropy, the source of os.urandom()) and expands
it with ChaCha20 (the rand_chacha crate), chunk c of the call reading
stream c – no two chunks, hence no two threads, share a stream. The seed,
the generator states and the sample bytes are overwritten when the call
ends; no generator state outlives a call, which limits what a fork or a
virtual-machine snapshot can duplicate to the call in progress, and the
system is asked for 32 bytes per tensor instead of about 86 KB per chunk.
ChaCha20 is a standard stream cipher, not an SP 800-90A DRBG, which matters
only for FIPS validation. The Python reference reads the operating system
for every byte (os.urandom() semantics); the distribution is the same
either way. Tests feed both implementations the same bytes through the
hooks _random_override (the native calls’ random bytes) and
_seed_override (a fixed DRBG seed) of this module.
set_threads(k) (or PYFE4AI_THREADS) lets one call use k threads
over the chunks of a tensor; the default is one, the most
MAX_THREADS (set_threads refuses more, the environment variable
is clamped to it, and a value that is not an integer raises ValueError
at import).
- pyfe4ai.backend.MAX_THREADS = 64¶
Most threads one native call may use (
set_threadsrefuses more;PYFE4AI_THREADSis clamped to it).
- pyfe4ai.backend.MISSING_EXTENSION_NOTICE = 'the native extension pyfe4ai._rust_native is not built: pyfe4ai runs its Python implementation, about 5 to 35 times slower on the lattice schemes, and has no BLS12-381 pairing group. Install pyfe4ai from source with a Rust toolchain (cargo) on the PATH to build it, as described under Installation in the documentation; PYFE4AI_BACKEND=python or a warnings filter on this FEWarning silences this notice'¶
The text of the missing-extension notice (see the module docstring).
- pyfe4ai.backend.available()[source]¶
Whether the native extension is built and importable.
- Return type:
- pyfe4ai.backend.get_backend()[source]¶
The selected backend (
"auto","python"or"native").- Return type:
- pyfe4ai.backend.set_backend(name)[source]¶
Select the backend for the process (see the module docstring).
- Raises:
ValueError – For an unknown name.
ImportError – For
"native"when the extension is not built.
- Parameters:
name (
str)- Return type:
- pyfe4ai.backend.backend(name)[source]¶
Use backend name inside a
withblock (this thread only).- Parameters:
name (
str)
- pyfe4ai.backend.active_backend()[source]¶
"native"if the native path is used where it applies, else"python".- Return type:
- pyfe4ai.backend.get_threads()[source]¶
Threads one native call may use (see
set_threads()).- Return type:
- pyfe4ai.backend.set_threads(threads)[source]¶
Number of threads one native call may use (over the chunks of a tensor), at most
MAX_THREADS.
- pyfe4ai.backend.describe()[source]¶
Backend selection and build information, for benchmark records.
- Return type:
- pyfe4ai.backend.backend_info()[source]¶
What runs the hot operations in the calling thread, and how it was chosen.
A dict of plain values (it can be written as JSON):
- Return type:
"backend""native"or"python": the implementationops()uses in this thread (active_backend();"native"means the extension’s kernels where they take the parameters, the Python reference for the inputs they do not take, see above);"selected"this thread’s selection,
"auto","python"or"native"(get_backend(): abackend()block, elseset_backend()orPYFE4AI_BACKEND);"native_available"whether the extension
pyfe4ai._rust_nativeis built and importable (available());"native_version","native_build","native_path"the version of the extension’s crate (
pyfe4ai-native), its build information (compiler, OS random source, randomness of the samplers, hardware threads) and the file it was loaded from;Nonewithout the extension;"import_error"why the extension could not be imported, else
None;"threads","max_threads"the threads one native call may use (
set_threads(),PYFE4AI_THREADS) and their maximumMAX_THREADS;"gaussian_sampler"the library-wide Gaussian sampler in this context (
get_gaussian_sampler(),PYFE4AI_GAUSSIAN_SAMPLER);"scheme_sampler"the default sampler of the schemes that record theirs in their public parameters (
PROOF_SAMPLER,"facct"), which the library-wide choice does not change;"cache_dir"the parameter cache directory a key generator without a
cache_direntry uses (cache_dir():PYFE4AI_CACHE_DIRor the per-user cache directory);"version"the version of pyfe4ai.
Also exported as
pyfe4ai.backend_info().describe()keeps the shorter record the benchmarks store.
- pyfe4ai.backend.ext()[source]¶
The extension module (
Noneif not built); for the tests of its kernels.
- pyfe4ai.backend.ops()[source]¶
The implementation of the backend operations (
interface) for this thread’s selection:NativeBackendwhereactive_backend()is"native", elsePythonBackend.- Return type:
- pyfe4ai.backend.ring(q, ring_n)[source]¶
ops().ring(q, ring_n): the ringZ_q[x] / (x^ring_n + 1)of the selected implementation (natively where the extension supports it).
The interface¶
The scheme and primitive layers call the operations of
pyfe4ai.backend.interface on pyfe4ai.backend.ops() and
pyfe4ai.backend.ring(), never the extension:
PythonBackend implements every
operation in Python (the reference), and
NativeBackend subclasses it with the
extension’s kernels, keeping the reference for every input they do not take.
tests/backend/test_backend_interface.py checks both: that no module outside
pyfe4ai.backend reaches the extension, and that each operation gives
the same outputs (for the same random bytes) in both implementations.
The operations the scheme and primitive layers take from a backend.
Two implementations stand behind this interface:
PythonBackend (the reference) and
NativeBackend (the Rust extension),
which subclasses it and keeps the reference for every input its kernels do
not take (super()): that is where “native or Python” is decided for the
given parameters. pyfe4ai.backend.ops() returns the implementation
selected for the calling thread, pyfe4ai.backend.ring() its
Ring of (q, ring_n). The scheme and primitive layers call
nothing else of the backend; tests/backend/test_backend_interface.py checks that
no module outside pyfe4ai.backend reaches the extension, and runs
every operation through both implementations.
Each operation documents what both implementations guarantee:
outputs: the same values, of the documented types, for the same inputs and, where randomness is drawn, the same random bytes;
randomness: the source each implementation draws from, how much and in which order. The reference draws from the Python sources of its callers (
pyfe4ai.primitives.sampling:_CSPRNG,_OS_RANDOM, anXofRandom); the extension from a per-call DRBG seeded with 32 bytes of the operating system, or from the test hooks_random_override/_seed_overrideofpyfe4ai.backend(random_input(),seed_kwargs()), with the same number of bytes per sample, in the same order;errors: what each implementation raises for malformed inputs;
secrets: the extension wipes its copies of secret inputs (keys, pads, pad materials, shares, noise, residues) and the temporaries derived from them before it frees them, on every path, including errors; Python objects cannot be wiped, and the reference keeps no reference to its inputs beyond the call, except the handles documented as such (the caller owns them).
Two kinds of operations:
operations proper – both implementations compute the result; the scheme calls them unconditionally;
native kernels of a different step sequence – where the Python steps of a scheme check, convert or batch differently from the native call (element by element instead of a whole layer, values read one by one with other errors, an exact fallback on unusual inputs), the scheme keeps both step sequences and asks the backend which applies (
native,threshold_native,batched_layers,zq_kernels()); the reference of such a kernel is then the scheme’s own Python steps, named in the kernel’s documentation, which the tests compare with it. Their Python ring raisesNotImplementedError(or returnsNonewhere documented).
- class pyfe4ai.backend.interface.ReferenceKey(a, pk, u, delta, ntt, a_hat, pk_hat)[source]¶
Bases:
objectThe reference’s Ring-LWE encryption key (
Ring.encrypt_rows()):aand the rowspkas mpz, the pad rowsu(orNone),Delta, theRingNTTof(q, ring_n)(Nonewithout an NTT root) andNTT(a),NTT(pk_i)with it. Built by the scheme, in the order it reads its key.
- class pyfe4ai.backend.interface.Ring(q, ring_n)[source]¶
Bases:
ABCZ_q[x] / (x^ring_n + 1)of one implementation (pyfe4ai.backend.ring()).Values cross the interface as Python ints,
gmpy2.mpzor decimal strings (inputs) and as decimal strings or mpz in[0, q)(outputs), or as the binary words ofencode_values()(ceil(bits(q) / 8)little-endian bytes per value) where binary is set. Handles (native encryption keys, precomputations, NTT transforms) belong to the ring that made them.- Parameters:
ring_n (
int)
- native = False¶
Whether the extension computes this ring’s operations.
- threshold_native = False¶
Whether the threshold MCFE kernels run natively on this ring (a native ring other than the two-prime context of the MIFE / MCFE prototype).
- abstractmethod reference()[source]¶
The Python ring of the same
(q, ring_n)(this ring if it is one).- Return type:
- abstractmethod hash_to_zq(material, count, mode='shake256-wide')[source]¶
The count values in
[0, q)ofhash_to_zq_vector()for the XOF input material (hash_to_zq_material()) and the label-hash mode, as mpz. Deterministic. The extension wipes its words.
- prepare_rows(rows)[source]¶
rows (of
ring_nvalues) in the formlincomb()reads fastest, for rows combined many times: the rows themselves (Python), packed words (native).- Raises:
KeyError, TypeError, ValueError – Natively, for rows the extension does not read (the caller then combines the rows themselves with
reference()).- Parameters:
rows (
list)
- abstractmethod lincomb(rows, coeffs)[source]¶
sum_r coeffs[r] rows[r] mod q(rows ofring_nvalues, orprepare_rows()’s) asring_ndecimal strings. rows may be an iterator: each row is read when it is reached.The reference accumulates
coeffs[r] * gmpy2.mpz(v)row by row (zip, so a longer row is cut toring_n) and raises gmpy2’sValueError/TypeErrorfor a malformed value; the extension packs each row first and raises its own.
- abstractmethod label_terms(base, materials, weights, eta, mode, binary=False)[source]¶
Per label
l:base + sum_i sum_r weights[i][r] H_r(materials[l][i]) mod q,Htheetarows ofring_nvalues ofhash_to_zq()(mode), as decimal strings or (binary) words: the MCFE key termsz'' = z + sum_i y_i H(u'_i, l)and the dMCFE pad and GDSum terms.The base (
sum_r y_r u_r) and the materials (a client’su', the pairwise keys of dMCFE) are secret: the extension wipes its copies.
- abstractmethod samples_natively(*sigmas, sampler)[source]¶
Whether the encryption kernels sample Gaussians of these widths with sampler natively (widths up to
2^40; the rounded or, where built, the FACCT sampler): the caller then builds anencryption_key().
- encryption_key(a, pk, u, delta, hash_mode=None)[source]¶
The native encryption key of
(a, pk, u, Delta)–NTT(a),NTT(pk_i), the padu(orNone) and the label-hash mode of the pads (MCFE) –, orNone: then the encryption operations run the reference with the caller’sReferenceKey.Nonefor a Python ring and for a key the extension does not read (the reference then reports it). The key holds secrets (u) and wipes them when freed.
- abstractmethod encrypt_rows(key, reference, w_rows, eta, sigmas, sampler, binary)[source]¶
BMMS21 Ring-LWE encryption of w_rows (
etarows ofring_nintegers):ct0_i = pk_i r + e_i + Delta w_i,ct1 = a r + emod q,r, eof widthsigmas[0], thee_iof widthsigmas[1]. Returns(ct0 rows, ct1)as decimal strings, or the bytes ofencode_binary_ciphertext()(binary).With
encryption_key()’s key (andetarows ofring_nvalues): one native call, which samplesr, the noise rows andein that order fromgauss_bytes(ring_n) (2 + eta)bytes of its DRBG (or the test bytes), with the rounded sampler on 7 bytes per uniform or the FACCT tables ofnoise_kwargs(). Else the reference:reference()(a callable returning theReferenceKey), anddiscrete_gaussian_matrix()with sampler forr, theetanoise rows ande, in that order;ris transformed once and multiplied withNTT(pk_i)andNTT(a)(the products ofpoly_mulwith the same samples).
- abstractmethod encrypt_tensor(key, reference, ints, materials, mode, sigmas, sampler, binary)[source]¶
MCFE Ring-LWE encryption of a layer (
eta = 1): chunkjofring_nvalues of theint64array ints encrypted asencrypt_rows()the rowx_j + u + H(materials[j]) mod q(x_jzero-extended;H:hash_to_zq()with mode). Returns[(ct0 rows, ct1, k)](decimal strings) or[(bytes, k)](binary), one per material. The reference encrypts chunk by chunk (draws asencrypt_rows(), chunk after chunk); natively one call (chunkcreads DRBG streamc; test bytes:3 gauss_bytes(ring_n)per chunk, in chunk order). The pad and the rows are secret: the extension wipes them.
- abstractmethod precompute_tensor(key, reference, materials, mode, sigmas, sampler)[source]¶
The offline phase of
encrypt_tensor()forlen(materials)chunks (x = 0):(native, rows)– the native handle (NativePrecomputation,(key, capsule); the capsule marks every chunk used and wipes it when it is used) andNone, orNoneand the reference’s list of(pk r + e_0 + Delta (u + H), a r + e)per chunk. Draws asencrypt_tensor(). Secret and one-time (OneTimePrecomputation).
- abstractmethod encrypt_precomputed(native, rows, first, ints, delta, binary)[source]¶
The online phase: chunks
first, first + 1, ...of the handles ofprecompute_tensor()plusDelta xfor theint64values ints, asencrypt_tensor()returns them. The native handle runs its kernel whatever the selection (it marks and wipes each chunk; a chunk used twice raisesValueError); the reference drops each chunk from rows as it uses it.
- decryption_key_hat(poly)[source]¶
The transform of a functional-key polynomial that
decrypt_tensor()takes (NTTin Montgomery form), orNonewhen the extension does not read poly (and for a Python ring).
- decrypt_tensor(chunks, zs, ks, hats, ys, delta, bound)[source]¶
Dec2(sum_i (sum_r y_{i,r} ct0_{i,r} - ct1_i sk_i) - Delta z)of every chunk of a layer (MIFE / MCFE Ring-LWE) in one native call:(values, mask), the bytes of theint64values and of auint8mask of those that do not decode (Noneif all do); orNonewhen the extension does not decrypt them (a Python ring, layouts or values it does not read, values beyond anint64). The reference is the scheme’s own loop (MIFERingLWE._decrypt_with_pad_term).
- ntt_hat(poly)[source]¶
NTT(poly)of a key-share polynomial (values, or binary words) in the form the threshold kernels take.- Raises:
TypeError, ValueError – For values the extension does not read.
Shamir shares of every value (ints or decimal strings, or the words of a bytes object): per point, its share values in order, as decimal strings or (binary) one bytes object of words. Randomness: the
threshold - 1coefficients of each value, from stream 0 of the per-call DRBG (or the test bytes), drawn in the order and with the rejection sampling ofrandom.SystemRandom.randrange(0, q). The reference:share_polys()’sshare_secret()per value.- Raises:
FEKeyError – For malformed key material.
RuntimeError – If the test bytes run out.
- Parameters:
- Return type:
- floods_natively(width)[source]¶
Whether the partial-decryption kernels draw the uniform flooding of width bits themselves (
flood=(width, material)).
- threshold_inputs(keys, ks, rows, eta)[source]¶
JSON ciphertexts of a partial decryption read natively:
(packed, ct_text, key_text)(the texts ofciphertexts_digest()), orNonewhen the reference reads them (read_json_ciphertexts()).
- threshold_partial(packed, eta, ys, sk_hats, z_share, delta, multiplier, noise=None, flood=None)[source]¶
(w, c_y)of a partial decryption of the ciphertexts ofthreshold_inputs()as decimal strings:multiplier (Delta z + sum_i ct1_i sk_i) + noiseandsum_i sum_r y_{i,r} ct0_{i,r}. noise is the flooding polynomial, or flood(bits, material)draws it natively (the values ofuniform_flooding_poly()). The reference: the Python steps ofshare_decrypt(). The key share, its transforms and the noise are secret: wiped.
- threshold_partial_binary(cts, eta, ys, sk_hats, z_share, delta, multiplier, noise=None, flood=None)[source]¶
threshold_partial()of format-2 ciphertexts (cts: their bytes), as words:(w, c_y, k).
- threshold_combine(c_y, ws, delta, bound, k)[source]¶
decode(c_y - sum w)of JSON partial decryptions: the first k values (Nonefor those beyond the bound), orNonefor values or layouts the reference reads (json_partial_differenceanddecode_scaled).
- threshold_combine_binary(c_y, ws, delta, bound, k)[source]¶
threshold_combine()of binary partial decryptions (kwords each), orNonefor a value beyond anint64.- Raises:
ValueError – For a malformed partial decryption.
- Parameters:
- class pyfe4ai.backend.interface.Backend[source]¶
Bases:
ABCThe operations of one implementation (
pyfe4ai.backend.ops()).- name = 'python'¶
"python"or"native".
- batched_layers = False¶
Trueif whole tensor layers go through one call (the dense-LWE label terms oflabel_dots(), the ristretto255 MCFE layers ofr255_mcfe_encrypt()andr255_mcfe_decrypt()). The scheme then runs its batched steps, with the checks of a whole layer, and its element-by-element steps otherwise.
- abstractmethod ring(q, ring_n)[source]¶
The ring
Z_q[x] / (x^ring_n + 1)(the same object for the same arguments).
- abstractmethod ntt(a, q, root, inverse=False)[source]¶
The NTT (or its inverse) of
ring_lwe_utilsof a with root, as mpz in[0, q): natively where the extension has a context for(q, len(a))with this root.
- abstractmethod poly_mul(a, b, q)[source]¶
poly_mul()(with its warning whenqhas no NTT root).
- abstractmethod gaussian_bigint(sigma, count, rng, prec)[source]¶
The rounded samples of
discrete_gaussian_bigint()(count > 0): twogetrandbits(prec)per sample, from rng (None: the module’s_CSPRNG; natively its per-call DRBG, streamcfor samples256 c .., or anXofRandom’s stream, whose counter advances as in Python).
- abstractmethod gaussian_facct(sigma, count, rng)[source]¶
The samples of
discrete_gaussian_facct()(count > 0), drawinggetrandbitsin its order from rng (None: the module’s_OS_RANDOM; natively the per-call DRBG).
- zq_kernels(q)[source]¶
Whether the extension does the
Z_qarithmetic of the replicated sharings for q (the dealing and sums ofsharing).- Return type:
- replicated_deal(q, values, n_random, binary)[source]¶
The pieces of a replicated sharing of the flat values (ints or decimal strings, or the words of a bytes object), natively: the correction
values - sumfirst, thenn_randomuniform pieces, as lists of decimal strings or (binary) bytes;Nonewhen the reference reads the values instead (the Python steps ofshare_replicated(), and always on the Python backend).Randomness: every random value as
random.SystemRandom.randrange(0, q)draws it, in the order of the Python steps, from stream 0 of the per-call DRBG (or the test bytes). The values, the pieces, their digits and the caller’s bytes are wiped.- Raises:
FEKeyError – For binary values that are not whole words below q.
RuntimeError – If the test bytes run out.
- Parameters:
- abstractmethod zq_sum(q, count, rows, materials, subtract=False, binary=False, reference=True)[source]¶
sum(rows) +- sum_m H(m)mod q,H:countvalues ofhash_to_zq()("shake256-wide"), as decimal strings; with binary the sum of rows of words, as words. The reference converts withgmpy2.mpzrow by row and raisesFESchemeErrorfor a row of another length than count. Without reference:Noneinstead of the reference for what the extension does not take (the caller then has reference steps of its own). The materials hold seeds: the extension wipes its copies.
- abstractmethod r255_add(a, b)[source]¶
a + b: group elements as 32-byte encodings (native) or points (Python); both implementations read both forms (ec_group).
- abstractmethod r255_mul(point, k)[source]¶
k point(k an integer, reduced mod the group order natively).- Parameters:
k (
int)
- abstractmethod r255_decode(data)[source]¶
The element of a canonical encoding.
- Raises:
ValueError – If data is not one.
- Parameters:
data (
bytes)
- abstractmethod r255_dlog(point, bound)[source]¶
The
xwithx G = pointand|x| <= bound, orNone.- Parameters:
bound (
int)
- abstractmethod dense_key(key_row, q)[source]¶
A dense-LWE key row in the form
label_dots()reads (natively packed words with the row; secret, cached by the caller per key and pickled with it, so built of plain values only).- Parameters:
key_row (
list)
- abstractmethod label_dots(materials, key, q, dim, mode)[source]¶
<key_row, H(m)> mod qfor every materialm(key: thedense_key()ofkey_row;H: dim values ofhash_to_zq_vector()with mode), as mpz: the dense-LWE MCFE label terms. Natively the products are summed before the hash blocks are reduced (congruent mod q); a key row of another length than dim goes to the reference.
- abstractmethod r255_mcfe_encrypt(msgs, dst, values, s0, s1)[source]¶
MCFE ristretto255 ciphertexts
x G + s0 H0(l) + s1 H1(l)of the valuesxunder the labels of msgs (H_k(l):r255_hash()ofbytes([k]) + msg), as hex strings.
- abstractmethod r255_mcfe_decrypt(cts, ys, d0, d1, msgs, dst, bound)[source]¶
The decryptions
dlog(sum_i y_i ct_i - d0 H0(l) - d1 H1(l))of a layer (cts[i][e]: clienti’s hex ciphertext of elemente):(values, mask)as indecrypt_tensor().
- abstractmethod bls12_381()[source]¶
The BLS12-381 kernels (
Bls12381Kernels), orNonewhen the extension is not built: the pairing group has no Python implementation, so both backends return the extension’s.
Python implementation¶
The Python implementation of the backend operations: the reference.
PythonBackend and PythonRing implement every operation of
pyfe4ai.backend.interface in Python; the native implementation
(pyfe4ai.backend.native_impl) subclasses them and must give the same
outputs. Where the reference algorithm is a primitive of its own – the NTT
and polynomial product of ring_lwe_utils,
the samplers of sampling, the hashes of
hashing, the ristretto255 group of
ristretto255 – the operation calls it;
the kernels of the schemes (encryption and decryption of Ring-LWE tensors,
label terms, the threshold dealing and partial decryptions) are written out
here, with the Python sources of randomness of their callers.
- class pyfe4ai.backend.python_impl.PythonRing(q, ring_n)[source]¶
Bases:
RingZ_q[x] / (x^ring_n + 1)in Python.- Parameters:
ring_n (
int)
- native = False¶
Whether the extension computes this ring’s operations.
- hash_to_zq(material, count, mode='shake256-wide')[source]¶
The count values in
[0, q)ofhash_to_zq_vector()for the XOF input material (hash_to_zq_material()) and the label-hash mode, as mpz. Deterministic. The extension wipes its words.
- lincomb(rows, coeffs)[source]¶
sum_r coeffs[r] rows[r] mod q(rows ofring_nvalues, orprepare_rows()’s) asring_ndecimal strings. rows may be an iterator: each row is read when it is reached.The reference accumulates
coeffs[r] * gmpy2.mpz(v)row by row (zip, so a longer row is cut toring_n) and raises gmpy2’sValueError/TypeErrorfor a malformed value; the extension packs each row first and raises its own.
- label_terms(base, materials, weights, eta, mode, binary=False)[source]¶
Per label
l:base + sum_i sum_r weights[i][r] H_r(materials[l][i]) mod q,Htheetarows ofring_nvalues ofhash_to_zq()(mode), as decimal strings or (binary) words: the MCFE key termsz'' = z + sum_i y_i H(u'_i, l)and the dMCFE pad and GDSum terms.The base (
sum_r y_r u_r) and the materials (a client’su', the pairwise keys of dMCFE) are secret: the extension wipes its copies.
- samples_natively(*sigmas, sampler)[source]¶
Whether the encryption kernels sample Gaussians of these widths with sampler natively (widths up to
2^40; the rounded or, where built, the FACCT sampler): the caller then builds anencryption_key().
- encrypt_rows(key, reference, w_rows, eta, sigmas, sampler, binary)[source]¶
BMMS21 Ring-LWE encryption of w_rows (
etarows ofring_nintegers):ct0_i = pk_i r + e_i + Delta w_i,ct1 = a r + emod q,r, eof widthsigmas[0], thee_iof widthsigmas[1]. Returns(ct0 rows, ct1)as decimal strings, or the bytes ofencode_binary_ciphertext()(binary).With
encryption_key()’s key (andetarows ofring_nvalues): one native call, which samplesr, the noise rows andein that order fromgauss_bytes(ring_n) (2 + eta)bytes of its DRBG (or the test bytes), with the rounded sampler on 7 bytes per uniform or the FACCT tables ofnoise_kwargs(). Else the reference:reference()(a callable returning theReferenceKey), anddiscrete_gaussian_matrix()with sampler forr, theetanoise rows ande, in that order;ris transformed once and multiplied withNTT(pk_i)andNTT(a)(the products ofpoly_mulwith the same samples).
- encrypt_tensor(key, reference, ints, materials, mode, sigmas, sampler, binary)[source]¶
MCFE Ring-LWE encryption of a layer (
eta = 1): chunkjofring_nvalues of theint64array ints encrypted asencrypt_rows()the rowx_j + u + H(materials[j]) mod q(x_jzero-extended;H:hash_to_zq()with mode). Returns[(ct0 rows, ct1, k)](decimal strings) or[(bytes, k)](binary), one per material. The reference encrypts chunk by chunk (draws asencrypt_rows(), chunk after chunk); natively one call (chunkcreads DRBG streamc; test bytes:3 gauss_bytes(ring_n)per chunk, in chunk order). The pad and the rows are secret: the extension wipes them.
- precompute_tensor(key, reference, materials, mode, sigmas, sampler)[source]¶
The offline phase of
encrypt_tensor()forlen(materials)chunks (x = 0):(native, rows)– the native handle (NativePrecomputation,(key, capsule); the capsule marks every chunk used and wipes it when it is used) andNone, orNoneand the reference’s list of(pk r + e_0 + Delta (u + H), a r + e)per chunk. Draws asencrypt_tensor(). Secret and one-time (OneTimePrecomputation).
- encrypt_precomputed(native, rows, first, ints, delta, binary)[source]¶
The online phase: chunks
first, first + 1, ...of the handles ofprecompute_tensor()plusDelta xfor theint64values ints, asencrypt_tensor()returns them. The native handle runs its kernel whatever the selection (it marks and wipes each chunk; a chunk used twice raisesValueError); the reference drops each chunk from rows as it uses it.
- class pyfe4ai.backend.python_impl.PythonBackend[source]¶
Bases:
BackendThe backend operations in Python (the reference).
- name = 'python'¶
"python"or"native".
- batched_layers = False¶
Trueif whole tensor layers go through one call (the dense-LWE label terms oflabel_dots(), the ristretto255 MCFE layers ofr255_mcfe_encrypt()andr255_mcfe_decrypt()). The scheme then runs its batched steps, with the checks of a whole layer, and its element-by-element steps otherwise.
- ring(q, ring_n)[source]¶
The ring
Z_q[x] / (x^ring_n + 1)(the same object for the same arguments).- Parameters:
ring_n (
int)- Return type:
- ntt(a, q, root, inverse=False)[source]¶
The NTT (or its inverse) of
ring_lwe_utilsof a with root, as mpz in[0, q): natively where the extension has a context for(q, len(a))with this root.
- poly_mul(a, b, q)[source]¶
poly_mul()(with its warning whenqhas no NTT root).
- gaussian_bigint(sigma, count, rng, prec)[source]¶
The rounded samples of
discrete_gaussian_bigint()(count > 0): twogetrandbits(prec)per sample, from rng (None: the module’s_CSPRNG; natively its per-call DRBG, streamcfor samples256 c .., or anXofRandom’s stream, whose counter advances as in Python).
- gaussian_facct(sigma, count, rng)[source]¶
The samples of
discrete_gaussian_facct()(count > 0), drawinggetrandbitsin its order from rng (None: the module’s_OS_RANDOM; natively the per-call DRBG).
- zq_kernels(q)[source]¶
Whether the extension does the
Z_qarithmetic of the replicated sharings for q (the dealing and sums ofsharing).- Return type:
- replicated_deal(q, values, n_random, binary)[source]¶
The pieces of a replicated sharing of the flat values (ints or decimal strings, or the words of a bytes object), natively: the correction
values - sumfirst, thenn_randomuniform pieces, as lists of decimal strings or (binary) bytes;Nonewhen the reference reads the values instead (the Python steps ofshare_replicated(), and always on the Python backend).Randomness: every random value as
random.SystemRandom.randrange(0, q)draws it, in the order of the Python steps, from stream 0 of the per-call DRBG (or the test bytes). The values, the pieces, their digits and the caller’s bytes are wiped.- Raises:
FEKeyError – For binary values that are not whole words below q.
RuntimeError – If the test bytes run out.
- Parameters:
- zq_sum(q, count, rows, materials, subtract=False, binary=False, reference=True)[source]¶
sum(rows) +- sum_m H(m)mod q,H:countvalues ofhash_to_zq()("shake256-wide"), as decimal strings; with binary the sum of rows of words, as words. The reference converts withgmpy2.mpzrow by row and raisesFESchemeErrorfor a row of another length than count. Without reference:Noneinstead of the reference for what the extension does not take (the caller then has reference steps of its own). The materials hold seeds: the extension wipes its copies.
- r255_add(a, b)[source]¶
a + b: group elements as 32-byte encodings (native) or points (Python); both implementations read both forms (ec_group).
- r255_mul(point, k)[source]¶
k point(k an integer, reduced mod the group order natively).- Parameters:
k (
int)
- r255_decode(data)[source]¶
The element of a canonical encoding.
- Raises:
ValueError – If data is not one.
- Parameters:
data (
bytes)
- r255_dlog(point, bound)[source]¶
The
xwithx G = pointand|x| <= bound, orNone.- Parameters:
bound (
int)
- dense_key(key_row, q)[source]¶
A dense-LWE key row in the form
label_dots()reads (natively packed words with the row; secret, cached by the caller per key and pickled with it, so built of plain values only).- Parameters:
key_row (
list)
- label_dots(materials, key, q, dim, mode)[source]¶
<key_row, H(m)> mod qfor every materialm(key: thedense_key()ofkey_row;H: dim values ofhash_to_zq_vector()with mode), as mpz: the dense-LWE MCFE label terms. Natively the products are summed before the hash blocks are reduced (congruent mod q); a key row of another length than dim goes to the reference.
- r255_mcfe_encrypt(msgs, dst, values, s0, s1)[source]¶
MCFE ristretto255 ciphertexts
x G + s0 H0(l) + s1 H1(l)of the valuesxunder the labels of msgs (H_k(l):r255_hash()ofbytes([k]) + msg), as hex strings.
- r255_mcfe_decrypt(cts, ys, d0, d1, msgs, dst, bound)[source]¶
The decryptions
dlog(sum_i y_i ct_i - d0 H0(l) - d1 H1(l))of a layer (cts[i][e]: clienti’s hex ciphertext of elemente):(values, mask)as indecrypt_tensor().
- bls12_381()[source]¶
The BLS12-381 kernels (
Bls12381Kernels), orNonewhen the extension is not built: the pairing group has no Python implementation, so both backends return the extension’s.
Native implementation¶
The native implementation of the backend operations: pyfe4ai._rust_native.
NativeBackend and RingContext (its Ring)
subclass the reference (pyfe4ai.backend.python_impl) and run the
extension’s kernels where they apply; every input a kernel does not handle
goes to the reference (super()), which is what decides “native or
Python” for the given parameters. This is the only module that calls the
extension (the extension object is pyfe4ai.backend._ext, read at
every call so that tests can wrap it), and the only one that assembles a
native call’s randomness (random_input(), seed_kwargs()), noise
tables (noise_kwargs()) and threads (pyfe4ai.backend.get_threads()).
The applicability rules (see pyfe4ai.backend): moduli below
MAX_NATIVE_Q with a 2 ring_n-th root of unity (and the RNS moduli
of register_rns_modulus()),
Gaussian widths up to MAX_NATIVE_SIGMA inside the encryption calls
with the rounded or the FACCT sampler (sampling_ok()), the rounded
wide Gaussians of NativeBackend.gaussian_bigint() and the FACCT
samples of NativeBackend.gaussian_facct() within their bounds.
- pyfe4ai.backend.native_impl.MAX_NATIVE_SIGMA = 1099511627776¶
Largest Gaussian width sampled natively (that of the double-precision sampler of
discrete_gaussian_matrix()).
- pyfe4ai.backend.native_impl.MAX_NATIVE_Q = 14474011154664524427946373126085988481658748083205070504932198000989141204992¶
Moduli must be below this bound (four 64-bit words with 3 bits of headroom).
- pyfe4ai.backend.native_impl.random_input(nbytes)[source]¶
Random bytes for a native call:
None(the extension reads the OS source itself) unless a test installedpyfe4ai.backend._random_override.- Parameters:
nbytes (
int)
- pyfe4ai.backend.native_impl.seed_kwargs()[source]¶
Keyword arguments of the tensor calls: a test-injected DRBG seed (
pyfe4ai.backend._seed_override), if any.- Return type:
- pyfe4ai.backend.native_impl.gauss_bytes(count)[source]¶
Random bytes the native sampler consumes for count samples.
- pyfe4ai.backend.native_impl.facct_table(sigma)[source]¶
Native tables of the exact sampler for sigma (see
_facct_table).
- pyfe4ai.backend.native_impl.MAX_NATIVE_FACCT_K = 1125899906842624¶
Largest FACCT double-sampling factor k of the native kernel (samples in an i64) and, beyond it, of the wide kernel (k, y and samples in 64-bit words).
- pyfe4ai.backend.native_impl.noise_kwargs(*sigmas, sampler)[source]¶
Keyword arguments of the native encryption calls for the scheme’s sampler: the FACCT tables of the widths, or nothing (rounded).
- pyfe4ai.backend.native_impl.sampling_ok(*sigmas, sampler)[source]¶
Whether Gaussians of these widths may be sampled natively with the scheme’s sampler: rounded, or FACCT when the extension has it.
- pyfe4ai.backend.native_impl.MAX_NATIVE_BIGINT_BITS = 448¶
Widths below
2^MAX_NATIVE_BIGINT_BITS(and uniforms of at mostMAX_NATIVE_BIGINT_PRECbits) are sampled natively byNativeBackend.gaussian_bigint().
- pyfe4ai.backend.native_impl.MAX_NATIVE_BIGINT_EXP = 1048576¶
Largest exponent (in absolute value) of
sig = mantissa 2^exponentand most samples per call sampled natively (more: the Python code).
- class pyfe4ai.backend.native_impl.RingContext(q, ring_n, root, factors=None)[source]¶
Bases:
PythonRingNative NTT context of
Z_q[x] / (x^n + 1)(root and tables ofpoly_mul()): the nativeRing.With factors (an RNS modulus, see
register_rns_modulus()) the native code runs one 64-bit NTT per factor, with the rootroot mod q_i, and converts to and from[0, q)(Garner) where values cross the boundary. Two factors withq < 2^124(the MIFE / MCFE prototype) get the context of their own kernels (legacy_rns; packed values are 128-bit words); other RNS moduli (up to four factors,q < 2^253: the threshold MCFE) a ring context whose kernels are those of a prime ring, with the values of a ring mod q.- Parameters:
ring_n (
int)
- native = True¶
Whether the extension computes this ring’s operations.
- ntt_mont(values)[source]¶
NTT of values in the form the native encryption / decryption keys use.
- Return type:
- hash_to_zq(material, count, mode='shake256-wide')[source]¶
The count values in
[0, q)ofhash_to_zq_vector()for the XOF input material (hash_to_zq_material()) and the label-hash mode, as mpz. Deterministic. The extension wipes its words.
- prepare_rows(rows)[source]¶
The rows packed (
PackedRow), eachring_nwords.- Raises:
KeyError, TypeError, ValueError – For rows the extension does not read (the caller then combines the rows themselves with the reference).
- Parameters:
rows (
list)
- lincomb(rows, coeffs)[source]¶
sum_r coeffs[r] rows[r] mod q(rows ofring_nvalues, orprepare_rows()’s) asring_ndecimal strings. rows may be an iterator: each row is read when it is reached.The reference accumulates
coeffs[r] * gmpy2.mpz(v)row by row (zip, so a longer row is cut toring_n) and raises gmpy2’sValueError/TypeErrorfor a malformed value; the extension packs each row first and raises its own.
- label_terms(base, materials, weights, eta, mode, binary=False)[source]¶
Per label
l:base + sum_i sum_r weights[i][r] H_r(materials[l][i]) mod q,Htheetarows ofring_nvalues ofhash_to_zq()(mode), as decimal strings or (binary) words: the MCFE key termsz'' = z + sum_i y_i H(u'_i, l)and the dMCFE pad and GDSum terms.The base (
sum_r y_r u_r) and the materials (a client’su', the pairwise keys of dMCFE) are secret: the extension wipes its copies.
- samples_natively(*sigmas, sampler)[source]¶
Whether the encryption kernels sample Gaussians of these widths with sampler natively (widths up to
2^40; the rounded or, where built, the FACCT sampler): the caller then builds anencryption_key().
- encryption_key(a, pk, u, delta, hash_mode=None)[source]¶
The native encryption key of
(a, pk, u, Delta)–NTT(a),NTT(pk_i), the padu(orNone) and the label-hash mode of the pads (MCFE) –, orNone: then the encryption operations run the reference with the caller’sReferenceKey.Nonefor a Python ring and for a key the extension does not read (the reference then reports it). The key holds secrets (u) and wipes them when freed.
- encrypt_rows(key, reference, w_rows, eta, sigmas, sampler, binary)[source]¶
BMMS21 Ring-LWE encryption of w_rows (
etarows ofring_nintegers):ct0_i = pk_i r + e_i + Delta w_i,ct1 = a r + emod q,r, eof widthsigmas[0], thee_iof widthsigmas[1]. Returns(ct0 rows, ct1)as decimal strings, or the bytes ofencode_binary_ciphertext()(binary).With
encryption_key()’s key (andetarows ofring_nvalues): one native call, which samplesr, the noise rows andein that order fromgauss_bytes(ring_n) (2 + eta)bytes of its DRBG (or the test bytes), with the rounded sampler on 7 bytes per uniform or the FACCT tables ofnoise_kwargs(). Else the reference:reference()(a callable returning theReferenceKey), anddiscrete_gaussian_matrix()with sampler forr, theetanoise rows ande, in that order;ris transformed once and multiplied withNTT(pk_i)andNTT(a)(the products ofpoly_mulwith the same samples).
- encrypt_tensor(key, reference, ints, materials, mode, sigmas, sampler, binary)[source]¶
MCFE Ring-LWE encryption of a layer (
eta = 1): chunkjofring_nvalues of theint64array ints encrypted asencrypt_rows()the rowx_j + u + H(materials[j]) mod q(x_jzero-extended;H:hash_to_zq()with mode). Returns[(ct0 rows, ct1, k)](decimal strings) or[(bytes, k)](binary), one per material. The reference encrypts chunk by chunk (draws asencrypt_rows(), chunk after chunk); natively one call (chunkcreads DRBG streamc; test bytes:3 gauss_bytes(ring_n)per chunk, in chunk order). The pad and the rows are secret: the extension wipes them.
- precompute_tensor(key, reference, materials, mode, sigmas, sampler)[source]¶
The offline phase of
encrypt_tensor()forlen(materials)chunks (x = 0):(native, rows)– the native handle (NativePrecomputation,(key, capsule); the capsule marks every chunk used and wipes it when it is used) andNone, orNoneand the reference’s list of(pk r + e_0 + Delta (u + H), a r + e)per chunk. Draws asencrypt_tensor(). Secret and one-time (OneTimePrecomputation).
- decryption_key_hat(poly)[source]¶
The transform of a functional-key polynomial that
decrypt_tensor()takes (NTTin Montgomery form), orNonewhen the extension does not read poly (and for a Python ring).
- decrypt_tensor(chunks, zs, ks, hats, ys, delta, bound)[source]¶
Dec2(sum_i (sum_r y_{i,r} ct0_{i,r} - ct1_i sk_i) - Delta z)of every chunk of a layer (MIFE / MCFE Ring-LWE) in one native call:(values, mask), the bytes of theint64values and of auint8mask of those that do not decode (Noneif all do); orNonewhen the extension does not decrypt them (a Python ring, layouts or values it does not read, values beyond anint64). The reference is the scheme’s own loop (MIFERingLWE._decrypt_with_pad_term).
- property threshold_native: bool¶
bool(x) -> bool
Returns True when the argument x is true, False otherwise. The builtins True and False are the only two instances of the class bool. The class bool is a subclass of the class int, and cannot be subclassed.
- ntt_hat(poly)[source]¶
NTT(poly)in the form the native threshold kernels take (ntt_mont()): values, or the binary words of a bytes object.- Raises:
TypeError, ValueError – For values the extension does not read.
Shamir shares of every value (ints or decimal strings, or the words of a bytes object): per point, its share values in order, as decimal strings or (binary) one bytes object of words. Randomness: the
threshold - 1coefficients of each value, from stream 0 of the per-call DRBG (or the test bytes), drawn in the order and with the rejection sampling ofrandom.SystemRandom.randrange(0, q). The reference:share_polys()’sshare_secret()per value.- Raises:
FEKeyError – For malformed key material.
RuntimeError – If the test bytes run out.
- Parameters:
- Return type:
- floods_natively(width)[source]¶
Whether the partial-decryption kernels draw the uniform flooding of width bits themselves (
flood; the same values asuniform_flooding_poly()).
- threshold_inputs(keys, ks, rows, eta)[source]¶
JSON ciphertexts of a partial decryption read natively:
(packed, ct_text, key_text)(the texts ofciphertexts_digest()), orNonewhen the reference reads them (read_json_ciphertexts()).
- threshold_partial(packed, eta, ys, sk_hats, z_share, delta, multiplier, noise=None, flood=None)[source]¶
(w, c_y)of a partial decryption of the ciphertexts ofthreshold_inputs()as decimal strings:multiplier (Delta z + sum_i ct1_i sk_i) + noiseandsum_i sum_r y_{i,r} ct0_{i,r}. noise is the flooding polynomial, or flood(bits, material)draws it natively (the values ofuniform_flooding_poly()). The reference: the Python steps ofshare_decrypt(). The key share, its transforms and the noise are secret: wiped.
- threshold_partial_binary(cts, eta, ys, sk_hats, z_share, delta, multiplier, noise=None, flood=None)[source]¶
threshold_partial()of format-2 ciphertexts (cts: their bytes), as words:(w, c_y, k).
- threshold_combine(c_y, ws, delta, bound, k)[source]¶
decode(c_y - sum w)of JSON partial decryptions: the first k values (Nonefor those beyond the bound), orNonefor values or layouts the reference reads (json_partial_differenceanddecode_scaled).
- threshold_combine_binary(c_y, ws, delta, bound, k)[source]¶
threshold_combine()of binary partial decryptions (kwords each), orNonefor a value beyond anint64.- Raises:
ValueError – For a malformed partial decryption.
- Parameters:
- class pyfe4ai.backend.native_impl.NativePrecomputation(key, capsule)[source]¶
Bases:
tupleThe native offline phase of
RingContext.precompute_tensor():(key, capsule); the capsule marks every chunk used and wipes it when used (precomputed_info).
- class pyfe4ai.backend.native_impl.PackedRow[source]¶
Bases:
bytesA row packed by
RingContext.prepare_rows()(the ring’s words).
- pyfe4ai.backend.native_impl.hash_kwargs(mode)[source]¶
The
hash_modekeyword of the native hash and pad kernels (none for the kernels’ own"shake256-wide").
- pyfe4ai.backend.native_impl.ring_context(q, ring_n)[source]¶
The native context for
(q, ring_n), orNonewhen the Python path applies (backend"python", extension not built,q >= 2^253or no2 ring_n-th root of unity).- Parameters:
ring_n (
int)- Return type:
- class pyfe4ai.backend.native_impl.Bls12381Kernels(ext)[source]¶
Bases:
objectThe BLS12-381 functions of the extension (arkworks):
order(),random(kind),init(kind[, value]),generator(kind),deserialize(kind, raw),deserialize_many(items),hash(kind, msg, dst),pair,multi_pair,msm,pow_many,gt_dlogand the element typeElement. Like the extension module, not picklable.
- pyfe4ai.backend.native_impl.bls12_381_kernels()[source]¶
Bls12381Kernelsof the extension, orNoneif it is not built (or built without BLS12-381).
- class pyfe4ai.backend.native_impl.NativeBackend[source]¶
Bases:
PythonBackendThe backend operations on the extension, with the reference (
PythonBackend) for what it does not handle.- name = 'native'¶
"python"or"native".
- batched_layers = True¶
Trueif whole tensor layers go through one call (the dense-LWE label terms oflabel_dots(), the ristretto255 MCFE layers ofr255_mcfe_encrypt()andr255_mcfe_decrypt()). The scheme then runs its batched steps, with the checks of a whole layer, and its element-by-element steps otherwise.
- ring(q, ring_n)[source]¶
The ring
Z_q[x] / (x^ring_n + 1)(the same object for the same arguments).- Parameters:
ring_n (
int)
- ntt(a, q, root, inverse=False)[source]¶
The NTT (or its inverse) of
ring_lwe_utilsof a with root, as mpz in[0, q): natively where the extension has a context for(q, len(a))with this root.
- poly_mul(a, b, q)[source]¶
poly_mul()(with its warning whenqhas no NTT root).
- gaussian_facct(sigma, count, rng)[source]¶
discrete_gaussian_facct()natively where it applies.The extension draws
getrandbitsin the reference’s order and makes its decisions (fixed-point acceptance tests with the Python threshold for the undecided ones; see_facct_table), so for a given bit source the samples are the reference’s. Bits: from rng if it is anXofRandom– sampled with the GIL and the XofRandom claim lock held, since the number of calls depends on the samples, and its counter then advanced by the calls used – else, forrng=Nonewith the module’s OS source, from the per-call DRBG (streamcfor samples256 cto256 c + 255, onget_threads()threads; or_random_overridein the tests). Widths withk <= 2^50(sigmabelow about2^49.8) use the kernel ofrust/core/src/facct.rs, wider ones up tok < 2^448(sigmabelow about2^447.7) that offacct_wide.rs(the same decisions, with the error argument in its header); the MPFR context does not matter (the reference sets its own). Other inputs: the reference.
- gaussian_bigint(sigma, count, rng, prec)[source]¶
discrete_gaussian_bigint()with the rounded sampler, natively where it applies.The extension reads
b1, b2 = getrandbits(prec)per sample as the reference does – from rng if it is anXofRandom(whose counter advances as in Python), else, forrng=None, from its per-call DRBG (streamcfor samples256 cto256 c + 255; or from_random_overridein the tests) – and computesX = sig sqrt(-2 ln u1) cos(2 pi u2)in fixed point with an error below2^(s + 10 - 64 n)(sig < 2^s,n = ceil((s + 64) / 64)fraction words). The reference’s MPFR value is within2^-118ofX; wheneverXis farther than2^(max(s + 16 - 64 n, -100) + 1)from every half-integer both round to the same integer. The other samples (at most about one in2^45, whensis just below a multiple of 64; about one in2^62at thes = 47of SIFE FullySec) are computed here with the reference formula from the sameb1, b2. So for a given bit source the samples are the reference’s. Applies to widths below2^448(exponents within2^20) and up to2^24samples per call, under an MPFR context like the default one (round to nearest, no traps, no subnormals, exponent range at least2^20); any other rng (a replacedsampling._CSPRNGincluded) keeps the Python code. TheXofRandomcalls of the samples are claimed (its counter advanced, asgetrandbitsclaims one) before the extension runs, so a thread drawing from the sameXofRandommeanwhile gets other calls.
- zq_kernels(q)[source]¶
Whether the extension does the
Z_qarithmetic of the replicated sharings for q: built withzq_sum,qodd in[3, 2^253)(no NTT ring needed).- Return type:
- replicated_deal(q, values, n_random, binary)[source]¶
The pieces of
share_replicated()by the extension – the same pieces for the same random bytes: each random value drawn asrandom.SystemRandom.randrange(0, q)draws it, from the call’s DRBG (or the test bytes ofrandom_input(); a test-installed DRBG seed makes every call read stream 0 of that seed, as for the other tensor calls: tests only), in the order of the Python code – orNonewhen that code applies.- Raises:
FEKeyError – For binary values that are not whole words below q.
RuntimeError – If the test bytes run out (they are bounded; the DRBG is not).
- Parameters:
- zq_sum(q, count, rows, materials, subtract=False, binary=False, reference=True)[source]¶
sum(rows) +- sum_m H(m)mod q,H:countvalues ofhash_to_zq()("shake256-wide"), as decimal strings; with binary the sum of rows of words, as words. The reference converts withgmpy2.mpzrow by row and raisesFESchemeErrorfor a row of another length than count. Without reference:Noneinstead of the reference for what the extension does not take (the caller then has reference steps of its own). The materials hold seeds: the extension wipes its copies.
- r255_add(a, b)[source]¶
a + b: group elements as 32-byte encodings (native) or points (Python); both implementations read both forms (ec_group).
- r255_mul(point, k)[source]¶
k point(k an integer, reduced mod the group order natively).- Parameters:
k (
int)
- r255_decode(data)[source]¶
The element of a canonical encoding.
- Raises:
ValueError – If data is not one.
- Parameters:
data (
bytes)
- r255_dlog(point, bound)[source]¶
The
xwithx G = pointand|x| <= bound, orNone.- Parameters:
bound (
int)
- dense_key(key_row, q)[source]¶
The key row of
label_dots():(packed, key_row), its values mod q as little-endian words ofceil(bits(q) / 64)64-bit limbs and the row itself (plain values: the caller caches and may pickle it).- Parameters:
key_row (
list)
- label_dots(materials, key, q, dim, mode)[source]¶
<key_row, H(m)> mod qfor every materialm(key: thedense_key()ofkey_row;H: dim values ofhash_to_zq_vector()with mode), as mpz: the dense-LWE MCFE label terms. Natively the products are summed before the hash blocks are reduced (congruent mod q); a key row of another length than dim goes to the reference.
- r255_mcfe_encrypt(msgs, dst, values, s0, s1)[source]¶
MCFE ristretto255 ciphertexts
x G + s0 H0(l) + s1 H1(l)of the valuesxunder the labels of msgs (H_k(l):r255_hash()ofbytes([k]) + msg), as hex strings.
- r255_mcfe_decrypt(cts, ys, d0, d1, msgs, dst, bound)[source]¶
The decryptions
dlog(sum_i y_i ct_i - d0 H0(l) - d1 H1(l))of a layer (cts[i][e]: clienti’s hex ciphertext of elemente):(values, mask)as indecrypt_tensor().
- bls12_381()[source]¶
The BLS12-381 kernels (
Bls12381Kernels), orNonewhen the extension is not built: the pairing group has no Python implementation, so both backends return the extension’s.