Using NOISE_FLOODING_DECRYPT for CKKS in a multiparty setting

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?