How do TFHE-rs detect and manage noise overflow errors in homomorphic operations?
I understand that homomorphic multiplication is implemented with PBS hence noise is not an issue here.
What about other homomorphic operations like ciphertext addition? After a certain number of operations noise should overflow but it is not happening, is this because of automatic noise management?
Is the noise management dynamic? Is the noise computed after every homomorphic computation (other than multiplication) and is PBS performed if the noise is too high?
Is it possible to disable this feature, so that I can manually perform PBS?
I’m assuming you are talking about the shortint API, if you don’t want noise checking you can use unchecked APIs but then you are at risk of having wrong computations so we don’t recommend that.
As for the noise management, the max noise level is indicated in the parameter set and is the maximum number of additions you can do (it’s also the maximum clear value by which you can safely multiply without having too much noise).
So if the max noise level is 5 you can add 5 ciphertexts together before needing to refresh the noise or multiply a ciphertext by 5 before needing the refresh the noise.
What unchecked APIs do you recommend? I am trying the core API, it has the features I need.
Is there a feature to look at the noise in a ciphertext similar to SEAL?
Another question I have is, how can I compute noise in a fresh cipher? Can you point me to worst-case or average-case noise heuristics for TFHE-rs.
I am running into similar issue. I see that noise_level is checked in this testcase on github (weird that the forums wouldn’t let new users to put in links!):
tfhe-rs/blob/main/tfhe/src/shortint/server_key/tests/noise_level.rs
I am a Rust newbie; so any suggestion on writing a small testcase that checks the noise level in a ciphertext will be helpful. Alternatively, if you can suggest how to run the above testcase, that is also helpful.
Thanks @IceTDrinker . As a minimal example, I tried the following and it ran fine. However, I am now confused with two issues:
What is the right way to construct a value of NoiseLevel? (i.e., in your minimal example, how could one construct expected_noise_level?)
Why does the statement assert_eq!(ct_3.noise_level(), ct_1.noise_level()); work? Isn’t noise of ct_3 supposed to be different (and more) than ct_1 because of add? Shouldn’t the assertion fail?
use tfhe::shortint::prelude::*;
fn main() {
// We generate a set of client/server keys
let (client_key, server_key) = gen_keys(PARAM_MESSAGE_2_CARRY_2_KS_PBS);
let msg1 = 1;
let msg2 = 0;
let modulus = client_key.parameters.message_modulus().0;
// We use the client key to encrypt two messages:
let ct_1 = client_key.encrypt(msg1);
let ct_2 = client_key.encrypt(msg2);
// We use the server public key to execute an integer circuit:
let ct_3 = server_key.add(&ct_1, &ct_2);
assert_eq!(ct_3.noise_level(), ct_1.noise_level());
// We use the client key to decrypt the output of the circuit:
let output = client_key.decrypt(&ct_3);
println!("Hello {}", output);
assert_eq!(output, (msg1 + msg2) % modulus);
}
Just to kind of follow-up, I am able to use the following with (1) and (2) throwing an assertion failure whereas (3) passes. But I am clueless on what those options means.
The way we model the noise in shortint is the following :
Noise at encryption and after a PBS is seen as a NoiseLevel::NOMINAL which has an associated value of 1
Trivial ciphertexts have a noise level of NoiseLevel::ZERO as they are not real encryptions, just a plaintext encoded value without noise or mask
The unknown value is more of an internal mechanism to force automatic noise refresh if we are in a part of the code where we can’t really tell what the noise level is, this only should happen during certain keyswitching between parameter sets, a very niche case
As for the noise growth of linear operations:
an addition or subtraction will add the noise of the two ciphertexts
a multiplication by a scalar will multiply the noise of the ciphertext by that scalar
a PBS refreshes the noise to a nominal level (provided you did not overflow the plaintext space and respec the MaxNoiseLevel of the ciphertext)
The reason for the refresh of the noise of the add operation is that it is a so called “default operation” which for historical reasons always refreshes the noise of the ciphertext by applying a PBS, if you want the “true” add you have to use the unchecked_add operation, you just need to make sure that the add is possible before hand by calling the ServerKey is_add_possible or use a checked_add operation so that it makes the verification for you.