Skip to content

CKKS Bootstrappping

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 107.517 44.110 32.211 3.121
Small 4 69.709 28.623 20.833 2.039
Small 5 67.595 26.996 19.412 1.824
Medium 3 29.936 12.708 9.880 0.813
Medium 4 24.987 10.188 7.865 0.681
Medium 5 23.007 9.533 7.147 0.620
Large 3 27.887 11.873 9.617 0.792
Large 4 24.197 10.180 8.174 0.688
Large 5 21.490 8.928 6.937 0.616

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.877 17.057 12.289 0.979
32 Small 2 32.298 12.819 9.197 0.730
32 Medium 1 27.181 10.853 8.013 0.624
32 Medium 2 25.483 10.089 7.343 0.584
32 Large 1 26.815 10.798 8.124 0.634
1024 Small 2 70.839 28.214 20.153 1.640
1024 Small 3 49.149 19.323 13.883 1.130
1024 Small 4 45.288 17.685 12.680 1.059
1024 Medium 2 29.119 11.907 9.073 0.724
1024 Medium 3 25.107 10.264 7.662 0.632
1024 Medium 4 24.363 9.537 7.133 0.605
1024 Large 2 27.518 11.341 8.999 0.716
1024 Large 3 24.158 9.959 7.677 0.633

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.

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 84.346 35.136 25.935 3.015
Small 4 59.703 24.254 17.881 1.943
Small 5 58.620 24.001 17.477 1.778
Medium 3 27.083 11.167 8.618 0.766
Medium 4 23.056 9.422 7.226 0.648
Medium 5 22.556 9.093 6.808 0.617
Large 3 25.034 10.561 8.454 0.753
Large 4 22.493 9.343 7.470 0.657
Large 5 20.777 8.515 6.558 0.593