Hello.
I am trying to use the NOISE_FLOODING_DECRYPT mode for CKKS in a multiparty setting in the Python version of OpenFHE.
This post Support for NOISE_FLOODING_MULTIPARTY in threshold CKKS? - Library Questions - OpenFHE talks about using this example openfhe-development/src/pke/examples/ckks-noise-flooding.cpp at main · openfheorg/openfhe-development and scaling the noise estimate by the number of clients. When I try to do this I either get wrong results or I get a runtime error such as
/usr/local/include/openfhe/pke/scheme/ckksrns/gen-cryptocontext-ckksrns-internal.h:l.90:genCryptoContextCKKSRNSInternal(): Precision of less than 3 bits is not supported. logstd 16.792481 + noiseEstimate 60.571271 must be 56 or less.
As an example I have the following code, where openfhe is imported as fhe. When I run it with num_clients = 3 I get wrong results, e.g.,
Results of homomorphic computations
#1 + #2 + #3 = (-536.83891, 54.510953, -111.15191, ... ); Estimated precision: 9 bits
while if I run it with a higher number such as 7 I get the runtime error above.
def generate_cc(noise_estimate = None):
# securityLevel = fhe.SecurityLevel.HEStd_NotSet
securityLevel = fhe.SecurityLevel.HEStd_128_classic
# securityLevel = fhe.SecurityLevel.HEStd_256_classic
multDepth = 0
ringDim = 2**13
sigma = 3.19
secretKeyDist = fhe.UNIFORM_TERNARY
q_0_bits = 60 # 60 bits fungerer ihvertfall.
delta_bits = 50
# Sample Program: Step 1: Set CryptoContext
parameters = fhe.CCParamsCKKSRNS()
parameters.SetSecurityLevel(securityLevel)
parameters.SetMultiplicativeDepth(multDepth)
parameters.SetRingDim(ringDim)
batchsize = ringDim//2 # Int division for å få heltall istedenfor .0
parameters.SetBatchSize(batchsize)
parameters.SetSecretKeyDist(secretKeyDist)
parameters.SetStandardDeviation(sigma)
parameters.SetKeySwitchTechnique(fhe.BV)
parameters.SetScalingModSize(delta_bits)
parameters.SetFirstModSize(q_0_bits)
parameters.SetDecryptionNoiseMode(fhe.NOISE_FLOODING_DECRYPT)
parameters.SetScalingTechnique(fhe.FIXEDAUTO)
if noise_estimate:
print(f"Noise estimate is {noise_estimate}")
parameters.SetExecutionMode(fhe.EXEC_EVALUATION)
parameters.SetNoiseEstimate(noise_estimate)
else:
print("No noise estimate.")
parameters.SetExecutionMode(fhe.EXEC_NOISE_ESTIMATION)
cc = fhe.GenCryptoContext(parameters)
# Enable features that you wish to use
cc.Enable(fhe.PKE)
cc.Enable(fhe.KEYSWITCH)
cc.Enable(fhe.LEVELEDSHE)
cc.Enable(fhe.ADVANCEDSHE)
cc.Enable(fhe.MULTIPARTY)
return cc
def do_computations_multiparty(num_clients, cc, sks, jpk):
real1 = 16667.23
real2 = -12123.213
real3 = 1223.9991
plaintext1 = cc.MakeCKKSPackedPlaintext([real1, real2, real3])
plaintext2 = cc.MakeCKKSPackedPlaintext([real2, real3, real1])
plaintext3 = cc.MakeCKKSPackedPlaintext([real3, real1, real2])
# The encoded vectors are encrypted
ciphertext1 = cc.Encrypt(jpk, plaintext1)
ciphertext2 = cc.Encrypt(jpk, plaintext2)
ciphertext3 = cc.Encrypt(jpk, plaintext3)
# Homomorphic additions
ciphertext_add12 = cc.EvalAdd(ciphertext1, ciphertext2)
ciphertext_add_result = cc.EvalAdd(ciphertext_add12, ciphertext3)
# Decrypt the result of additions
pd0 = cc.MultipartyDecryptLead([ciphertext_add_result], sks[0])[0]
pds = [pd0]
for i in range(1,num_clients):
pds.append(cc.MultipartyDecryptMain([ciphertext_add_result], sks[i])[0])
plaintext_add_result = cc.MultipartyDecryptFusion(pds)
print(f"real1 = {real1}")
print(f"real2 = {real2}")
print(f"real3 = {real3}")
print(f"Plaintext #1: {plaintext1.GetRealPackedValue()}")
print(f"Plaintext #2: {plaintext2.GetRealPackedValue()}")
print(f"Plaintext #3: {plaintext3.GetRealPackedValue()}")
# Output Results
print("\nResults of homomorphic computations")
plaintext_add_result.SetLength(3)
print(f"#1 + #2 + #3 = {plaintext_add_result}")
print(f"Expected result = {[real1+real2+real3, real1+real2+real3, real1+real2+real3]}")
def do_computations_single(cc, sk, pk):
# Sample Program: Step 3: Encryption
real1 = 16667.23
real2 = -12123.213
real3 = 1223.9991
plaintext1 = cc.MakeCKKSPackedPlaintext([real1, real2, real3])
plaintext2 = cc.MakeCKKSPackedPlaintext([real2, real3, real1])
plaintext3 = cc.MakeCKKSPackedPlaintext([real3, real1, real2])
# The encoded vectors are encrypted
ciphertext1 = cc.Encrypt(pk, plaintext1)
ciphertext2 = cc.Encrypt(pk, plaintext2)
ciphertext3 = cc.Encrypt(pk, plaintext3)
# Homomorphic additions
ciphertext_add12 = cc.EvalAdd(ciphertext1, ciphertext2)
ciphertext_add_result = cc.EvalAdd(ciphertext_add12, ciphertext3)
# Decrypt the result of additions
plaintext_add_result = cc.Decrypt(ciphertext_add_result, sk)
return plaintext_add_result.GetLogError()
def main():
# Generate first cc and key pair for noise estimate.
cc1 = generate_cc()
# Generate a public/private key pair
key_pair_single = cc1.KeyGen()
pk = key_pair_single.publicKey
sk = key_pair_single.secretKey
# Noise estimate should be multiplied number of clients.
noise = do_computations_single(cc1, sk, pk)
print(f"\nNoise estimate is: {noise}")
num_clients = 7
noise = noise * num_clients
print(f"Scale noise estimate is: {noise}\n")
# Generate new keys.
cc2 = generate_cc(noise)
key_pair_multi = cc2.KeyGen()
pks = [key_pair_multi.publicKey]
sks = [key_pair_multi.secretKey]
for i in range(num_clients-1):
new_keys = cc2.MultipartyKeyGen(pks[i])
pks.append(new_keys.publicKey)
sks.append(new_keys.secretKey)
jpk = pks[-1]
print("\n")
do_computations_multiparty(num_clients, cc2, sks, jpk)
if __name__ == "__main__":
main()
Any suggestions to what I am doing wrong is appreciated. Thanks in advance.
Also, in my usecase it might be difficult to know exactly what data will be encrypted before hand. Is it possible to do noise estimation part in the multiparty setting or must it be done with a non-multiparty key pair?