Skip to content

CKKS Bootstrapping

When a ciphertext is created, it has a fixed multiplication count called a level. When two cipehrtexts are multiplied, the resulting ciphertext will have the level reduced by one compared to the input ciphertexts. A ciphertext with level 0 can no longer be multiplied. It is possible to reset this level to a higher value by the bootstrapping operation.

Regular Bootstrap

The Bootstrapping operation requires the bootstrap key. The bootstrap key incorporates every single fixed rotation key that is needed by the bootstrap operation. Because of this, the bootstrap key is quite large (about 12.3GB).

from desilofhe import Engine

engine = Engine(use_bootstrap=True)
secret_key = engine.create_secret_key()
public_key = engine.create_public_key(secret_key)
relinearization_key = engine.create_relinearization_key(secret_key)
conjugation_key = engine.create_conjugation_key(secret_key)
bootstrap_key = engine.create_bootstrap_key(secret_key, stage_count=3)

message = [-1, 0, 1]
ciphertext = engine.encrypt(message, public_key, level=0)
bootstrapped = engine.bootstrap(
    ciphertext, relinearization_key, conjugation_key, bootstrap_key
)

Small Bootstrap Key

Alternatively, the bootstrapping operation can be performed using the rotation key and the small bootstrap key instead of the bootstrapping key. This approach reduces memory usage to around 3.8GB but makes the operation slower.

from desilofhe import Engine

engine = Engine(use_bootstrap=True)
secret_key = engine.create_secret_key()
public_key = engine.create_public_key(secret_key)
relinearization_key = engine.create_relinearization_key(secret_key)
conjugation_key = engine.create_conjugation_key(secret_key)
rotation_key = engine.create_rotation_key(secret_key)
small_bootstrap_key = engine.create_small_bootstrap_key(secret_key)

message = [-1, 0, 1]
ciphertext = engine.encrypt(message, public_key, level=0)
bootstrapped = engine.bootstrap(
    ciphertext,
    relinearization_key,
    conjugation_key,
    rotation_key,
    small_bootstrap_key,
)

bootstrapped_stage_count_5 = engine.bootstrap(
    ciphertext,
    relinearization_key,
    conjugation_key,
    rotation_key,
    small_bootstrap_key,
    stage_count=5,
)

Benchmark

Here are the benchmarks of the bootstrapping operation. The experiments were performed on an Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz for the CPU measurements and an NVIDIA GeForce RTX 5090 for the GPU measurements. These values are the averages of 10 runs.

Key
Size
Stage
Count
Runtime (s)
1 Thread

4 Threads

16 Threads

GPU
Small 3 111.153 50.250 34.906 3.080
Small 4 71.865 30.536 21.556 2.007
Small 5 69.345 28.541 19.907 1.820
Medium 3 30.719 12.560 9.732 0.776
Medium 4 24.994 10.196 7.745 0.635
Medium 5 23.611 9.623 7.118 0.600
Large 3 27.399 11.850 9.468 0.747
Large 4 24.226 10.026 8.049 0.648
Large 5 21.586 8.928 6.846 0.582

Bootstrap To 14 Levels

The original parameter set for bootstrapping leaves 10 levels after 3 stages. To overcome this, a new parameter set has been introduced to have 14 levels after the bootstrapping operation. Due to the limitations of the new parameter set, the only supported stage count is 3.

from desilofhe import Engine

engine = Engine(use_bootstrap_to_14_levels=True)
secret_key = engine.create_secret_key()
public_key = engine.create_public_key(secret_key)
relinearization_key = engine.create_relinearization_key(secret_key)
conjugation_key = engine.create_conjugation_key(secret_key)
bootstrap_key = engine.create_bootstrap_key(secret_key)

message = [-1, 0, 1]
ciphertext = engine.encrypt(message, public_key, level=0)
bootstrapped = engine.bootstrap(
    ciphertext, relinearization_key, conjugation_key, bootstrap_key
)

Small Bootstrap Key

The small boostrap key can also be used for this parameter set.

from desilofhe import Engine

engine = Engine(use_bootstrap_to_14_levels=True)
secret_key = engine.create_secret_key()
public_key = engine.create_public_key(secret_key)
relinearization_key = engine.create_relinearization_key(secret_key)
conjugation_key = engine.create_conjugation_key(secret_key)
rotation_key = engine.create_rotation_key(secret_key)
small_bootstrap_key = engine.create_small_bootstrap_key(secret_key)

message = [-1, 0, 1]
ciphertext = engine.encrypt(message, public_key, level=0)
bootstrapped = engine.bootstrap(
    ciphertext,
    relinearization_key,
    conjugation_key,
    rotation_key,
    small_bootstrap_key,
)

Benchmark

Here are the benchmarks of the bootstrapping operation to 14 levels. The experiments were performed on an Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz for the CPU measurements and an NVIDIA GeForce RTX 5090 for the GPU measurements. These values are the averages of 10 runs.

Key
Size
Stage
Count
Runtime (s)
1 Thread

4 Threads

16 Threads

GPU
Small 3 139.025 60.864 42.782 3.283
Medium 3 37.549 15.535 12.095 0.849
Large 3 33.582 14.619 11.689 0.815

Sparse Bootstrap

The bootstrapping operation is faster with a reduced slot_count. The sparse bootstrapping operation can be done with either a bootstrap key or a small bootstrap key.

Bootstrap Key

from desilofhe import Engine

engine = Engine(slot_count=1024, use_bootstrap=True)
secret_key = engine.create_secret_key()
public_key = engine.create_public_key(secret_key)
relinearization_key = engine.create_relinearization_key(secret_key)
conjugation_key = engine.create_conjugation_key(secret_key)
bootstrap_key = engine.create_bootstrap_key(secret_key, stage_count=3)

message = [-1, 0, 1, 0] * 256
ciphertext = engine.encrypt(message, public_key, level=0)
bootstrapped = engine.bootstrap(
    ciphertext, relinearization_key, conjugation_key, bootstrap_key
)

Small Bootstrap Key

from desilofhe import Engine

engine = Engine(slot_count=1024, use_bootstrap=True)
secret_key = engine.create_secret_key()
public_key = engine.create_public_key(secret_key)
relinearization_key = engine.create_relinearization_key(secret_key)
conjugation_key = engine.create_conjugation_key(secret_key)
rotation_key = engine.create_rotation_key(secret_key)
small_bootstrap_key = engine.create_small_bootstrap_key(secret_key)

message = [-1, 0, 1, 0] * 256
ciphertext = engine.encrypt(message, public_key, level=0)
bootstrapped = engine.bootstrap(
    ciphertext,
    relinearization_key,
    conjugation_key,
    rotation_key,
    small_bootstrap_key,
    stage_count=1,
)

Benchmark

Here are the benchmarks of the sparse bootstrapping operation. The experiments were performed on an Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz for the CPU measurements and an NVIDIA GeForce RTX 5090 for the GPU measurements. These values are the averages of 10 runs.

Slot
Count
Key
Size
Stage
Count
Runtime (s)
1 Thread

4 Threads

16 Threads

GPU
32 Small 1 43.630 16.745 12.004 0.927
32 Small 2 32.727 12.591 9.002 0.687
32 Medium 1 26.502 10.728 7.889 0.577
32 Medium 2 25.079 10.070 7.202 0.538
32 Large 1 26.197 10.795 7.981 0.580
1024 Small 2 69.975 28.240 19.700 1.582
1024 Small 3 49.533 19.160 13.577 1.080
1024 Small 4 44.567 17.565 12.414 1.010
1024 Medium 2 29.201 11.850 8.916 0.678
1024 Medium 3 25.569 10.188 7.504 0.588
1024 Medium 4 24.274 9.515 6.989 0.562
1024 Large 2 27.581 11.478 8.836 0.670
1024 Large 3 24.617 9.906 7.548 0.589

Lossy Bootstap

The lossy bootstrap operation is faster while sacrificing some precision. The resulting significant figures below the decimal is about halved. In general, the regular bootstrapping is the recommended method, but depending on the precision requirements this method could also be useful. The lossy bootstrapping operation can be done with either a lossy bootstrap key or a small bootstrap key. The lossy bootstrapping operation is not supported for the bootstrap to 14 levels parameter set.

Lossy Bootstrap Key

from desilofhe import Engine

engine = Engine(use_bootstrap=True)
secret_key = engine.create_secret_key()
public_key = engine.create_public_key(secret_key)
relinearization_key = engine.create_relinearization_key(secret_key)
conjugation_key = engine.create_conjugation_key(secret_key)
lossy_bootstrap_key = engine.create_lossy_bootstrap_key(
    secret_key, stage_count=3
)

message = [-1, 0, 1]
ciphertext = engine.encrypt(message, public_key, level=3)
bootstrapped = engine.lossy_bootstrap(
    ciphertext, relinearization_key, conjugation_key, lossy_bootstrap_key
)

Small Bootstrap Key

from desilofhe import Engine

engine = Engine(use_bootstrap=True)
secret_key = engine.create_secret_key()
public_key = engine.create_public_key(secret_key)
relinearization_key = engine.create_relinearization_key(secret_key)
conjugation_key = engine.create_conjugation_key(secret_key)
rotation_key = engine.create_rotation_key(secret_key)
small_bootstrap_key = engine.create_small_bootstrap_key(secret_key)

message = [-1, 0, 1]
ciphertext_level_3 = engine.encrypt(message, public_key, level=3)
bootstrapped = engine.lossy_bootstrap(
    ciphertext_level_3,
    relinearization_key,
    conjugation_key,
    rotation_key,
    small_bootstrap_key,
)

ciphertext_level_5 = engine.encrypt(message, public_key, level=5)
bootstrapped_stage_count_5 = engine.lossy_bootstrap(
    ciphertext_level_5,
    relinearization_key,
    conjugation_key,
    rotation_key,
    small_bootstrap_key,
    stage_count=5,
)

Benchmark

Here are the benchmarks of the lossy bootstrapping operation. The experiments were performed on an Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz for the CPU measurements and an NVIDIA GeForce RTX 5090 for the GPU measurements. These values are the averages of 10 runs.

Key
Size
Stage
Count
Runtime (s)
1 Thread

4 Threads

16 Threads

GPU
Small 3 87.112 38.158 27.515 2.746
Small 4 58.221 24.471 17.754 1.795
Small 5 57.687 24.314 17.201 1.619
Medium 3 24.039 10.046 7.641 0.640
Medium 4 20.360 8.322 6.328 0.534
Medium 5 20.255 8.019 5.979 0.505
Large 3 22.067 9.445 7.483 0.630
Large 4 20.132 8.296 6.590 0.551
Large 5 18.552 7.408 5.743 0.493

Lossy bootstrapping is optimized for ciphertexts that contain only real values.

Key
Size
Stage
Count
Runtime (s)
1 Thread

4 Threads

16 Threads

GPU
Small 3 81.395 35.765 25.752 2.611
Small 4 52.606 22.552 16.032 1.663
Small 5 53.114 22.092 15.689 1.490
Medium 3 18.253 7.532 5.893 0.498
Medium 4 14.835 6.076 4.699 0.395
Medium 5 14.887 5.960 4.436 0.371
Large 3 16.046 7.006 5.731 0.488
Large 4 14.215 6.089 4.952 0.406
Large 5 13.090 5.343 4.214 0.351

Sign Bootstrap

The sign bootstrapping operation is a variant of the lossy bootstrap that enables bootstrapping of sign values, i.e., -1 and 1, with higher precision. Although this is applicable only to sign values, the resulting ciphertext achieves roughly three times more significant digits. By efficiently reducing the noise of sign values, this operation facilitates fast and high-precision comparison functions such as min and max. The sign bootstrapping can be performed with either a small bootstrap key or a lossy bootstrap key. The sign bootstrapping operation is not supported for the bootstrap to 14 levels parameter set.

Sign Bootstrap with Lossy Bootstrap Key

from desilofhe import Engine

engine = Engine(use_bootstrap=True)
secret_key = engine.create_secret_key()
public_key = engine.create_public_key(secret_key)
relinearization_key = engine.create_relinearization_key(secret_key)
conjugation_key = engine.create_conjugation_key(secret_key)
lossy_bootstrap_key = engine.create_lossy_bootstrap_key(
    secret_key, stage_count=3
)

message = [-1, 1]
ciphertext = engine.encrypt(message, public_key, level=3)
bootstrapped = engine.sign_bootstrap(
    ciphertext, relinearization_key, conjugation_key, lossy_bootstrap_key
)

Small Bootstrap Key

from desilofhe import Engine

engine = Engine(use_bootstrap=True)
secret_key = engine.create_secret_key()
public_key = engine.create_public_key(secret_key)
relinearization_key = engine.create_relinearization_key(secret_key)
conjugation_key = engine.create_conjugation_key(secret_key)
rotation_key = engine.create_rotation_key(secret_key)
small_bootstrap_key = engine.create_small_bootstrap_key(secret_key)

message = [-1, 1]
ciphertext_level_3 = engine.encrypt(message, public_key, level=3)
bootstrapped = engine.sign_bootstrap(
    ciphertext_level_3,
    relinearization_key,
    conjugation_key,
    rotation_key,
    small_bootstrap_key,
)

ciphertext_level_5 = engine.encrypt(message, public_key, level=5)
bootstrapped_stage_count_5 = engine.sign_bootstrap(
    ciphertext_level_5,
    relinearization_key,
    conjugation_key,
    rotation_key,
    small_bootstrap_key,
    stage_count=5,
)

Sign Message Bootstrapping Error

The following shows the average error observed when bootstrapping sign messages (i.e., -1 and 1) using the regular, lossy, and sign bootstrapping methods, respectively. Note that for sign messages, the precision of the regular and lossy bootstrapping is measured to be similar; however, when the message range covers the entire interval [-1, 1], the regular bootstrapping exhibits higher precision.

Regular
Bootstrap
Lossy
Bootstrap
Sign
Bootstrap
Average error 1.60562e-3 1.60562e-3 3.10572e-10

Benchmark

Here are the benchmarks of the sign bootstrapping operation. The experiments were performed on an Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz for the CPU measurements and an NVIDIA GeForce RTX 5090 for the GPU measurements. These values are the averages of 10 runs.

Key
Size
Stage
Count
Runtime (s)
1 Thread

4 Threads

16 Threads

GPU
Small 3 87.257 38.385 27.588 2.744
Small 4 58.256 24.834 17.732 1.808
Small 5 58.498 24.236 17.178 1.625
Medium 3 24.391 10.039 7.656 0.641
Medium 4 20.577 8.415 6.340 0.529
Medium 5 19.968 8.104 5.987 0.509
Large 3 22.408 9.405 7.485 0.628
Large 4 19.977 8.376 6.585 0.543
Large 5 18.151 7.484 5.771 0.491

Sign bootstrapping is optimized for ciphertexts that contain only real values.

Key
Size
Stage
Count
Runtime (s)
1 Thread

4 Threads

16 Threads

GPU
Small 3 80.993 35.728 25.818 2.611
Small 4 52.593 22.451 16.105 1.662
Small 5 52.560 22.111 15.644 1.488
Medium 3 18.242 7.665 5.890 0.503
Medium 4 14.918 6.026 4.687 0.396
Medium 5 14.895 5.954 4.450 0.375
Large 3 16.271 7.006 5.723 0.489
Large 4 14.378 6.080 4.934 0.404
Large 5 12.840 5.392 4.206 0.351