Forráskód Böngészése

December Release Updates (#88)

* link to contrib guide

* IntMPBoot functions

* scheme switchinh api + partial tkks example

* RemoveElement function + TCKKS Bootstrapping

* TCKKS - chebyschev

* binfhe wrappers

* scheme-switching api

* ckks-to-fhew example

* SwitchFHEWtoCKKS, Floor and Func

* evalmin + evalsign

* Argmin alt and unit

* PolyViaSchemeSwitching

* lmkcdey bootstrapping

* ProxyReencryption API + Plaintext API

* remove debug line

* updating readme with new examples

* updated to v0.8.3 (#91)

Co-authored-by: Yuriy Polyakov <ypolyakod@dualitytech.com>

---------

Co-authored-by: yspolyakov <89226542+yspolyakov@users.noreply.github.com>
Co-authored-by: Yuriy Polyakov <ypolyakod@dualitytech.com>
Rener Oliveira 11 hónapja
szülő
commit
24d4483265

+ 1 - 1
CMakeLists.txt

@@ -4,7 +4,7 @@ project (OpenFHE-Python)
 
 set(OPENFHE_PYTHON_VERSION_MAJOR 0)
 set(OPENFHE_PYTHON_VERSION_MINOR 8)
-set(OPENFHE_PYTHON_VERSION_PATCH 2)
+set(OPENFHE_PYTHON_VERSION_PATCH 3)
 set(OPENFHE_PYTHON_VERSION ${OPENFHE_PYTHON_VERSION_MAJOR}.${OPENFHE_PYTHON_VERSION_MINOR}.${OPENFHE_PYTHON_VERSION_PATCH})
 
 set(CMAKE_CXX_STANDARD 17)

+ 13 - 4
README.md

@@ -9,6 +9,7 @@
     - [Using Conda environments](#conda)
 - [Running Examples](#code-examples)
 - [OpenFHE Python Wrapper Documentation](#openfhe-python-wrapper-documentation)
+- [Contributing Guide](#contributing-guide)
 
 ## Building
 
@@ -104,16 +105,24 @@ To get familiar with the OpenFHE Python API, check out the examples:
   - [Advanced CKKS Bootstrapping Example](examples/pke/advanced-ckks-bootstrapping.cpp)
   - [Double-Precision (Iterative) Bootstrapping Example](examples/pke/iterative-ckks-bootstrapping.py)
 - FHE for Boolean circuits and larger plaintext spaces (FHEW/TFHE):
-  - [Simple Code Example](examples/binfhe/boolean.py)
+  - [Simple Code Example with Symmetric Encryption](examples/binfhe/boolean.py)
   - [Truth Table Example](examples/binfhe/boolean-truth-table.py)
+- Scheme Switching:
+  - [Examples with Scheme Switching between CKKS and FHEW/TFHE](examples/pke/scheme-switching.py)
   <!-- - [Code with JSON serialization](examples/binfhe/boolean-serial-json.py) -->
   <!-- - [Code with Binary Serialization](examples/binfhe/boolean-serial-binary.py) -->
   <!-- - [Large-Precision Comparison](examples/binfhe/eval-sign.py) -->
   <!-- - [Small-Precison Arbitrary Function Evaluation](examples/binfhe/eval-function.py) -->
-  <!-- - Threshold FHE:  -->
-  <!-- - [Code Example for BGV, BFV, and CKKS](examples/pke/threshold-fhe.py) -->
-  <!-- - [Code Example for BFV with 5 parties](examples/pke/threshold-fhe-5p.py) -->
+- Threshold FHE: 
+  - [Code Example for BGV, BFV, and CKKS](examples/pke/threshold-fhe.py)
+  - [Simple Interactive Bootstrapping Example](examples/pke/tckks-interactive-mp-bootstrapping.py)
+  - [Interactive Bootstrapping after Chebyshev Approximation](examples/pke/tckks-interactive-mp-bootstrapping-Chebyschev.py)
+  - [Code Example for BFV with 5 parties](examples/pke/threshold-fhe-5p.py)
 
 ## OpenFHE Python Wrapper Documentation
 
 [OpenFHE Python Wrapper API Reference](https://openfheorg.github.io/openfhe-python/html/index.html)
+
+## Contributing Guide
+
+[OpenFHE Development - Contributing Guide](https://openfhe-development.readthedocs.io/en/latest/sphinx_rsts/contributing/contributing_workflow.html)

+ 51 - 0
examples/binfhe/boolean-lmkcdey.py

@@ -0,0 +1,51 @@
+from openfhe import *
+
+## Sample Program: Step 1: Set CryptoContext
+
+cc = BinFHEContext()
+
+# We use the STD128 setting optimized for the LMKCDEY mode.
+cc.GenerateBinFHEContext(STD128,LMKCDEY)
+
+## Sample Program: Step 2: Key Generation
+
+# Generate the secret key
+sk = cc.KeyGen()
+
+print("Generating the bootstrapping keys...\n")
+
+# Generate the bootstrapping keys (refresh and switching keys)
+cc.BTKeyGen(sk)
+
+print("Completed the key generation.\n")
+
+# Sample Program: Step 3: Encryption
+"""
+Encrypt two ciphertexts representing Boolean True (1).
+By default, freshly encrypted ciphertexts are bootstrapped.
+If you wish to get a fresh encryption without bootstrapping, write
+ct1 = cc.Encrypt(sk, 1, FRESH)
+"""
+ct1 = cc.Encrypt(sk, 1)
+ct2 = cc.Encrypt(sk, 1)
+
+# Sample Program: Step 4: Evaluation
+
+# Compute (1 AND 1) = 1; Other binary gate options are OR, NAND, and NOR
+ctAND1 = cc.EvalBinGate(AND, ct1, ct2)
+
+# Compute (NOT 1) = 0
+ct2Not = cc.EvalNOT(ct2)
+
+# Compute (1 AND (NOT 1)) = 0
+ctAND2 = cc.EvalBinGate(AND, ct2Not, ct1)
+
+# Compute OR of the result in ctAND1 and ctAND2
+ctResult = cc.EvalBinGate(OR, ctAND1, ctAND2)
+
+# Sample Program: Step 5: Decryption
+
+result = cc.Decrypt(sk, ctResult)
+
+print(f"Result of encrypted computation of (1 AND 1) OR (1 AND (NOT 1)) = {result}")
+

+ 127 - 0
examples/pke/pre-buffer.py

@@ -0,0 +1,127 @@
+import time
+import random
+from math import log2
+from openfhe import *
+
+def main():
+    passed = run_demo_pre()
+
+    if not passed: # there could be an error
+        return 1
+    return 0 # successful return
+
+def run_demo_pre():
+    # Generate parameters.
+    print("setting up BFV RNS crypto system")
+    start_time = time.time()
+    plaintextModulus = 65537  # can encode shorts
+
+    parameters = CCParamsBFVRNS()
+    parameters.SetPlaintextModulus(plaintextModulus)
+    parameters.SetScalingModSize(60)
+
+    cc = GenCryptoContext(parameters)
+    print(f"\nParam generation time: {time.time() - start_time} ms")
+
+    # Turn on features
+    cc.Enable(PKE)
+    cc.Enable(KEYSWITCH)
+    cc.Enable(LEVELEDSHE)
+    cc.Enable(PRE)
+
+    print(f"p = {cc.GetPlaintextModulus()}")
+    print(f"n = {cc.GetCyclotomicOrder()/2}")
+    print(f"log2 q = {log2(cc.GetModulus())}")
+    print(f"r = {cc.GetDigitSize()}")
+
+    ringsize = cc.GetRingDimension()
+    print(f"Alice can encrypt {ringsize * 2} bytes of data")
+
+    # Perform Key Generation Operation
+
+    print("\nRunning Alice key generation (used for source data)...")
+    start_time = time.time()
+    keyPair1 = cc.KeyGen()
+    print(f"Key generation time: {time.time() - start_time} ms")
+
+    if not keyPair1.good():
+        print("Alice Key generation failed!")
+        return False
+
+    # Encode source data
+    nshort = ringsize
+    vShorts = [random.randint(0, 65536) for _ in range(nshort)]
+    pt = cc.MakePackedPlaintext(vShorts)
+
+    # Encryption
+    start_time = time.time()
+    ct1 = cc.Encrypt(keyPair1.publicKey, pt)
+    print(f"Encryption time: {time.time() - start_time} ms")
+
+    # Decryption of Ciphertext
+    start_time = time.time()
+    ptDec1 = cc.Decrypt(keyPair1.secretKey, ct1)
+    print(f"Decryption time: {time.time() - start_time} ms")
+
+    ptDec1.SetLength(pt.GetLength())
+
+    # Perform Key Generation Operation
+    print("Bob Running key generation ...")
+    start_time = time.time()
+    keyPair2 = cc.KeyGen()
+    print(f"Key generation time: {time.time() - start_time} ms")
+
+    if not keyPair2.good():
+        print("Bob Key generation failed!")
+        return False
+    
+    # Perform the proxy re-encryption key generation operation.
+    # This generates the keys which are used to perform the key switching.
+
+    print("\nGenerating proxy re-encryption key...")
+    start_time = time.time()
+    reencryptionKey12 = cc.ReKeyGen(keyPair1.secretKey, keyPair2.publicKey)
+    print(f"Key generation time: {time.time() - start_time} ms")
+
+    # Re-Encryption
+    start_time = time.time()
+    ct2 = cc.ReEncrypt(ct1, reencryptionKey12)
+    print(f"Re-Encryption time: {time.time() - start_time} ms")
+
+    # Decryption of Ciphertext
+    start_time = time.time()
+    ptDec2 = cc.Decrypt(keyPair2.secretKey, ct2)
+    print(f"Decryption time: {time.time() - start_time} ms")
+
+    ptDec2.SetLength(pt.GetLength())
+
+    unpacked0 = pt.GetPackedValue()
+    unpacked1 = ptDec1.GetPackedValue()
+    unpacked2 = ptDec2.GetPackedValue()
+    good = True
+
+    # note that OpenFHE assumes that plaintext is in the range of -p/2..p/2
+    # to recover 0...q simply add q if the unpacked value is negative
+    for j in range(pt.GetLength()):
+        if unpacked1[j] < 0:
+            unpacked1[j] += plaintextModulus
+        if unpacked2[j] < 0:
+            unpacked2[j] += plaintextModulus
+
+    # compare all the results for correctness
+    for j in range(pt.GetLength()):
+        if (unpacked0[j] != unpacked1[j]) or (unpacked0[j] != unpacked2[j]):
+            print(f"{j}, {unpacked0[j]}, {unpacked1[j]}, {unpacked2[j]}")
+            good = False
+
+    if good:
+        print("PRE passes")
+    else:
+        print("PRE fails")
+
+    print("Execution Completed.")
+
+    return good
+
+if __name__ == "__main__":
+    main()

+ 1240 - 0
examples/pke/scheme-switching.py

@@ -0,0 +1,1240 @@
+from openfhe import *
+from math import *
+
+def main():
+    SwitchCKKSToFHEW()
+    SwitchFHEWtoCKKS()
+    FloorViaSchemeSwitching()
+    # FuncViaSchemeSwitching()
+    # PolyViaSchemeSwitching()
+    ComparisonViaSchemeSwitching()
+    ArgminViaSchemeSwitching()
+    ArgminViaSchemeSwitchingAlt()
+    #ArgminViaSchemeSwitchingUnit()
+    #ArgminViaSchemeSwitchingAltUnit()
+
+def SwitchCKKSToFHEW():
+
+    # Example of switching a packed ciphertext from CKKS to multiple FHEW ciphertexts.
+    print("\n-----SwitchCKKSToFHEW-----\n")
+
+    # Step 1: Setup CryptoContext for CKKS
+
+    # Specify main parameters
+    multDepth    = 3
+    firstModSize = 60
+    scaleModSize = 50
+    ringDim      = 4096
+    sl      = HEStd_NotSet
+    slBin = TOY
+    logQ_ccLWE   = 25
+    # slots = ringDim / 2  # Uncomment for fully-packed
+    slots     = 16  # sparsely-packed
+    batchSize = slots
+
+    parameters = CCParamsCKKSRNS()
+
+    parameters.SetMultiplicativeDepth(multDepth)
+    parameters.SetFirstModSize(firstModSize)
+    parameters.SetScalingModSize(scaleModSize)
+    parameters.SetScalingTechnique(FIXEDMANUAL)
+    parameters.SetSecurityLevel(sl)
+    parameters.SetRingDim(ringDim)
+    parameters.SetBatchSize(batchSize)
+
+    cc = GenCryptoContext(parameters)
+
+    # Enable the features that you wish to use
+    cc.Enable(PKE)
+    cc.Enable(KEYSWITCH)
+    cc.Enable(LEVELEDSHE)
+    cc.Enable(SCHEMESWITCH)
+
+    print(f"CKKS scheme is using ring dimension {cc.GetRingDimension()},")
+    print(f"number of slots {slots}, and supports a multiplicative depth of {multDepth}\n")
+
+    # Generate encryption keys
+    keys = cc.KeyGen()
+
+    # Step 2: Prepare the FHEW cryptocontext and keys for FHEW and scheme switching
+    FHEWparams     = cc.EvalCKKStoFHEWSetup(sl, slBin, False, logQ_ccLWE, False, slots)
+    ccLWE          = FHEWparams[0]
+    privateKeyFHEW = FHEWparams[1]
+    cc.EvalCKKStoFHEWKeyGen(keys, privateKeyFHEW)
+
+    print(f"FHEW scheme is using a lattice parameter {ccLWE.Getn()},")
+    print(f"logQ {logQ_ccLWE},")
+    print(f"and modulus q {ccLWE.Getq()}\n")
+
+    #  Compute the scaling factor to decrypt correctly in FHEW; the LWE mod switch is performed on the ciphertext at the last level
+    modulus_CKKS_from = cc.GetModulusCKKS()
+
+    pLWE1 = ccLWE.GetMaxPlaintextSpace() # Small precision
+    modulus_LWE  = 1 << logQ_ccLWE
+    beta = ccLWE.GetBeta()
+    pLWE2 = modulus_LWE / (2*beta) # Large precision
+
+    scFactor = cc.GetScalingFactorReal(0)
+    # if (cc.GetScalingTechnique() == FLEXIBLEAUTOEXT):
+    #     scFactor = cc.GetScalingFactorReal(1)
+    scale1 = modulus_CKKS_from / (scFactor * pLWE1)
+    scale2 = modulus_CKKS_from / (scFactor * pLWE2)
+
+    # Perform the precomputation for switching
+    cc.EvalCKKStoFHEWPrecompute(scale1)
+
+    # Step 3: Encoding and encryption of inputs
+    # Inputs
+    x1 = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
+    x2 = [0.0, 271.1, 30000.0, pLWE2-2]
+    encodedLength1 = len(x1)
+    encodedLength2 = len(x2)
+
+    # Encoding as plaintexts
+    ptxt1 = cc.MakeCKKSPackedPlaintext(x1, 1, 0)
+    ptxt2 = cc.MakeCKKSPackedPlaintext(x2, 1, 0)
+
+    # Encrypt the encoded vectors
+    c1 = cc.Encrypt(keys.publicKey, ptxt1)
+    c2 = cc.Encrypt(keys.publicKey, ptxt2)
+
+    # Step 4: Scheme Switching from CKKS to FHEW
+    cTemp = cc.EvalCKKStoFHEW(c1, encodedLength1)
+
+    print(f"\n---Decrypting switched ciphertext with small precision (plaintext modulus {pLWE1}) ---\n")
+    
+    x1Int = [round(x) % pLWE1 for x in x1]
+
+    ptxt1.SetLength(encodedLength1)
+    print(f"Input x1: {ptxt1.GetRealPackedValue()}; which rounds to {x1Int}")
+    print("FHEW Decryption")
+
+    for i in range(len(cTemp)):
+        result = ccLWE.Decrypt(privateKeyFHEW, cTemp[i], pLWE1)
+        print(result, end=" ")
+    print("\n")
+
+    # B: Second scheme switching case
+
+    # Perform the precomputation for switching
+    cc.EvalCKKStoFHEWPrecompute(scale2)
+    
+    # Transform the ciphertext from CKKS to FHEW (only for the number of inputs given)
+    cTemp2 = cc.EvalCKKStoFHEW(c2, encodedLength2)
+
+    print(f"\n---Decrypting switched ciphertext with large precision (plaintext modulus {pLWE2}) ---\n")
+    ptxt2.SetLength(encodedLength2)
+    print(f"Input x2: {ptxt2.GetRealPackedValue()}")
+    print("FHEW Decryption")
+
+    for i in range(len(cTemp2)):
+        result = ccLWE.Decrypt(privateKeyFHEW, cTemp2[i], int(pLWE2))
+        print(result, end=" ")
+    print("\n")
+
+    # C: Decompose the FHEW ciphertexts in smaller digits
+    print(f"Decomposed values for digit size of {pLWE1}:")
+    # Generate the bootstrapping keys (refresh and switching keys)
+    ccLWE.BTKeyGen(privateKeyFHEW)
+
+    for j in range(len(cTemp2)):
+        # Decompose the large ciphertext into small ciphertexts that fit in q
+        decomp = ccLWE.EvalDecomp(cTemp2[j])
+
+        # Decryption
+        p = ccLWE.GetMaxPlaintextSpace()
+        for i in range(len(decomp)):
+            ct = decomp[i]
+            if i == len(decomp) - 1:
+                p = int(pLWE2 / (pLWE1 ** floor(log(pLWE2)/log(pLWE1))))
+                # The last digit should be up to P / p^floor(log_p(P))
+            resultDecomp = ccLWE.Decrypt(privateKeyFHEW, ct, p)
+            print(f"( {resultDecomp} * {pLWE1} ^ {i} )") 
+            if i != len(decomp) - 1:
+                print("+", end=" ")
+        print("\n")
+
+def SwitchFHEWtoCKKS():
+    print("\n-----SwitchFHEWtoCKKS-----\n")
+    print("Output precision is only wrt the operations in CKKS after switching back.\n")
+
+    # Step 1: Setup CryptoContext for CKKS to be switched into
+
+    #  A. Specify main parameters
+    scTech = FIXEDAUTO
+    multDepth = 3 + 9 + 1 
+    # for r = 3 in FHEWtoCKKS, Chebyshev max depth allowed is 9, 1 more level for postscaling
+    if scTech == FLEXIBLEAUTOEXT:
+        multDepth += 1
+    scaleModSize = 50
+    ringDim = 8192
+    sl = HEStd_NotSet #  If this is not HEStd_NotSet, ensure ringDim is compatible
+    logQ_ccLWE = 28
+
+    # slots = ringDim/2; # Uncomment for fully-packed
+    slots = 16 # sparsely-packed
+    batchSize = slots
+
+    parameters = CCParamsCKKSRNS()
+    parameters.SetMultiplicativeDepth(multDepth)
+    parameters.SetScalingModSize(scaleModSize)
+    parameters.SetScalingTechnique(scTech)
+    parameters.SetSecurityLevel(sl)
+    parameters.SetRingDim(ringDim)
+    parameters.SetBatchSize(batchSize)
+
+    cc = GenCryptoContext(parameters)
+
+    # Enable the features that you wish to use
+    cc.Enable(PKE)
+    cc.Enable(KEYSWITCH)
+    cc.Enable(LEVELEDSHE)
+    cc.Enable(ADVANCEDSHE)
+    cc.Enable(SCHEMESWITCH)
+
+    print(f"CKKS scheme is using ring dimension {cc.GetRingDimension()},\n number of slots {slots}, and suports a multiplicative depth of {multDepth}\n")
+
+    # Generate encryption keys
+    keys = cc.KeyGen()
+
+    # Step 2: Prepare the FHEW cryptocontext and keys for FHEW and scheme switching
+    ccLWE = BinFHEContext()
+    ccLWE.GenerateBinFHEContext(TOY, False, logQ_ccLWE, 0, GINX, False)
+
+    # LWE private key
+    lwesk = ccLWE.KeyGen()
+
+    print(f"FHEW scheme is using lattice parameter {ccLWE.Getn()},\n logQ {logQ_ccLWE},\n and modulus q {ccLWE.Getq()}\n")
+
+    # Step 3. Precompute the necessary keys and information for switching from FHEW to CKKS
+    cc.EvalFHEWtoCKKSSetup(ccLWE, slots, logQ_ccLWE)
+
+    cc.EvalFHEWtoCKKSKeyGen(keys, lwesk)
+
+    # Step 4: Encoding and encryption of inputs
+    # For correct CKKS decryption, the messages have to be much smaller than the FHEW plaintext modulus!
+    pLWE1 = ccLWE.GetMaxPlaintextSpace() # Small precision
+    pLWE2 = 256 # Medium precision
+    modulus_LWE = 1 << logQ_ccLWE
+    beta = ccLWE.GetBeta()
+    pLWE3 = int(modulus_LWE / (2 * beta)) # Large precision
+    x1 = [1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0]
+    x2 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
+    if len(x1) < slots:
+        zeros = [0] * (slots - len(x1))
+        x1.extend(zeros)
+        x2.extend(zeros)
+
+    # Encrypt
+    # Encrypted nder small plaintext modulus p = 4 and ciphertext modulus:
+    ctxtsLWE1 = [ccLWE.Encrypt(lwesk, x1[i]) for i in range(slots)]
+    # Encrypted under larger plaintext modulus p = 16 but small ciphertext modulus:
+    ctxtsLWE2 = [ccLWE.Encrypt(lwesk, x1[i], FRESH, pLWE1) for i in range(slots)]
+    # Encrypted under larger plaintext modulus and large ciphertext modulus:
+    ctxtsLWE3 = [ccLWE.Encrypt(lwesk, x2[i], FRESH, pLWE2, modulus_LWE) for i in range(slots)]
+    # Encrypted under large plaintext modulus and large ciphertext modulus:
+    ctxtsLWE4 = [ccLWE.Encrypt(lwesk, x2[i], FRESH, pLWE3, modulus_LWE) for i in range(slots)]
+
+    # Step 5. Perform the scheme switching
+    cTemp = cc.EvalFHEWtoCKKS(ctxtsLWE1, slots, slots)
+
+    print(f"\n---Input x1: {x1} encrypted under p = 4 and Q = {ctxtsLWE1[0].GetModulus()} ---")
+
+    # Step 6. Decrypt
+    plaintextDec = cc.Decrypt(keys.secretKey, cTemp)
+    plaintextDec.SetLength(slots)
+    print(f"Switched CKKS decryption 1: {plaintextDec}")
+
+    # Step 5'. Perform the scheme switching
+    cTemp = cc.EvalFHEWtoCKKS(ctxtsLWE2, slots, slots, pLWE1, 0, pLWE1)
+
+    print(f"\n---Input x1: {x1} encrypted under p = {pLWE1} and Q = {ctxtsLWE2[0].GetModulus()} ---")
+
+    # Step 6'. Decrypt
+    plaintextDec = cc.Decrypt(keys.secretKey, cTemp)
+    plaintextDec.SetLength(slots)
+    print(f"Switched CKKS decryption 2: {plaintextDec}")
+
+    # Step 5''. Perform the scheme switching
+    cTemp = cc.EvalFHEWtoCKKS(ctxtsLWE3, slots, slots, pLWE2, 0, pLWE2)
+
+    print(f"\n---Input x2: {x2} encrypted under p = {pLWE2} and Q = {ctxtsLWE3[0].GetModulus()} ---")
+
+    # Step 6''. Decrypt
+    plaintextDec = cc.Decrypt(keys.secretKey, cTemp)
+    plaintextDec.SetLength(slots)
+    print(f"Switched CKKS decryption 3: {plaintextDec}")
+
+    # Step 5'''. Perform the scheme switching
+    cTemp2 = cc.EvalFHEWtoCKKS(ctxtsLWE4, slots, slots, pLWE3, 0, pLWE3)
+
+    print(f"\n---Input x2: {x2} encrypted under p = {pLWE3} and Q = {ctxtsLWE4[0].GetModulus()} ---")
+
+    # Step 6'''. Decrypt
+    plaintextDec = cc.Decrypt(keys.secretKey, cTemp2)
+    plaintextDec.SetLength(slots)
+    print(f"Switched CKKS decryption 4: {plaintextDec}")
+
+def FloorViaSchemeSwitching():
+    print("\n-----FloorViaSchemeSwitching-----\n")
+    print("Output precision is only wrt the operations in CKKS after switching back.\n")
+
+    # Step 1: Setup CryptoContext for CKKS
+    scTech = FIXEDAUTO
+    multDepth = 3 + 9 + 1  # for r = 3 in FHEWtoCKKS, Chebyshev max depth allowed is 9, 1 more level for postscaling
+    if scTech == FLEXIBLEAUTOEXT:
+        multDepth += 1
+
+    scaleModSize = 50
+    ringDim = 8192
+    sl = HEStd_NotSet
+    slBin = TOY
+    logQ_ccLWE = 23
+    slots = 16  # sparsely-packed
+    batchSize = slots
+
+    parameters = CCParamsCKKSRNS()
+    parameters.SetMultiplicativeDepth(multDepth)
+    parameters.SetScalingModSize(scaleModSize)
+    parameters.SetScalingTechnique(scTech)
+    parameters.SetSecurityLevel(sl)
+    parameters.SetRingDim(ringDim)
+    parameters.SetBatchSize(batchSize)
+
+    cc = GenCryptoContext(parameters)
+
+    # Enable the features that you wish to use
+    cc.Enable(PKE)
+    cc.Enable(KEYSWITCH)
+    cc.Enable(LEVELEDSHE)
+    cc.Enable(ADVANCEDSHE)
+    cc.Enable(SCHEMESWITCH)
+
+    print(f"CKKS scheme is using ring dimension {cc.GetRingDimension()},\n number of slots {slots}, and suports a multiplicative depth of {multDepth}\n")
+
+    # Generate encryption keys.
+    keys = cc.KeyGen()
+
+    # Step 2: Prepare the FHEW cryptocontext and keys for FHEW and scheme switching
+    arbFunc = False
+    FHEWparams = cc.EvalSchemeSwitchingSetup(sl, slBin, arbFunc, logQ_ccLWE, False, slots)
+
+    ccLWE = FHEWparams[0]
+    privateKeyFHEW = FHEWparams[1]
+
+    cc.EvalSchemeSwitchingKeyGen(keys, privateKeyFHEW)
+
+    # Generate bootstrapping key for EvalFloor
+    ccLWE.BTKeyGen(privateKeyFHEW)
+
+    print(f"FHEW scheme is using lattice parameter {ccLWE.Getn()},\n logQ {logQ_ccLWE},\n and modulus q {ccLWE.Getq()}\n")
+
+    # Set the scaling factor to be able to decrypt; the LWE mod switch is performed on the ciphertext at the last level
+    modulus_CKKS_from = cc.GetModulusCKKS()
+
+    modulus_LWE = 1 << logQ_ccLWE
+    beta = ccLWE.GetBeta()
+    pLWE = int(modulus_LWE / (2 * beta))  # Large precision
+
+    scFactor = cc.GetScalingFactorReal(0)
+    if cc.GetScalingTechnique() == FLEXIBLEAUTOEXT:
+        scFactor = cc.GetScalingFactorReal(1)
+    scaleCF = int(modulus_CKKS_from) / (scFactor * pLWE)
+
+    cc.EvalCKKStoFHEWPrecompute(scaleCF)
+
+    # Step 3: Encoding and encryption of inputs
+    # Inputs
+    x1 = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0]
+
+    # Encoding as plaintexts
+    ptxt1 = cc.MakeCKKSPackedPlaintext(x1, 1, 0)#, None)
+
+    # Encrypt the encoded vectors
+    c1 = cc.Encrypt(keys.publicKey, ptxt1)
+
+    # Step 4: Scheme switching from CKKS to FHEW
+    cTemp = cc.EvalCKKStoFHEW(c1)
+
+    # Step 5: Evaluate the floor function
+    bits = 2
+
+    cFloor = [ccLWE.EvalFloor(cTemp[i], bits) for i in range(len(cTemp))]
+
+    print(f"Input x1: {ptxt1.GetRealPackedValue()}")
+    print(f"Expected result for EvalFloor with {bits} bits: ", end="")
+    for i in range(slots):
+        print(int(ptxt1.GetRealPackedValue()[i]) >> bits, end=" ")
+    
+    print(f"\nFHEW decryption p = {pLWE}/(1 << bits) = {pLWE // (1 << bits)}: ", end="")
+    for i in range(len(cFloor)):
+        pFloor = ccLWE.Decrypt(privateKeyFHEW, cFloor[i], pLWE // (1 << bits))
+        print(pFloor, end=" ")
+    print("\n")
+
+    # Step 6: Scheme switching from FHEW to CKKS
+    cTemp2 = cc.EvalFHEWtoCKKS(cFloor, slots, slots, pLWE // (1 << bits), 0, pLWE / (1 << bits))
+
+    plaintextDec2 = cc.Decrypt(keys.secretKey, cTemp2)
+    plaintextDec2.SetLength(slots)
+    print(f"Switched floor decryption modulus_LWE mod {pLWE // (1 << bits)}: {plaintextDec2}")
+
+def FuncViaSchemeSwitching():
+    print("\n-----FuncViaSchemeSwitching-----\n")
+    print("Output precision is only wrt the operations in CKKS after switching back.\n")
+
+    # Step 1: Setup CryptoContext for CKKS
+    multDepth = 9 + 3 + 2  # 1 for CKKS to FHEW, 14 for FHEW to CKKS
+    scaleModSize = 50
+    ringDim = 2048
+    sl = HEStd_NotSet
+    slBin = TOY
+    logQ_ccLWE = 25
+    arbFunc = True
+    slots = 8  # sparsely-packed
+    batchSize = slots
+
+    parameters = CCParamsCKKSRNS()
+    parameters.SetMultiplicativeDepth(multDepth)
+    parameters.SetScalingModSize(scaleModSize)
+    parameters.SetScalingTechnique(FIXEDMANUAL)
+    parameters.SetSecurityLevel(sl)
+    parameters.SetRingDim(ringDim)
+    parameters.SetBatchSize(batchSize)
+
+    cc = GenCryptoContext(parameters)
+
+    # Enable the features that you wish to use
+    cc.Enable(PKE)
+    cc.Enable(KEYSWITCH)
+    cc.Enable(LEVELEDSHE)
+    cc.Enable(ADVANCEDSHE)
+    cc.Enable(SCHEMESWITCH)
+
+    print(f"CKKS scheme is using ring dimension {cc.GetRingDimension()},\n and number of slots {slots}\n")
+
+    # Generate encryption keys.
+    keys = cc.KeyGen()
+
+    # Step 2: Prepare the FHEW cryptocontext and keys for FHEW and scheme switching
+    FHEWparams = cc.EvalSchemeSwitchingSetup(sl, slBin, arbFunc, logQ_ccLWE, False, slots)
+
+    ccLWE = FHEWparams[0]
+    privateKeyFHEW = FHEWparams[1]
+
+    cc.EvalSchemeSwitchingKeyGen(keys, privateKeyFHEW)
+
+    # Generate the bootstrapping keys for EvalFunc in FHEW
+    ccLWE.BTKeyGen(privateKeyFHEW)
+
+    print(f"FHEW scheme is using lattice parameter {ccLWE.Getn()},\n logQ {logQ_ccLWE},\n and modulus q {ccLWE.Getq()}\n")
+
+    # Set the scaling factor to be able to decrypt; the LWE mod switch is performed on the ciphertext at the last level
+    modulus_CKKS_from = cc.GetModulusCKKS()
+    pLWE = ccLWE.GetMaxPlaintextSpace()  # Small precision because GenerateLUTviaFunction needs p < q
+    scFactor = cc.GetScalingFactorReal(0)
+    if cc.GetScalingTechnique() == FLEXIBLEAUTOEXT:
+        scFactor = cc.GetScalingFactorReal(1)
+    scaleCF = modulus_CKKS_from / (scFactor * pLWE)
+
+    cc.EvalCKKStoFHEWPrecompute(scaleCF)
+
+    # Step 3: Initialize the function
+    # Initialize Function f(x) = x^3 + 2x + 1 % p
+    def fp(m, p1):
+        if m < p1:
+            return (m * m * m + 2 * m * m + 1) % p1
+        else:
+            return ((m - p1 / 2) * (m - p1 / 2) * (m - p1 / 2) + 2 * (m - p1 / 2) * (m - p1 / 2) + 1) % p1
+
+    # Generate LUT from function f(x)
+    lut = ccLWE.GenerateLUTviaFunction(fp, pLWE)
+    
+    # Step 4: Encoding and encryption of inputs
+    # Inputs
+    x1 = [0.0, 0.3, 2.0, 4.0, 5.0, 6.0, 7.0, 8.0]
+
+    # Encoding as plaintexts
+    ptxt1 = cc.MakeCKKSPackedPlaintext(x1, 1, 0, None)
+
+    # Encrypt the encoded vectors
+    c1 = cc.Encrypt(keys.publicKey, ptxt1)
+
+    # Step 5: Scheme switching from CKKS to FHEW
+    cTemp = cc.EvalCKKStoFHEW(c1)
+
+    print(f"Input x1: {ptxt1.GetRealPackedValue()}")
+    print("FHEW decryption: ", end="")
+    for i in range(len(cTemp)):
+        result = ccLWE.Decrypt(privateKeyFHEW, cTemp[i], pLWE)
+        print(result, end=" ")
+
+    # Step 6: Evaluate the function
+    cFunc = [ccLWE.EvalFunc(cTemp[i], lut) for i in range(len(cTemp))]
+
+    print("\nExpected result x^3 + 2*x + 1 mod p: ", end="")
+    for i in range(slots):
+        print(fp(int(x1[i]) % pLWE, pLWE), end=" ")
+
+    print(f"\nFHEW decryption mod {pLWE}: ", end="")
+    for i in range(len(cFunc)):
+        pFunc = ccLWE.Decrypt(privateKeyFHEW, cFunc[i], pLWE)
+        print(pFunc, end=" ")
+    print("\n")
+
+    # Step 7: Scheme switching from FHEW to CKKS
+    cTemp2 = cc.EvalFHEWtoCKKS(cFunc, slots, slots, pLWE, 0, pLWE)
+
+    plaintextDec2 = cc.Decrypt(keys.secretKey, cTemp2)
+    plaintextDec2.SetLength(slots)
+    print(f"\nSwitched decryption modulus_LWE mod {pLWE}\nwoeks only for messages << p: {plaintextDec2}")
+
+    # Transform through arcsine
+    cTemp2 = cc.EvalFHEWtoCKKS(cFunc, slots, slots, 4, 0, 2)
+
+    plaintextDec2 = cc.Decrypt(keys.secretKey, cTemp2)
+    plaintextDec2.SetLength(slots)
+
+    print("Arcsin(switched result) * p/2pi gives the correct result if messages are < p/4: ", end="")
+    for i in range(slots):
+        x = max(min(plaintextDec2.GetRealPackedValue()[i], 1.0), -1.0)
+        print(asin(x) * pLWE / (2 * pi), end=" ")
+    print()
+    
+
+def PolyViaSchemeSwitching():
+    print("\n-----PolyViaSchemeSwitching-----\n")
+
+    # Step 1: Setup CryptoContext for CKKS to be switched into
+
+    # A. Specify main parameters
+    scTech = FIXEDAUTO
+    multDepth = 3 + 9 + 1 + 2  # for r = 3 in FHEWtoCKKS, Chebyshev max depth allowed is 9, 1 more level for postscaling, 3 levels for functionality
+    if scTech == FLEXIBLEAUTOEXT:
+        multDepth += 1
+    scaleModSize = 50
+    ringDim = 2048
+    sl = HEStd_NotSet
+    slBin = TOY
+    logQ_ccLWE = 25
+
+    slots = 16  # sparsely-packed
+    batchSize = slots
+
+    # Create encryption parameters
+    parameters = CCParamsCKKSRNS()
+    parameters.SetMultiplicativeDepth(multDepth)
+    parameters.SetScalingModSize(scaleModSize)
+    parameters.SetScalingTechnique(scTech)
+    parameters.SetSecurityLevel(sl)
+    parameters.SetRingDim(ringDim)
+    parameters.SetBatchSize(batchSize)
+
+    cc = GenCryptoContext(parameters)
+
+    # Enable the features that you wish to use
+    cc.Enable(PKE)
+    cc.Enable(KEYSWITCH)
+    cc.Enable(LEVELEDSHE)
+    cc.Enable(ADVANCEDSHE)
+    cc.Enable(SCHEMESWITCH)
+
+    print(f"CKKS scheme is using ring dimension {cc.GetRingDimension()},\n number of slots {slots}, and suports a multiplicative depth of {multDepth}\n")
+
+    # Generate encryption keys
+    keys = cc.KeyGen()
+
+    # Step 2: Prepare the FHEW cryptocontext and keys for FHEW and scheme switching
+    FHEWparams = cc.EvalSchemeSwitchingSetup(sl, slBin, False, logQ_ccLWE, False, slots)
+
+    ccLWE = FHEWparams[0]
+    privateKeyFHEW = FHEWparams[1]
+
+    # Step 3: Precompute the necessary keys and information for switching from FHEW to CKKS
+    cc.EvalSchemeSwitchingKeyGen(keys, privateKeyFHEW, slots)
+
+    print(f"FHEW scheme is using lattice parameter {ccLWE.Getn()},\n logQ {logQ_ccLWE},\n and modulus q {ccLWE.Getq()}\n")
+
+    pLWE1 = ccLWE.GetMaxPlaintextSpace()  # Small precision
+    modulus_LWE = 1 << logQ_ccLWE
+    beta = ccLWE.GetBeta()
+    pLWE2 = modulus_LWE // (2 * beta)  # Large precision
+
+    modulus_from_CKKS = cc.GetModulusCKKS()
+    scFactor = cc.GetScalingFactorReal(0)
+    if cc.GetScalingTechnique() == FLEXIBLEAUTOEXT:
+        scFactor = cc.GetScalingFactorReal(1)
+    scale1 = modulus_from_CKKS // (scFactor * pLWE1)
+    scale2 = modulus_from_CKKS // (scFactor * pLWE2)
+
+    # Generate keys for the CKKS intermediate computation
+    cc.EvalMultKeyGen(keys.secretKey)
+    cc.EvalRotateKeyGen(keys.secretKey, [1,2])
+
+    # Step 4: Encoding and encryption of inputs
+    # For correct CKKS decryption, the messages have to be much smaller than the FHEW plaintext modulus!
+    # Inputs
+    x1 = [1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0]
+    x2 = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
+
+    x1Rot = RotateInt(x1,1)
+    x1Rot = [x1Rot[i] + x1[i] for i in range(len(x1))]
+    x1Int = [int(round(0.25 * elem * elem) % pLWE1) for elem in x1Rot]
+
+    x2Rot = RotateInt(x2,2)
+    x2Rot = [x2Rot[i] + x2[i] for i in range(len(x2))]
+    x2Int = [int(round(0.25 * elem * elem) % pLWE2) for elem in x2Rot]
+
+    # Encrypt
+    # encrypted under small plantext modulus p = 4 and ciphertext modulus
+    ctxtsLWE1 = [ccLWE.Encrypt(privateKeyFHEW, x1[i]) for i in range(slots)]  
+    # encrypted under large plaintext modulus and large ciphertext modulus
+    ctxtsLWE2 = [ccLWE.Encrypt(privateKeyFHEW, x2[i], FRESH, pLWE2, modulus_LWE) for i in range(slots)]  
+
+    # Step 5. Perform the scheme switching
+    cTemp = cc.EvalFHEWtoCKKS(ctxtsLWE1, slots, slots)
+
+    print(f"\n---Input x1: {x1} encrypted under p = 4 and Q = {ctxtsLWE1[0].GetModulus()} ---\n")
+    print(f"round( 0.5 * (x1 + rot(x1,1) )^2 ): {x1Int}\n")
+
+    # Step 6. Perform the desired computation in CKKS
+    cPoly = cc.EvalAdd(cTemp, cc.EvalRotate(cTemp, 1))
+    cPoly = cc.EvalMult(cc.EvalMult(cPoly, cPoly), 0.25)
+
+    # Perform the precomputation for switching back to CKKS
+    cc.EvalCKKStoFHEWPrecompute(scale1)
+
+    # Tranform the ciphertext from CKKS to FHEW
+    cTemp1 = cc.EvalCKKStoFHEW(cPoly, slots)
+
+    print(f"\nFHEW decryption with plaintext modulus {pLWE1}: ", end="")
+    for i in range(len(cTemp1)):
+        result = ccLWE.Decrypt(privateKeyFHEW, cTemp1[i], pLWE1)
+        print(result, end=" ")
+    print("\n")
+
+    # Step 5'. Perform the scheme switching 
+    cTemp = cc.EvalFHEWtoCKKS(ctxtsLWE2, slots, slots, pLWE2, 0, pLWE2)
+
+    print(f"\n---Input x2: {x2} encrypted under p = {pLWE2} and Q = {ctxtsLWE2[0].GetModulus()} ---\n")
+    print(f"round( 0.5 * (x2 + rot(x2,2) )^2 ): {x2Int}\n")
+
+    # Step 6'. Perform the desired computation in CKKS
+    cPoly = cc.EvalAdd(cTemp, cc.EvalRotate(cTemp, 2))
+    cPoly = cc.EvalMult(cc.EvalMult(cPoly, cPoly), 0.25)
+
+    # Perform the precomputation for switching back to CKKS
+    cc.EvalCKKStoFHEWPrecompute(scale2)
+
+    # Tranform the ciphertext from CKKS to FHEW
+    cTemp2 = cc.EvalCKKStoFHEW(cPoly, slots)
+
+    print(f"\nFHEW decryption with plaintext modulus {pLWE2}: ", end="")
+    for i in range(len(cTemp2)):
+        result = ccLWE.Decrypt(privateKeyFHEW, cTemp2[i], pLWE2)
+        print(result, end=" ")
+    print("\n")
+
+
+
+
+
+
+def ComparisonViaSchemeSwitching():
+    print("\n-----ComparisonViaSchemeSwitching-----\n")
+    print("Output precision is only wrt the operations in CKKS after switching back.\n")
+
+    # Step 1: Setup CryptoContext for CKKS
+    scTech = FIXEDAUTO
+    multDepth = 17
+    if scTech == FLEXIBLEAUTOEXT:
+        multDepth += 1
+
+    scaleModSize = 50
+    firstModSize = 60
+    ringDim = 8192
+    sl = HEStd_NotSet
+    slBin = TOY
+    logQ_ccLWE = 25
+    slots = 16  # sparsely-packed
+    batchSize = slots
+
+    parameters = CCParamsCKKSRNS()
+    parameters.SetMultiplicativeDepth(multDepth)
+    parameters.SetScalingModSize(scaleModSize)
+    parameters.SetFirstModSize(firstModSize)
+    parameters.SetScalingTechnique(scTech)
+    parameters.SetSecurityLevel(sl)
+    parameters.SetRingDim(ringDim)
+    parameters.SetBatchSize(batchSize)
+    parameters.SetSecretKeyDist(UNIFORM_TERNARY)
+    parameters.SetKeySwitchTechnique(HYBRID)
+    parameters.SetNumLargeDigits(3)
+
+    cc = GenCryptoContext(parameters)
+
+    # Enable the features that you wish to use
+    cc.Enable(PKE)
+    cc.Enable(KEYSWITCH)
+    cc.Enable(LEVELEDSHE)
+    cc.Enable(ADVANCEDSHE)
+    cc.Enable(SCHEMESWITCH)
+
+    print(f"CKKS scheme is using ring dimension {cc.GetRingDimension()},\n and number of slots {slots}\n and supports a multiplicative depth of {multDepth}\n")
+
+    # Generate encryption keys.
+    keys = cc.KeyGen()
+
+    # Step 2: Prepare the FHEW cryptocontext and keys for FHEW and scheme switching
+    FHEWparams = cc.EvalSchemeSwitchingSetup(sl, slBin, False, logQ_ccLWE, False, slots)
+
+    ccLWE = FHEWparams[0]
+    privateKeyFHEW = FHEWparams[1]
+    ccLWE.BTKeyGen(privateKeyFHEW)
+
+    cc.EvalSchemeSwitchingKeyGen(keys, privateKeyFHEW)
+
+    print(f"FHEW scheme is using lattice parameter {ccLWE.Getn()},\n logQ {logQ_ccLWE},\n and modulus q {ccLWE.Getq()}\n")
+
+    # Set the scaling factor to be able to decrypt; the LWE mod switch is performed on the ciphertext at the last level
+    pLWE1 = ccLWE.GetMaxPlaintextSpace()  # Small precision
+    modulus_LWE = 1 << logQ_ccLWE
+    beta = ccLWE.GetBeta()
+    pLWE2 = modulus_LWE // (2 * beta)  # Large precision
+
+    scaleSignFHEW = 1
+    init_level = 0
+    if cc.GetScalingTechnique() == FLEXIBLEAUTOEXT:
+        init_level = 1 
+
+    cc.EvalCompareSwitchPrecompute(pLWE2, init_level, scaleSignFHEW)
+
+    # Step 3: Encoding and encryption of inputs
+    x1 = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0]
+    x2 = [5.25] * slots
+
+    ptxt1 = cc.MakeCKKSPackedPlaintext(x1, 1, 0, None, slots)
+    ptxt2 = cc.MakeCKKSPackedPlaintext(x2, 1, 0, None, slots)
+
+    c1 = cc.Encrypt(keys.publicKey, ptxt1)
+    c2 = cc.Encrypt(keys.publicKey, ptxt2)
+
+    cDiff = cc.EvalSub(c1, c2)
+
+    # Step 4: CKKS to FHEW switching and sign evaluation to test correctness
+    pDiff = cc.Decrypt(keys.secretKey, cDiff)
+    pDiff.SetLength(slots)
+
+    print("Difference of inputs: ", end="")
+    for i in range(slots):
+        print(pDiff.GetRealPackedValue()[i], end=" ")
+
+    eps = 0.0001
+    print("\nExpected sign result from CKKS: ", end="")
+    for i in range(slots):
+        print(int(round(pDiff.GetRealPackedValue()[i] / eps) * eps < 0), end=" ")
+    print()
+
+    LWECiphertexts = cc.EvalCKKStoFHEW(cDiff, slots)
+
+    print("\nFHEW decryption with plaintext modulus ", pLWE2, ": ", end="")
+    for i in range(len(LWECiphertexts)):
+        plainLWE = ccLWE.Decrypt(privateKeyFHEW, LWECiphertexts[i], pLWE2)
+        print(plainLWE, end=" ")
+
+    print("\nExpected sign result in FHEW with plaintext modulus ", pLWE2, " and scale ", scaleSignFHEW, ": ", end="")
+    for i in range(slots):
+        print((int(round(pDiff.GetRealPackedValue()[i] * scaleSignFHEW)) % pLWE2 - pLWE2 / 2.0 >= 0), end=" ")
+    print()
+
+    print("Obtained sign result in FHEW with plaintext modulus ", pLWE2, " and scale ", scaleSignFHEW, ": ", end="")
+    LWESign = [None] * len(LWECiphertexts)
+    for i in range(len(LWECiphertexts)):
+        LWESign[i] = ccLWE.EvalSign(LWECiphertexts[i])
+        plainLWE = ccLWE.Decrypt(privateKeyFHEW, LWESign[i], 2)
+        print(plainLWE, end=" ")
+    print()
+
+    # Step 5'': Direct comparison via CKKS->FHEW->CKKS
+    cResult = cc.EvalCompareSchemeSwitching(c1, c2, slots, 0, scaleSignFHEW)
+    plaintextDec3 = cc.Decrypt(keys.secretKey, cResult)
+    plaintextDec3.SetLength(slots)
+    print(f"Decrypted swutched result: {plaintextDec3}\n")
+   
+
+def ArgminViaSchemeSwitching():
+    print("\n-----ArgminViaSchemeSwitching-----\n")
+    print("Output precision is only wrt the operations in CKKS after switching back\n")
+
+    # Step 1: Setup CryptoContext for CKKS
+    scaleModSize = 50
+    firstModSize = 60
+    ringDim = 8192
+    sl = HEStd_NotSet
+    slBin = TOY
+    logQ_ccLWE = 25
+    arbFunc = False
+    oneHot = True  # Change to false if the output should not be one-hot encoded
+
+    slots = 16  # sparsely-packed
+    batchSize = slots
+    numValues = 16
+    scTech = FIXEDMANUAL
+    multDepth = 9 + 3 + 1 + int(log2(numValues))  # 13 for FHEW to CKKS, log2(numValues) for argmin
+    if scTech == FLEXIBLEAUTOEXT:
+        multDepth += 1
+
+    parameters = CCParamsCKKSRNS()
+    parameters.SetMultiplicativeDepth(multDepth)
+    parameters.SetScalingModSize(scaleModSize)
+    parameters.SetFirstModSize(firstModSize)
+    parameters.SetScalingTechnique(scTech)
+    parameters.SetSecurityLevel(sl)
+    parameters.SetRingDim(ringDim)
+    parameters.SetBatchSize(batchSize)
+
+    cc = GenCryptoContext(parameters)
+
+    # Enable the features that you wish to use
+    cc.Enable(PKE)
+    cc.Enable(KEYSWITCH)
+    cc.Enable(LEVELEDSHE)
+    cc.Enable(ADVANCEDSHE)
+    cc.Enable(SCHEMESWITCH)
+
+    print("CKKS scheme is using ring dimension ", cc.GetRingDimension())
+    print(", and number of slots ", slots, ", and supports a depth of ", multDepth, "\n")
+
+    # Generate encryption keys
+    keys = cc.KeyGen()
+
+    # Step 2: Prepare the FHEW cryptocontext and keys for FHEW and scheme switching
+    FHEWparams = cc.EvalSchemeSwitchingSetup(sl, slBin, arbFunc, logQ_ccLWE, False, slots)
+    ccLWE = FHEWparams[0]
+    privateKeyFHEW = FHEWparams[1]
+
+    cc.EvalSchemeSwitchingKeyGen(keys, privateKeyFHEW, numValues, oneHot)
+
+    print(f"FHEW scheme is using lattice parameter {ccLWE.Getn()},\n logQ {logQ_ccLWE},\n and modulus q {ccLWE.Getq()}\n")
+
+    # Scale the inputs to ensure their difference is correctly represented after switching to FHEW
+    scaleSign = 512
+    modulus_LWE = 1 << logQ_ccLWE
+    beta = ccLWE.GetBeta()
+    pLWE = modulus_LWE // (2 * beta)  # Large precision
+
+    init_level = 0
+    if cc.GetScalingTechnique() == FLEXIBLEAUTOEXT:
+        init_level = 1
+    cc.EvalCompareSwitchPrecompute(pLWE, init_level, scaleSign)
+
+    # Step 3: Encoding and encryption of inputs
+    x1 = [-1.125, -1.12, 5.0, 6.0, -1.0, 2.0, 8.0, -1.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.25, 15.30]
+
+    print("Expected minimum value ", min(x1), " at location ", x1.index(min(x1)))
+    print("Expected maximum value ", max(x1), " at location ", x1.index(max(x1)))
+
+    ptxt1 = cc.MakeCKKSPackedPlaintext(x1)
+
+    c1 = cc.Encrypt(keys.publicKey, ptxt1)
+
+    # Step 4: Argmin evaluation
+    result = cc.EvalMinSchemeSwitching(c1, keys.publicKey, numValues, slots, oneHot)
+
+    ptxtMin = cc.Decrypt(keys.secretKey, result[0])
+    ptxtMin.SetLength(1)
+    print("Minimum value: ", ptxtMin)
+
+    ptxtMin = cc.Decrypt(keys.secretKey, result[1])
+    if oneHot:
+        ptxtMin.SetLength(numValues)
+        print("Argmin indicator vector: ", ptxtMin)
+    else:
+        ptxtMin.SetLength(1)
+        print("Argmin: ", ptxtMin)
+
+    result = cc.EvalMaxSchemeSwitching(c1, keys.publicKey, numValues, slots, oneHot)
+
+    ptxtMax = cc.Decrypt(keys.secretKey, result[0])
+    ptxtMax.SetLength(1)
+    print("Maximum value: ", ptxtMax)
+
+    ptxtMax = cc.Decrypt(keys.secretKey, result[1])
+    if oneHot:
+        ptxtMax.SetLength(numValues)
+        print("Argmax indicator vector: ", ptxtMax)
+    else:
+        ptxtMax.SetLength(1)
+        print("Argmax: ", ptxtMax)
+
+def ArgminViaSchemeSwitchingAlt():
+    print("\n-----ArgminViaSchemeSwitchingAlt-----\n")
+    print("Output precision is only wrt the operations in CKKS after switching back\n")
+
+    # Step 1: Setup CryptoContext for CKKS
+    scaleModSize = 50
+    firstModSize = 60
+    ringDim = 8192
+    sl = HEStd_NotSet
+    slBin = TOY
+    logQ_ccLWE = 25
+    arbFunc = False
+    oneHot = True
+    alt = True
+
+    slots = 16
+    batchSize = slots
+    numValues = 16
+    scTech = FIXEDAUTO
+    multDepth = 9 + 3 + 1 + int(log2(numValues))
+
+    parameters = CCParamsCKKSRNS()
+    parameters.SetMultiplicativeDepth(multDepth)
+    parameters.SetScalingModSize(scaleModSize)
+    parameters.SetFirstModSize(firstModSize)
+    parameters.SetScalingTechnique(scTech)
+    parameters.SetSecurityLevel(sl)
+    parameters.SetRingDim(ringDim)
+    parameters.SetBatchSize(batchSize)
+
+    cc = GenCryptoContext(parameters)
+
+    cc.Enable(PKE)
+    cc.Enable(KEYSWITCH)
+    cc.Enable(LEVELEDSHE)
+    cc.Enable(ADVANCEDSHE)
+    cc.Enable(SCHEMESWITCH)
+
+    print(f"CKKS scheme is using ring dimension {cc.GetRingDimension()},")
+    print(f"number of slots {slots}, and supports a multiplicative depth of {multDepth}\n")
+
+    keys = cc.KeyGen()
+
+    # Step 2: Prepare the FHEW cryptocontext and keys for FHEW and scheme switching
+    FHEWparams = cc.EvalSchemeSwitchingSetup(sl, slBin, arbFunc, logQ_ccLWE, False, slots)
+    ccLWE = FHEWparams[0]
+    privateKeyFHEW = FHEWparams[1]
+
+    cc.EvalSchemeSwitchingKeyGen(keys, privateKeyFHEW, numValues, oneHot, alt)
+
+    print(f"FHEW scheme is using lattice parameter {ccLWE.Getn()},\n logQ {logQ_ccLWE},\n and modulus q {ccLWE.Getq()}\n")
+
+    scaleSign = 512
+    modulus_LWE = 1 << logQ_ccLWE
+    beta = ccLWE.GetBeta()
+    pLWE = modulus_LWE // (2 * beta)
+
+    init_level = 0
+    if cc.GetScalingTechnique() == FLEXIBLEAUTOEXT:
+        init_level = 1
+    cc.EvalCompareSwitchPrecompute(pLWE, init_level, scaleSign)
+    # But we can also include the scaleSign in pLWE (here we use the fact both pLWE and scaleSign are powers of two)
+    # cc.EvalCompareSwitchPrecompute(pLWE / scaleSign, init_level, 1)
+
+    # Step 3: Encoding and encryption of inputs
+
+    # Inputs
+    x1 = [-1.125, -1.12, 5.0, 6.0, -1.0, 2.0, 8.0, -1.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.25, 15.30]
+
+    print("Expected minimum value ", min(x1), " at location ", x1.index(min(x1)))
+    print("Expected maximum value ", max(x1), " at location ", x1.index(max(x1)))
+
+    # Encoding as plaintexts
+    ptxt1 = cc.MakeCKKSPackedPlaintext(x1)  # Only if we set batchsize
+    # ptxt1 = cc.MakeCKKSPackedPlaintext(x1, 1, 0, None, slots) # If batchsize is not set
+
+    # Encrypt the encoded vectors
+    c1 = cc.Encrypt(keys.publicKey, ptxt1)
+
+    # Step 4: Argmin evaluation
+    result = cc.EvalMinSchemeSwitchingAlt(c1, keys.publicKey, numValues, slots, oneHot)
+
+    ptxtMin = cc.Decrypt(keys.secretKey, result[0])
+    ptxtMin.SetLength(1)
+    print("Minimum value: ", ptxtMin)
+
+    ptxtMin = cc.Decrypt(keys.secretKey, result[1])
+    if oneHot:
+        ptxtMin.SetLength(numValues)
+        print("Argmin indicator vector: ", ptxtMin)
+    else:
+        ptxtMin.SetLength(1)
+        print("Argmin: ", ptxtMin)
+
+    result = cc.EvalMaxSchemeSwitchingAlt(c1, keys.publicKey, numValues, slots, oneHot)
+
+    ptxtMax = cc.Decrypt(keys.secretKey, result[0])
+    ptxtMax.SetLength(1)
+    print("Maximum value: ", ptxtMax)
+
+    ptxtMax = cc.Decrypt(keys.secretKey, result[1])
+    if oneHot:
+        ptxtMax.SetLength(numValues)
+        print("Argmax indicator vector: ", ptxtMax)
+    else:
+        ptxtMax.SetLength(1)
+        print("Argmax: ", ptxtMax)
+
+def ArgminViaSchemeSwitchingUnit():
+    print("\n-----ArgminViaSchemeSwitchingUnit-----\n")
+    print("Output precision is only wrt the operations in CKKS after switching back\n")
+
+    # Step 1: Setup CryptoContext for CKKS
+    scaleModSize = 50
+    firstModSize = 60
+    ringDim = 8192
+    sl = HEStd_NotSet
+    slBin = TOY
+    logQ_ccLWE = 25
+    arbFunc = False
+    oneHot = True
+
+    slots = 32  # sparsely-packed
+    batchSize = slots
+    numValues = 32
+    scTech = FLEXIBLEAUTOEXT
+    multDepth = 9 + 3 + 1 + int(log2(numValues))  # 1 for CKKS to FHEW, 13 for FHEW to CKKS, log2(numValues) for argmin
+    if scTech == FLEXIBLEAUTOEXT:
+        multDepth += 1
+
+    parameters = CCParamsCKKSRNS()
+    parameters.SetMultiplicativeDepth(multDepth)
+    parameters.SetScalingModSize(scaleModSize)
+    parameters.SetFirstModSize(firstModSize)
+    parameters.SetScalingTechnique(scTech)
+    parameters.SetSecurityLevel(sl)
+    parameters.SetRingDim(ringDim)
+    parameters.SetBatchSize(batchSize)
+
+    cc = GenCryptoContext(parameters)
+
+    # Enable the features that you wish to use
+    cc.Enable(PKE)
+    cc.Enable(KEYSWITCH)
+    cc.Enable(LEVELEDSHE)
+    cc.Enable(ADVANCEDSHE)
+    cc.Enable(SCHEMESWITCH)
+    cc.Enable(FHE)
+
+    print(f"CKKS scheme is using ring dimension {cc.GetRingDimension()},")
+    print(f"number of slots {slots}, and supports a multiplicative depth of {multDepth}\n")
+
+    # Generate encryption keys.
+    keys = cc.KeyGen()
+
+    # Step 2: Prepare the FHEW cryptocontext and keys for FHEW and scheme switching
+    FHEWparams = cc.EvalSchemeSwitchingSetup(sl, slBin, arbFunc, logQ_ccLWE, False, slots)
+
+    ccLWE = FHEWparams[0]
+    privateKeyFHEW = FHEWparams[1]
+
+    cc.EvalSchemeSwitchingKeyGen(keys, privateKeyFHEW, numValues, oneHot)
+
+    print(f"FHEW scheme is using lattice parameter {ccLWE.Getn()},\n logQ {logQ_ccLWE},\n and modulus q {ccLWE.Getq()}\n")
+
+    init_level = 0
+
+    if cc.GetScalingTechnique() == FLEXIBLEAUTOEXT:
+        init_level = 1
+
+    # Here we assume the message does not need scaling, as they are in the unit circle.
+    cc.EvalCompareSwitchPrecompute(1, init_level, 1)
+
+    # Step 3: Encoding and encryption of inputs
+
+    # Inputs
+    x1 = [-1.125, -1.12, 5.0, 6.0, -1.0, 2.0, 8.0, -1.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.25, 15.30]
+    if len(x1) < slots:
+        x1.extend([0] * (slots - len(x1)))
+    print("Input: ", x1)
+
+    p = 1 << (firstModSize - scaleModSize - 1)
+    x1 = [elem / (2 * p) for elem in x1]
+
+    print("Input scaled: ", x1)
+    print("Expected minimum value ", min(x1), " at location ", x1.index(min(x1)))
+    print("Expected maximum value ", max(x1), " at location ", x1.index(max(x1)))
+
+    # Encoding as plaintexts
+    ptxt1 = cc.MakeCKKSPackedPlaintext(x1)
+
+    # Encrypt the encoded vectors
+    c1 = cc.Encrypt(keys.publicKey, ptxt1)
+
+    # Step 4: Argmin evaluation
+    result = cc.EvalMinSchemeSwitching(c1, keys.publicKey, numValues, slots, oneHot)
+
+    ptxtMin = cc.Decrypt(keys.secretKey, result[0])
+    ptxtMin.SetLength(1)
+    print("Minimum value: ", ptxtMin)
+
+    ptxtMin = cc.Decrypt(keys.secretKey, result[1])
+    if oneHot:
+        ptxtMin.SetLength(numValues)
+        print("Argmin indicator vector: ", ptxtMin)
+    else:
+        ptxtMin.SetLength(1)
+        print("Argmin: ", ptxtMin)
+
+    result = cc.EvalMaxSchemeSwitching(c1, keys.publicKey, numValues, slots, oneHot)
+
+    ptxtMax = cc.Decrypt(keys.secretKey, result[0])
+    ptxtMax.SetLength(1)
+    print("Maximum value: ", ptxtMax)
+
+    ptxtMax = cc.Decrypt(keys.secretKey, result[1])
+    if oneHot:
+        ptxtMax.SetLength(numValues)
+        print("Argmax indicator vector: ", ptxtMax)
+    else:
+        ptxtMax.SetLength(1)
+        print("Argmax: ", ptxtMax)
+
+def ArgminViaSchemeSwitchingAltUnit():
+    print("\n-----ArgminViaSchemeSwitchingAltUnit-----\n")
+    print("Output precision is only wrt the operations in CKKS after switching back\n")
+
+    # Step 1: Setup CryptoContext for CKKS
+    scaleModSize = 50
+    firstModSize = 60
+    ringDim = 8192
+    sl = HEStd_NotSet
+    slBin = TOY
+    logQ_ccLWE = 25
+    arbFunc = False
+    oneHot = True
+    alt = True  # alternative mode of argmin which has fewer rotation keys and does more operations in FHEW than in CKKS
+
+    slots = 32  # sparsely-packed
+    batchSize = slots
+    numValues = 32
+    scTech = FLEXIBLEAUTOEXT
+    multDepth = 9 + 3 + 1 + int(log2(numValues))  # 1 for CKKS to FHEW, 13 for FHEW to CKKS, log2(numValues) for argmin
+    if scTech == FLEXIBLEAUTOEXT:
+        multDepth += 1
+
+    parameters = CCParamsCKKSRNS()
+    parameters.SetMultiplicativeDepth(multDepth)
+    parameters.SetScalingModSize(scaleModSize)
+    parameters.SetFirstModSize(firstModSize)
+    parameters.SetScalingTechnique(scTech)
+    parameters.SetSecurityLevel(sl)
+    parameters.SetRingDim(ringDim)
+    parameters.SetBatchSize(batchSize)
+
+    cc = GenCryptoContext(parameters)
+
+    # Enable the features that you wish to use
+    cc.Enable(PKE)
+    cc.Enable(KEYSWITCH)
+    cc.Enable(LEVELEDSHE)
+    cc.Enable(ADVANCEDSHE)
+    cc.Enable(SCHEMESWITCH)
+    cc.Enable(FHE)
+
+    print(f"CKKS scheme is using ring dimension {cc.GetRingDimension()},")
+    print(f"number of slots {slots}, and supports a multiplicative depth of {multDepth}\n")
+
+    # Generate encryption keys.
+    keys = cc.KeyGen()
+
+    # Step 2: Prepare the FHEW cryptocontext and keys for FHEW and scheme switching
+    FHEWparams = cc.EvalSchemeSwitchingSetup(sl, slBin, arbFunc, logQ_ccLWE, False, slots)
+
+    ccLWE = FHEWparams[0]
+    privateKeyFHEW = FHEWparams[1]
+
+    cc.EvalSchemeSwitchingKeyGen(keys, privateKeyFHEW, numValues, oneHot, alt)
+
+    print(f"FHEW scheme is using lattice parameter {ccLWE.Getn()},\n logQ {logQ_ccLWE},\n and modulus q {ccLWE.Getq()}\n")
+
+    init_level = 0
+
+    if cc.GetScalingTechnique() == FLEXIBLEAUTOEXT:
+        init_level = 1
+    # Here we assume the message does not need scaling, as they are in the unit circle.
+    cc.EvalCompareSwitchPrecompute(1, init_level, 1)
+
+    # Step 3: Encoding and encryption of inputs
+    # Inputs
+    x1 = [-1.125, -1.12, 5.0, 6.0, -1.0, 2.0, 8.0, -1.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.25, 15.30]
+    if len(x1) < slots:
+        zeros = [0] * (slots - len(x1))
+        x1.extend(zeros)
+    print("Input: ", x1)
+
+    p = 1 << (firstModSize - scaleModSize - 1)
+    x1 = [elem / (2 * p) for elem in x1]
+
+    print("Input scaled: ", x1)
+    print("Expected minimum value ", min(x1), " at location ", x1.index(min(x1)))
+    print("Expected maximum value ", max(x1), " at location ", x1.index(max(x1)))
+
+    # Encoding as plaintexts
+    ptxt1 = cc.MakeCKKSPackedPlaintext(x1)
+
+    # Encrypt the encoded vectors
+    c1 = cc.Encrypt(keys.publicKey, ptxt1)
+
+    # Step 4: Argmin evaluation
+    result = cc.EvalMinSchemeSwitchingAlt(c1, keys.publicKey, numValues, slots, oneHot)
+
+    ptxtMin = cc.Decrypt(keys.secretKey, result[0])
+    ptxtMin.SetLength(1)
+    print("Minimum value: ", ptxtMin)
+
+    ptxtMin = cc.Decrypt(keys.secretKey, result[1])
+    if oneHot:
+        ptxtMin.SetLength(numValues)
+        print("Argmin indicator vector: ", ptxtMin)
+    else:
+        ptxtMin.SetLength(1)
+        print("Argmin: ", ptxtMin)
+
+    result = cc.EvalMaxSchemeSwitchingAlt(c1, keys.publicKey, numValues, slots, oneHot)
+
+    ptxtMax = cc.Decrypt(keys.secretKey, result[0])
+    ptxtMax.SetLength(1)
+    print("Maximum value: ", ptxtMax)
+
+    ptxtMax = cc.Decrypt(keys.secretKey, result[1])
+    if oneHot:
+        ptxtMax.SetLength(numValues)
+        print("Argmax indicator vector: ", ptxtMax)
+    else:
+        ptxtMax.SetLength(1)
+        print("Argmax: ", ptxtMax)
+
+# Helper functions:
+def ReduceRotation(index, slots):
+    islots = int(slots)
+
+    # if slots is a power of 2
+    if (slots & (slots - 1)) == 0:
+        n = int(log2(slots))
+        if index >= 0:
+            return index - ((index >> n) << n)
+        return index + islots + ((int(abs(index)) >> n) << n)
+    return (islots + index % islots) % islots
+
+def RotateInt(a, index):
+    slots = len(a)
+
+    result = [0]*slots
+
+    if index < 0 or index > slots:
+        index = ReduceRotation(index, slots)
+
+    if index == 0:
+        result = a.copy()
+    else:
+        # two cases: i+index <= slots and i+index > slots
+        for i in range(0, slots - index):
+            result[i] = a[i + index]
+        for i in range(slots - index, slots):
+            result[i] = a[i + index - slots]
+
+    return result
+
+if __name__ == "__main__":
+    main()

+ 187 - 0
examples/pke/tckks-interactive-mp-bootstrapping-Chebyschev.py

@@ -0,0 +1,187 @@
+from openfhe import *
+
+def main():
+    print("Interactive (3P) Bootstrapping Ciphertext [Chebyshev] (TCKKS) started ...")
+
+    # Same test with different rescaling techniques in CKKS
+    TCKKSCollectiveBoot(FIXEDMANUAL)
+    TCKKSCollectiveBoot(FIXEDAUTO)
+    TCKKSCollectiveBoot(FLEXIBLEAUTO)
+    TCKKSCollectiveBoot(FLEXIBLEAUTOEXT)
+
+    print("Interactive (3P) Bootstrapping Ciphertext [Chebyshev] (TCKKS) terminated gracefully!")
+
+
+
+def checkApproximateEquality(a, b, vectorSize, epsilon):
+    allTrue = [1] * vectorSize
+    tmp = [abs(a[i] - b[i]) <= epsilon for i in range(vectorSize)]
+    if tmp != allTrue:
+        print("IntMPBoot - Ctxt Chebyshev Failed:")
+        print(f"- is diff <= eps?: {tmp}")
+    else:
+        print("SUCCESSFUL Bootstrapping!")
+
+def TCKKSCollectiveBoot(scaleTech):
+    if scaleTech not in [FIXEDMANUAL, FIXEDAUTO, FLEXIBLEAUTO, FLEXIBLEAUTOEXT]:
+        errMsg = "ERROR: Scaling technique is not supported!"
+        raise Exception(errMsg)
+
+    parameters = CCParamsCKKSRNS()
+
+    secretKeyDist = UNIFORM_TERNARY
+    parameters.SetSecretKeyDist(secretKeyDist)
+
+    parameters.SetSecurityLevel(HEStd_128_classic)
+
+    dcrtBits = 50
+    firstMod = 60
+
+    parameters.SetScalingModSize(dcrtBits)
+    parameters.SetScalingTechnique(scaleTech)
+    parameters.SetFirstModSize(firstMod)
+
+    multiplicativeDepth = 10  # Adjust according to your requirements
+    parameters.SetMultiplicativeDepth(multiplicativeDepth)
+    parameters.SetKeySwitchTechnique(HYBRID)
+
+    batchSize = 16  # Adjust batch size if needed
+    parameters.SetBatchSize(batchSize)
+
+    compressionLevel = COMPRESSION_LEVEL.COMPACT  # or COMPRESSION_LEVEL.SLACK
+    parameters.SetInteractiveBootCompressionLevel(compressionLevel)
+
+    cryptoContext = GenCryptoContext(parameters)
+    cryptoContext.Enable(PKE)
+    cryptoContext.Enable(KEYSWITCH)
+    cryptoContext.Enable(LEVELEDSHE)
+    cryptoContext.Enable(ADVANCEDSHE)
+    cryptoContext.Enable(MULTIPARTY)
+
+    ringDim = cryptoContext.GetRingDimension()
+    maxNumSlots = ringDim // 2
+
+    print(f"TCKKS scheme is using ring dimension {ringDim}")
+    print(f"TCKKS scheme number of slots         {batchSize}")
+    print(f"TCKKS scheme max number of slots     {maxNumSlots}")
+    print(f"TCKKS example with Scaling Technique {scaleTech}")
+
+    numParties = 3
+
+    print("\n===========================IntMPBoot protocol parameters===========================\n")
+    print(f"number of parties: {numParties}\n")
+    print("===============================================================\n")
+
+    # Round 1 (party A)
+    kp1 = cryptoContext.KeyGen()
+
+    # Generate evalmult key part for A
+    evalMultKey = cryptoContext.KeySwitchGen(kp1.secretKey, kp1.secretKey)
+
+    # Generate evalsum key part for A
+    cryptoContext.EvalSumKeyGen(kp1.secretKey)
+    evalSumKeys = cryptoContext.GetEvalSumKeyMap(kp1.secretKey.GetKeyTag())
+
+    # Round 2 (party B)
+    kp2 = cryptoContext.MultipartyKeyGen(kp1.publicKey)
+    evalMultKey2 = cryptoContext.MultiKeySwitchGen(kp2.secretKey, kp2.secretKey, evalMultKey)
+    evalMultAB = cryptoContext.MultiAddEvalKeys(evalMultKey, evalMultKey2, kp2.publicKey.GetKeyTag())
+    evalMultBAB = cryptoContext.MultiMultEvalKey(kp2.secretKey, evalMultAB, kp2.publicKey.GetKeyTag())
+    evalSumKeysB = cryptoContext.MultiEvalSumKeyGen(kp2.secretKey, evalSumKeys, kp2.publicKey.GetKeyTag())
+    evalSumKeysJoin = cryptoContext.MultiAddEvalSumKeys(evalSumKeys, evalSumKeysB, kp2.publicKey.GetKeyTag())
+    cryptoContext.InsertEvalSumKey(evalSumKeysJoin)
+    evalMultAAB = cryptoContext.MultiMultEvalKey(kp1.secretKey, evalMultAB, kp2.publicKey.GetKeyTag())
+    evalMultFinal = cryptoContext.MultiAddEvalMultKeys(evalMultAAB, evalMultBAB, evalMultAB.GetKeyTag())
+    cryptoContext.InsertEvalMultKey([evalMultFinal])
+
+    # Round 3 (party C) - Lead Party (who encrypts and finalizes the bootstrapping protocol)
+    kp3 = cryptoContext.MultipartyKeyGen(kp2.publicKey)
+    evalMultKey3 = cryptoContext.MultiKeySwitchGen(kp3.secretKey, kp3.secretKey, evalMultKey)
+    evalMultABC = cryptoContext.MultiAddEvalKeys(evalMultAB, evalMultKey3, kp3.publicKey.GetKeyTag())
+    evalMultBABC = cryptoContext.MultiMultEvalKey(kp2.secretKey, evalMultABC, kp3.publicKey.GetKeyTag())
+    evalMultAABC = cryptoContext.MultiMultEvalKey(kp1.secretKey, evalMultABC, kp3.publicKey.GetKeyTag())
+    evalMultCABC = cryptoContext.MultiMultEvalKey(kp3.secretKey, evalMultABC, kp3.publicKey.GetKeyTag())
+    evalMultABABC = cryptoContext.MultiAddEvalMultKeys(evalMultBABC, evalMultAABC, evalMultBABC.GetKeyTag())
+    evalMultFinal2 = cryptoContext.MultiAddEvalMultKeys(evalMultABABC, evalMultCABC, evalMultCABC.GetKeyTag())
+    cryptoContext.InsertEvalMultKey([evalMultFinal2])
+
+    if not kp1.good():
+        print("Key generation failed!")
+        exit(1)
+    if not kp2.good():
+        print("Key generation failed!")
+        exit(1)
+    if not kp3.good():
+        print("Key generation failed!")
+        exit(1)
+
+    # END of Key Generation
+
+    input = [-4.0, -3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0, 4.0]
+
+    # Chebyshev coefficients
+    coefficients = [1.0, 0.558971, 0.0, -0.0943712, 0.0, 0.0215023, 0.0, -0.00505348, 0.0, 0.00119324,
+                    0.0, -0.000281928, 0.0, 0.0000664347, 0.0, -0.0000148709]
+    # Input range
+    a = -4
+    b = 4
+
+    pt1 = cryptoContext.MakeCKKSPackedPlaintext(input)
+    encodedLength = len(input)
+
+    ct1 = cryptoContext.Encrypt(kp3.publicKey, pt1)
+
+    ct1 = cryptoContext.EvalChebyshevSeries(ct1, coefficients, a, b)
+
+    # INTERACTIVE BOOTSTRAPPING STARTS
+
+    ct1 = cryptoContext.IntMPBootAdjustScale(ct1)
+
+    # Leading party (party B) generates a Common Random Poly (crp) at max coefficient modulus (QNumPrime).
+    # a is sampled at random uniformly from R_{Q}
+    crp = cryptoContext.IntMPBootRandomElementGen(kp3.publicKey)
+    # Each party generates its own shares: maskedDecryptionShare and reEncryptionShare
+    # (h_{0,i}, h_{1,i}) = (masked decryption share, re-encryption share)
+    
+    # extract c1 - element-wise
+    c1 = ct1.Clone()
+    c1.RemoveElement(0)
+    sharesPair0 = cryptoContext.IntMPBootDecrypt(kp1.secretKey, c1, crp)
+    sharesPair1 = cryptoContext.IntMPBootDecrypt(kp2.secretKey, c1, crp)
+    sharesPair2 = cryptoContext.IntMPBootDecrypt(kp3.secretKey, c1, crp)
+
+    sharesPairVec = [sharesPair0, sharesPair1, sharesPair2]
+
+    # Party B finalizes the protocol by aggregating the shares and reEncrypting the results
+    aggregatedSharesPair = cryptoContext.IntMPBootAdd(sharesPairVec)
+    ciphertextOutput = cryptoContext.IntMPBootEncrypt(kp3.publicKey, aggregatedSharesPair, crp, ct1)
+
+    # INTERACTIVE BOOTSTRAPPING ENDS
+
+    # distributed decryption
+
+    ciphertextPartial1 = cryptoContext.MultipartyDecryptMain([ciphertextOutput], kp1.secretKey)
+    ciphertextPartial2 = cryptoContext.MultipartyDecryptMain([ciphertextOutput], kp2.secretKey)
+    ciphertextPartial3 = cryptoContext.MultipartyDecryptLead([ciphertextOutput], kp3.secretKey)
+    partialCiphertextVec = [ciphertextPartial1[0], ciphertextPartial2[0], ciphertextPartial3[0]]
+
+    plaintextMultiparty = cryptoContext.MultipartyDecryptFusion(partialCiphertextVec)
+    plaintextMultiparty.SetLength(encodedLength)
+
+    # Ground truth result
+    result = [0.0179885, 0.0474289, 0.119205, 0.268936, 0.5, 0.731064, 0.880795, 0.952571, 0.982011]
+    plaintextResult = cryptoContext.MakeCKKSPackedPlaintext(result)
+
+    print("Ground Truth:")
+    print("\t", plaintextResult.GetCKKSPackedValue())
+    print("Computed Result:")
+    print("\t", plaintextMultiparty.GetCKKSPackedValue())
+
+    checkApproximateEquality(plaintextResult.GetCKKSPackedValue(), plaintextMultiparty.GetCKKSPackedValue(), encodedLength, 0.0001)
+
+    print("\n============================ INTERACTIVE DECRYPTION ENDED ============================")
+
+    print(f"\nTCKKSCollectiveBoot FHE example with rescaling technique: {scaleTech} Completed!")
+
+if __name__ == "__main__":
+    main()

+ 173 - 0
examples/pke/tckks-interactive-mp-bootstrapping.py

@@ -0,0 +1,173 @@
+from openfhe import *
+
+#
+# A utility class defining a party that is involved in the collective bootstrapping protocol
+#
+class Party:
+    def __init__(self, id, sharesPair, kpShard):
+        self.id = id
+        self.sharesPair = sharesPair
+        self.kpShard = kpShard
+    def __init__(self):
+        self.id = None
+        self.sharesPair = None
+        self.kpShard = None
+    def __str__(self):
+        return f"Party {self.id}"
+
+def main():
+    print( "Interactive Multi-Party Bootstrapping Ciphertext (TCKKS) started ...\n")
+
+    # Same test with different rescaling techniques in CKKS
+    TCKKSCollectiveBoot(FIXEDMANUAL)
+    TCKKSCollectiveBoot(FIXEDAUTO)
+    TCKKSCollectiveBoot(FLEXIBLEAUTO)
+    TCKKSCollectiveBoot(FLEXIBLEAUTOEXT)
+
+    print("Interactive Multi-Party Bootstrapping Ciphertext (TCKKS) terminated gracefully!\n")
+
+# Demonstrate interactive multi-party bootstrapping for 3 parties
+# We follow Protocol 5 in https://eprint.iacr.org/2020/304, "Multiparty
+# Homomorphic Encryption from Ring-Learning-With-Errors"
+
+def TCKKSCollectiveBoot(scaleTech):
+    if scaleTech != FIXEDMANUAL and scaleTech != FIXEDAUTO and scaleTech != FLEXIBLEAUTO and scaleTech != FLEXIBLEAUTOEXT:
+        errMsg = "ERROR: Scaling technique is not supported!"
+        raise Exception(errMsg)
+
+    parameters = CCParamsCKKSRNS()
+
+    secretKeyDist = UNIFORM_TERNARY
+    parameters.SetSecretKeyDist(secretKeyDist)
+
+    parameters.SetSecurityLevel(HEStd_128_classic)
+
+    dcrtBits = 50
+    firstMod = 60
+
+    parameters.SetScalingModSize(dcrtBits)
+    parameters.SetScalingTechnique(scaleTech)
+    parameters.SetFirstModSize(firstMod)
+
+    multiplicativeDepth = 7
+    parameters.SetMultiplicativeDepth(multiplicativeDepth)
+    parameters.SetKeySwitchTechnique(HYBRID)
+
+    batchSize = 4
+    parameters.SetBatchSize(batchSize)
+
+    compressionLevel = COMPRESSION_LEVEL.SLACK
+    parameters.SetInteractiveBootCompressionLevel(compressionLevel)
+
+    cryptoContext = GenCryptoContext(parameters)
+    cryptoContext.Enable(PKE)
+    cryptoContext.Enable(KEYSWITCH)
+    cryptoContext.Enable(LEVELEDSHE)
+    cryptoContext.Enable(ADVANCEDSHE)
+    cryptoContext.Enable(MULTIPARTY)
+
+    ringDim = cryptoContext.GetRingDimension()
+    maxNumSlots = ringDim / 2
+
+    print(f"TCKKS scheme is using ring dimension {ringDim}")
+    print(f"TCKKS scheme number of slots         {maxNumSlots}")
+    print(f"TCKKS scheme max number of slots     {maxNumSlots}")
+    print(f"TCKKS example with Scaling Technique {scaleTech}")
+
+    numParties = 3
+
+    print("\n===========================IntMPBoot protocol parameters===========================\n")
+    print(f"number of parties: {numParties}\n")
+    print("===============================================================\n")
+
+    # List to store parties objects
+    parties = [Party()]*numParties
+
+    print("Running key generation (used for source data)...\n")
+
+    for i in range(numParties):
+        #define id of parties[i] as i
+        parties[i].id = i
+        print(f"Party {parties[i].id} started.")
+        if i == 0:
+            parties[i].kpShard = cryptoContext.KeyGen()
+        else:
+            parties[i].kpShard = cryptoContext.MultipartyKeyGen(parties[0].kpShard.publicKey)
+        print(f"Party {i} key generation completed.\n")
+    
+    print("Joint public key for (s_0 + s_1 + ... + s_n) is generated...")
+
+    # Assert everything is good
+    for i in range(numParties):
+        if not parties[i].kpShard.good():
+            print(f"Key generation failed for party {i}!\n")
+            return 1
+
+    # Generate collective public key
+    secretKeys = []
+    for i in range(numParties):
+        secretKeys.append(parties[i].kpShard.secretKey)
+    kpMultiparty = cryptoContext.MultipartyKeyGen(secretKeys)
+
+    # Prepare input vector
+    msg1 = [-0.9, -0.8, 0.2, 0.4]
+    ptxt1 = cryptoContext.MakeCKKSPackedPlaintext(msg1)
+
+    # Encryption
+    inCtxt = cryptoContext.Encrypt(kpMultiparty.publicKey, ptxt1)
+    
+    print("Compressing ctxt to the smallest possible number of towers!\n")
+    inCtxt = cryptoContext.IntMPBootAdjustScale(inCtxt)
+
+    print("\n============================ INTERACTIVE BOOTSTRAPPING STARTS ============================\n")
+    
+    #Leading party (P0) generates a Common Random Poly (a) at max coefficient modulus (QNumPrime).
+    # a is sampled at random uniformly from R_{Q}
+    a = cryptoContext.IntMPBootRandomElementGen(parties[0].kpShard.publicKey)
+    print("Common Random Poly (a) has been generated with coefficient modulus Q\n")
+
+    # Each party generates its own shares: maskedDecryptionShare and reEncryptionShare
+    sharePairVec = []
+
+    # Make a copy of input ciphertext and remove the first element (c0), we only
+    # c1 for IntMPBootDecrypt
+    c1 = inCtxt.Clone()
+    c1.RemoveElement(0)
+
+    for i in range(numParties):
+        print(f"Party {i} started its part in Collective Bootstrapping Protocol.\n")
+        parties[i].sharesPair = cryptoContext.IntMPBootDecrypt(parties[i].kpShard.secretKey, c1, a)
+        sharePairVec.append(parties[i].sharesPair)
+    
+    # P0 finalizes the protocol by aggregating the shares and reEncrypting the results
+    aggregatedSharesPair = cryptoContext.IntMPBootAdd(sharePairVec);
+    # Make sure you provide the non-striped ciphertext (inCtxt) in IntMPBootEncrypt
+    outCtxt = cryptoContext.IntMPBootEncrypt(parties[0].kpShard.publicKey, aggregatedSharesPair, a, inCtxt)
+
+    # INTERACTIVE BOOTSTRAPPING ENDS
+    print("\n============================ INTERACTIVE BOOTSTRAPPING ENDED ============================\n")
+
+    # Distributed Decryption
+    print("\n============================ INTERACTIVE DECRYPTION STARTED ============================ \n")
+
+    partialCiphertextVec = []
+    print("Party 0 started its part in the collective decryption protocol\n")
+    partialCiphertextVec.append(cryptoContext.MultipartyDecryptLead([outCtxt], parties[0].kpShard.secretKey)[0])
+
+    for i in range(1, numParties):
+        print(f"Party {i} started its part in the collective decryption protocol\n")
+        partialCiphertextVec.append(cryptoContext.MultipartyDecryptMain([outCtxt], parties[i].kpShard.secretKey)[0])
+
+    # Checking the results
+    print("MultipartyDecryptFusion ...\n")
+    plaintextMultiparty = cryptoContext.MultipartyDecryptFusion(partialCiphertextVec)
+    plaintextMultiparty.SetLength(len(msg1))
+
+    # transform to python:
+    print(f"Original plaintext \n\t {ptxt1.GetCKKSPackedValue()}\n")
+    print(f"Result after bootstrapping \n\t {plaintextMultiparty.GetCKKSPackedValue()}\n")
+
+    print("\n============================ INTERACTIVE DECRYPTION ENDED ============================\n")      
+
+if __name__ == "__main__":
+    main()

+ 20 - 0
src/include/binfhe/binfhecontext_wrapper.h

@@ -32,6 +32,8 @@
 #include <pybind11/stl.h>
 #include "openfhe.h"
 #include "binfhecontext.h"
+#include <functional>
+#include <pybind11/functional.h>
 
 namespace py = pybind11;
 using namespace lbcrypto;
@@ -46,4 +48,22 @@ LWEPlaintext binfhe_DecryptWrapper(BinFHEContext &self,
                                    ConstLWECiphertext ct,
                                    LWEPlaintextModulus p);
 
+uint32_t GetnWrapper(BinFHEContext &self);
+
+const uint64_t GetqWrapper(BinFHEContext &self) ;
+
+const uint64_t GetMaxPlaintextSpaceWrapper(BinFHEContext &self);
+
+const uint64_t GetBetaWrapper(BinFHEContext &self);
+
+const uint64_t GetLWECiphertextModulusWrapper(LWECiphertext &self);
+
+std::vector<uint64_t> GenerateLUTviaFunctionWrapper(BinFHEContext &self, py::function f, uint64_t p);
+
+NativeInteger StaticFunction(NativeInteger m, NativeInteger p);
+
+// Define static variables to hold the state
+extern py::function static_f;
+
+LWECiphertext EvalFuncWrapper(BinFHEContext &self, ConstLWECiphertext &ct, const std::vector<uint64_t> &LUT);
 #endif // BINFHE_CRYPTOCONTEXT_BINDINGS_H

+ 75 - 3
src/include/docstrings/binfhecontext_docs.h

@@ -30,16 +30,36 @@
 
 // GenerateBinFHEContext
 const char* binfhe_GenerateBinFHEContext_parset_docs = R"pbdoc(
-    Creates a crypto context using predefined parameter sets. Recommended for most users.
+    Creates a crypto context using predefined parameters sets. Recommended for most users.
 
-    :param set: The parameter set: TOY, MEDIUM, STD128, STD192, STD256.
+    :param set: the parameter set: TOY, MEDIUM, STD128, STD192, STD256 with variants
     :type set: BINFHE_PARAMSET
-    :param method: The bootstrapping method (DM or CGGI).
+    :param method: the bootstrapping method (DM or CGGI or LMKCDEY)
     :type method: BINFHE_METHOD
     :return: The created crypto context.
     :rtype: BinFHEContext
 )pbdoc";
 
+////void GenerateBinFHEContext(BINFHE_PARAMSET set, bool arbFunc, uint32_t logQ = 11, int64_t N = 0, BINFHE_METHOD method = GINX, bool timeOptimization = false)
+const char* binfhe_GenerateBinFHEContext_docs  = R"pbdoc(
+    Creates a crypto context using custom parameters. Should be used with care (only for advanced users familiar with LWE parameter selection).
+
+    :param set: The parameter set: TOY, MEDIUM, STD128, STD192, STD256 with variants.
+    :type set: BINFHE_PARAMSET
+    :param arbFunc:  whether need to evaluate an arbitrary function using functional bootstrapping
+    :type arbFunc: bool
+    :param logQ:  log(input ciphertext modulus)
+    :type logQ: int
+    :param N:  ring dimension for RingGSW/RLWE used in bootstrapping
+    :type N: int
+    :param method: the bootstrapping method (DM or CGGI or LMKCDEY)
+    :type method: BINFHE_METHOD
+    :param timeOptimization:  whether to use dynamic bootstrapping technique
+    :type timeOptimization: bool
+    :return: creates the cryptocontext.
+    :rtype: BinFHEContext
+)pbdoc";
+
 // KeyGen
 const char* binfhe_KeyGen_docs = R"pbdoc(
     Generates a secret key for the main LWE scheme.
@@ -112,5 +132,57 @@ const char* binfhe_EvalNOT_docs = R"pbdoc(
     :rtype: LWECiphertext
 )pbdoc";
 
+const char* binfhe_EvalDecomp_docs = R"pbdoc(
+    Evaluate ciphertext decomposition
+
+    :param ct: ciphertext to be bootstrapped
+    :type ct: LWECiphertext
+    :return: a list with the resulting ciphertexts
+    :rtype: List[LWECiphertext]
+)pbdoc";
+
+const char* binfhe_EvalFloor_docs = R"pbdoc(
+    Evaluate a round down function
+
+    :param ct: ciphertext to be bootstrapped
+    :type ct: LWECiphertext
+    :param roundbits: number of bits to be rounded
+    :type roundbits: int
+    :return: the resulting ciphertext
+    :rtype: LWECiphertext
+)pbdoc";
 
+const char* binfhe_GenerateLUTviaFunction_docs = R"pbdoc(
+    Generate the LUT for the to-be-evaluated function
+
+    :param f: the to-be-evaluated function on an integer message and a plaintext modulus
+    :type f: function(int, int) -> int
+    :param p: plaintext modulus
+    :type p: int
+    :return: the resulting ciphertext
+    :rtype: List[int]
+)pbdoc";
+
+const char* binfhe_EvalFunc_docs = R"pbdoc(
+    Evaluate an arbitrary function
+
+    :param ct: ciphertext to be bootstrapped
+    :type ct: LWECiphertext
+    :param LUT: the look-up table of the to-be-evaluated function
+    :type LUT: List[int]
+    :return: the resulting ciphertext
+    :rtype: LWECiphertext
+)pbdoc";
+
+//LWECiphertext EvalSign(ConstLWECiphertext &ct, bool schemeSwitch = false)
+const char* binfhe_EvalSign_docs = R"pbdoc(
+    Evaluate a sign function over large precisions
+
+    :param ct: ciphertext to be bootstrapped
+    :type ct: LWECiphertext
+    :param schemeSwitch: flag that indicates if it should be compatible to scheme switching
+    :type schemeSwitch: bool
+    :return: the resulting ciphertext
+    :rtype: LWECiphertext
+)pbdoc";
 #endif // BINFHECONTEXT_DOCSTRINGS_H

+ 6 - 0
src/include/docstrings/ciphertext_docs.h

@@ -52,4 +52,10 @@ const char* kp_good_docs = R"pbdoc(
     :rtype: bool
 )pbdoc";
 
+const char* cc_RemoveElement_docs = R"pbdoc(
+    Remove an element from the ciphertext inner vector given its index.
+
+    :param index: The index of the element to remove.
+    :type index: int
+)pbdoc";
 #endif // CIPHERTEXT_DOCSTRINGS_H

+ 107 - 1
src/include/docstrings/cryptocontext_docs.h

@@ -879,6 +879,15 @@ const char* cc_MultipartyKeyGen_docs = R"pbdoc(
     :rtype: KeyPair
 )pbdoc";
 
+const char* cc_MultipartyKeyGen_vector_docs = R"pbdoc(
+    Threshold FHE: Generates a public key from a vector of secret shares. ONLY FOR DEBUGGIN PURPOSES. SHOULD NOT BE USED IN PRODUCTION.
+
+    :param privateKeyVec: secret key shares.
+    :type privateKeyVec: List[PrivateKey]
+    :return KeyPair: key pair including the private for the current party and joined public key
+    :rtype: KeyPair
+)pbdoc";
+
 const char* cc_MultipartyDecryptLead_docs = R"pbdoc(
     Threshold FHE: Method for decryption operation run by the lead decryption client
 
@@ -1000,6 +1009,62 @@ const char* cc_MultiAddEvalMultKeys_docs = R"pbdoc(
     :rtype: EvalKey
 )pbdoc";
 
+
+const char* cc_IntMPBootAdjustScale_docs = R"pbdoc(
+    Threshold FHE: Prepare a ciphertext for Multi-Party Interactive Bootstrapping.
+
+    :param ciphertext: Input Ciphertext
+    :type ciphertext: Ciphertext
+    :return: Resulting Ciphertext
+    :rtype: Ciphertext
+)pbdoc";
+
+const char* cc_IntMPBootRandomElementGen_docs = R"pbdoc(
+    Threshold FHE: Generate a common random polynomial for Multi-Party Interactive Bootstrapping
+
+    :param publicKey: the scheme public key (you can also provide the lead party's public-key)
+    :type publicKey: PublicKey
+    :return: Resulting ring element
+    :rtype: Ciphertext
+)pbdoc";
+
+const char* cc_IntMPBootDecrypt_docs = R"pbdoc(
+    Threshold FHE: Does masked decryption as part of Multi-Party Interactive Bootstrapping. Each party calls this function as part of the protocol
+
+    :param privateKey: secret key share for party i
+    :type privateKey: PrivateKey
+    :param ciphertext: input ciphertext
+    :type ciphertext: Ciphertext
+    :param a: input common random polynomial
+    :type a: Ciphertext
+    :return: Resulting masked decryption
+    :rtype: Ciphertext
+)pbdoc";
+
+const char* cc_IntMPBootAdd_docs = R"pbdoc(
+    Threshold FHE: Aggregates a vector of masked decryptions and re-encryotion shares, which is the second step of the interactive multiparty bootstrapping procedure.
+
+    :param sharesPairVec: vector of pair of ciphertexts, each element of this vector contains (h_0i, h_1i) - the masked-decryption and encryption shares ofparty i
+    :type sharesPairVec: List[List[Ciphertext]]
+    :return: aggregated pair of shares ((h_0, h_1)
+    :rtype: List[Ciphertext]
+)pbdoc";
+
+const char* cc_IntMPBootEncrypt_docs = R"pbdoc(
+    Threshold FHE: Does public key encryption of lead party's masked decryption as part of interactive multi-party bootstrapping, which increases the ciphertext modulus and enables future computations. This operation is done by the lead party as the final step of interactive multi-party bootstrapping.
+
+    :param publicKey: the lead party's public key
+    :type publicKey: PublicKey
+    :param sharesPair: aggregated decryption and re-encryption shares
+    :type sharesPair: List[Ciphertext]
+    :param a: common random ring element
+    :type a: Ciphertext
+    :param ciphertext: input ciphertext
+    :type ciphertext: Ciphertext
+    :return: Resulting encryption
+    :rtype: Ciphertext
+)pbdoc";
+
 const char* cc_InsertEvalMultKey_docs = R"pbdoc(
     InsertEvalMultKey - add the given vector of keys to the map, replacing the existing vector if there
 
@@ -1024,7 +1089,31 @@ const char* cc_EvalMerge_docs = R"pbdoc(
 
     :param ciphertextVec: vector of ciphertexts to be merged.
     :type ciphertextVec: list
-    :return: Ciphertext: resulting ciphertext
+    :return: resulting ciphertext
+    :rtype: Ciphertext
+)pbdoc";
+
+const char* cc_ReKeyGen_docs = R"pbdoc(
+    ReKeyGen produces an Eval Key that OpenFHE can use for Proxy Re-Encryption
+
+    :param oldPrivateKey: original private key
+    :type privateKey: PrivateKey
+    :param newPublicKey: public key
+    :type publicKey: PublicKey
+    :return: new evaluation key
+    :rtype: EvalKey
+)pbdoc";
+
+const char* cc_ReEncrypt_docs = R"pbdoc(
+    ReEncrypt - Proxy Re-Encryption mechanism for OpenFHE
+
+    :param ciphertext: input ciphertext
+    :type ciphertext: Ciphertext
+    :param evalKey: evaluation key for PRE keygen method
+    :type evalKey: EvalKey
+    :param publicKey: the public key of the recipient of the reencrypted ciphertext
+    :type publicKey: PublicKey
+    :return: the resulting ciphertext
     :rtype: Ciphertext
 )pbdoc";
 
@@ -1136,6 +1225,23 @@ const char* cc_EvalBootstrap_docs = R"pbdoc(
     :rtype: Ciphertext
 )pbdoc";
 
+// TODO (Oliveira, R.) - Complete the following documentation
+const char* cc_EvalCKKStoFHEWSetup_docs = "";
+const char* cc_EvalCKKStoFHEWKeyGen_docs = "";
+const char* cc_EvalCKKStoFHEWPrecompute_docs = "";
+const char* cc_EvalCKKStoFHEW_docs = "";
+const char* cc_EvalFHEWtoCKKSSetup_docs = "";
+const char* cc_EvalFHEWtoCKKSKeyGen_docs = "";
+const char* cc_EvalFHEWtoCKKS_docs = "";
+const char* cc_EvalSchemeSwitchingSetup_docs = "";
+const char* cc_EvalSchemeSwitchingKeyGen_docs = "";
+const char* cc_EvalCompareSwitchPrecompute_docs = "";
+const char* cc_EvalCompareSchemeSwitching_docs = "";
+const char* cc_EvalMinSchemeSwitching_docs = "";
+const char* cc_EvalMinSchemeSwitchingAlt_docs = "";
+const char* cc_EvalMaxSchemeSwitching_docs = "";
+const char* cc_EvalMaxSchemeSwitchingAlt_docs = "";
+
 const char* cc_EvalAutomorphismKeyGen_docs = R"pbdoc(
     Generate automophism keys for a given private key; Uses the private key for encryption
 

+ 10 - 0
src/include/docstrings/cryptoparameters_docs.h

@@ -62,4 +62,14 @@ const char* ccparams_doc = R"doc(
     :ivar MultiplicationTechnique multiplicationTechnique: multiplication method in BFV: BEHZ, HPS, etc.
 )doc";
 
+const char* cc_GetScalingFactorReal_docs = R"pbdoc(
+    Method to retrieve the scaling factor of level l. For FIXEDMANUAL scaling technique method always returns 2^p, where p corresponds to plaintext modulus
+
+    :param l:  For FLEXIBLEAUTO scaling technique the level whose scaling factor we want to learn. Levels start from 0 (no scaling done - all towers) and go up to K-1, where K is the number of towers supported.
+    :type l: int
+    :return: the scaling factor.
+    :rtype: float
+)pbdoc";
+
+
 #endif // CRYPTOPARAMS_DOCSTRINGS_H

+ 22 - 1
src/include/docstrings/plaintext_docs.h

@@ -94,6 +94,27 @@ const char* ptx_Decode_docs = R"pbdoc(
     Decode the polynomial into a plaintext.
 )pbdoc";
 
+const char* ptx_LowBound_docs = R"pbdoc(
+    Calculate and return lower bound that can be encoded with the plaintext modulus the number to encode MUST be greater than this value
+
+    :return: floor(-p/2)
+    :rtype: int
+)pbdoc";
+
+const char* ptx_HighBound_docs = R"pbdoc(
+    Calculate and return upper bound that can be encoded with the plaintext modulus the number to encode MUST be less than this value
+
+    :return: floor(p/2)
+    :rtype: int
+)pbdoc";
+
+const char* ptx_SetFormat_docs = R"pbdoc(
+    SetFormat - allows format to be changed for openfhe.Plaintext evaluations
+
+    :param fmt:
+    :type format: Format
+)pbdoc";
+
 // GetCKKSPackedValue
 const char* ptx_GetCKKSPackedValue_docs = R"pbdoc(
     Get the packed value of the plaintext for CKKS-based plaintexts.
@@ -108,7 +129,7 @@ const char* ptx_GetRealPackedValue_docs = R"pbdoc(
     Get the real component of the packed value of the plaintext for CKKS-based plaintexts.
 
     :return: The real-component of the packed value of the plaintext.
-    :rtype: List[float]
+    :rtype: List[double]
 )pbdoc";
 
 

+ 5 - 0
src/include/pke/cryptocontext_wrapper.h

@@ -61,4 +61,9 @@ const std::map<usint, EvalKey<DCRTPoly>> EvalAutomorphismKeyGenWrapper_PublicKey
 const std::shared_ptr<std::map<usint, EvalKey<DCRTPoly>>> GetEvalSumKeyMapWrapper(CryptoContext<DCRTPoly>& self, const std::string &id);
 const PlaintextModulus GetPlaintextModulusWrapper(CryptoContext<DCRTPoly>& self);
 const double GetModulusWrapper(CryptoContext<DCRTPoly>& self);
+void RemoveElementWrapper(Ciphertext<DCRTPoly>& self, usint index);
+const double GetScalingFactorRealWrapper(CryptoContext<DCRTPoly>& self, uint32_t l);
+const uint64_t GetModulusCKKSWrapper(CryptoContext<DCRTPoly>& self);
+const ScalingTechnique GetScalingTechniqueWrapper(CryptoContext<DCRTPoly>& self);
+const usint GetDigitSizeWrapper(CryptoContext<DCRTPoly>& self);
 #endif // OPENFHE_CRYPTOCONTEXT_BINDINGS_H

+ 199 - 9
src/lib/bindings.cpp

@@ -13,6 +13,7 @@
 #include "cryptocontext_wrapper.h"
 #include "binfhe_bindings.h"
 #include "cryptocontext_docs.h"
+#include "cryptoparameters_docs.h"
 #include "plaintext_docs.h"
 #include "ciphertext_docs.h"
 #include "serialization.h"
@@ -56,6 +57,7 @@ void bind_parameters(py::module &m,const std::string name)
         .def("GetEncryptionTechnique", &CCParams<T>::GetEncryptionTechnique)
         .def("GetMultiplicationTechnique", &CCParams<T>::GetMultiplicationTechnique)
         .def("GetMultiHopModSize", &CCParams<T>::GetMultiHopModSize)
+        .def("GetInteractiveBootCompressionLevel", &CCParams<T>::GetInteractiveBootCompressionLevel)
         // setters
         .def("SetPlaintextModulus", &CCParams<T>::SetPlaintextModulus)
         .def("SetDigitSize", &CCParams<T>::SetDigitSize)
@@ -85,6 +87,7 @@ void bind_parameters(py::module &m,const std::string name)
         .def("SetEncryptionTechnique", &CCParams<T>::SetEncryptionTechnique)
         .def("SetMultiplicationTechnique", &CCParams<T>::SetMultiplicationTechnique)
         .def("SetMultiHopModSize", &CCParams<T>::SetMultiHopModSize)
+        .def("SetInteractiveBootCompressionLevel", &CCParams<T>::SetInteractiveBootCompressionLevel)
         .def("__str__",[](const CCParams<T> &params) {
             std::stringstream stream;
             stream << params;
@@ -109,6 +112,10 @@ void bind_crypto_context(py::module &m)
         .def("GetRingDimension", &CryptoContextImpl<DCRTPoly>::GetRingDimension, cc_GetRingDimension_docs)
         .def("GetPlaintextModulus", &GetPlaintextModulusWrapper, cc_GetPlaintextModulus_docs)
         .def("GetModulus", &GetModulusWrapper, cc_GetModulus_docs)
+        .def("GetModulusCKKS", &GetModulusCKKSWrapper)
+        .def("GetScalingFactorReal", &GetScalingFactorRealWrapper, cc_GetScalingFactorReal_docs)
+        .def("GetScalingTechnique",&GetScalingTechniqueWrapper)
+        .def("GetDigitSize", &GetDigitSizeWrapper)
         .def("GetCyclotomicOrder", &CryptoContextImpl<DCRTPoly>::GetCyclotomicOrder, cc_GetCyclotomicOrder_docs)
         .def("Enable", static_cast<void (CryptoContextImpl<DCRTPoly>::*)(PKESchemeFeature)>(&CryptoContextImpl<DCRTPoly>::Enable), cc_Enable_docs,
              py::arg("feature"))
@@ -471,6 +478,9 @@ void bind_crypto_context(py::module &m)
              py::arg("publicKey"),
              py::arg("makeSparse") = false,
              py::arg("fresh") = false)
+        .def("MultipartyKeyGen", static_cast<KeyPair<DCRTPoly> (CryptoContextImpl<DCRTPoly>::*)(const std::vector<PrivateKey<DCRTPoly>> &)>(&CryptoContextImpl<DCRTPoly>::MultipartyKeyGen),
+             cc_MultipartyKeyGen_vector_docs,
+             py::arg("privateKeyVec"))
         .def("MultipartyDecryptLead", &CryptoContextImpl<DCRTPoly>::MultipartyDecryptLead,
              cc_MultipartyDecryptLead_docs,
              py::arg("ciphertextVec"),
@@ -502,6 +512,26 @@ void bind_crypto_context(py::module &m)
              py::arg("evalKey1"),
              py::arg("evalKey2"),
              py::arg("keyId") = "")
+        .def("IntMPBootAdjustScale",&CryptoContextImpl<DCRTPoly>::IntMPBootAdjustScale,
+             cc_IntMPBootAdjustScale_docs,
+             py::arg("ciphertext"))
+        .def("IntMPBootRandomElementGen", &CryptoContextImpl<DCRTPoly>::IntMPBootRandomElementGen,
+             cc_IntMPBootRandomElementGen_docs,
+             py::arg("publicKey"))
+        .def("IntMPBootDecrypt", &CryptoContextImpl<DCRTPoly>::IntMPBootDecrypt,
+             cc_IntMPBootDecrypt_docs,
+             py::arg("privateKey"),
+             py::arg("ciphertext"),
+             py::arg("a"))
+        .def("IntMPBootAdd", &CryptoContextImpl<DCRTPoly>::IntMPBootAdd,
+             cc_IntMPBootAdd_docs,
+             py::arg("sharePairVec"))
+        .def("IntMPBootEncrypt", &CryptoContextImpl<DCRTPoly>::IntMPBootEncrypt,
+             cc_IntMPBootEncrypt_docs,
+             py::arg("publicKey"),
+             py::arg("sharePair"),
+             py::arg("a"),
+             py::arg("ciphertext"))             
         .def("MultiMultEvalKey", &CryptoContextImpl<DCRTPoly>::MultiMultEvalKey,
              cc_MultiMultEvalKey_docs,
              py::arg("privateKey"),
@@ -515,6 +545,16 @@ void bind_crypto_context(py::module &m)
         .def("EvalMerge", &CryptoContextImpl<DCRTPoly>::EvalMerge,
              cc_EvalMerge_docs,
              py::arg("ciphertextVec"))
+             // use static_cast: inline EvalKey<Element> ReKeyGen(const PrivateKey<Element> oldPrivateKey, const PublicKey<Element> newPublicKey) const;
+        .def("ReKeyGen", static_cast<EvalKey<DCRTPoly> (CryptoContextImpl<DCRTPoly>::*)(const PrivateKey<DCRTPoly>, const PublicKey<DCRTPoly>) const>(&CryptoContextImpl<DCRTPoly>::ReKeyGen),
+             cc_ReKeyGen_docs,
+             py::arg("oldPrivateKey"),
+             py::arg("newPublicKey"))
+        .def("ReEncrypt", &CryptoContextImpl<DCRTPoly>::ReEncrypt,
+             cc_ReEncrypt_docs,
+             py::arg("ciphertext"),
+             py::arg("evalKey"),
+             py::arg("publicKey") = nullptr)
         .def("EvalPoly", &CryptoContextImpl<DCRTPoly>::EvalPoly,
              cc_EvalPoly_docs,
              py::arg("ciphertext"),
@@ -554,6 +594,120 @@ void bind_crypto_context(py::module &m)
              py::arg("ciphertext"),
              py::arg("numIterations") = 1,
              py::arg("precision") = 0)
+        .def("EvalCKKStoFHEWSetup", &CryptoContextImpl<DCRTPoly>::EvalCKKStoFHEWSetup,
+            cc_EvalCKKStoFHEWSetup_docs,
+            py::arg("sl") = HEStd_128_classic,
+            py::arg("slBin") = BINFHE_PARAMSET::STD128,
+            py::arg("arbFunc") = false,
+            py::arg("logQ") = 25,
+            py::arg("dynamic") = false,
+            py::arg("numSlotsCKKS") = 0,
+            py::arg("logQswitch") = 27)
+        .def("EvalCKKStoFHEWKeyGen", &CryptoContextImpl<DCRTPoly>::EvalCKKStoFHEWKeyGen,
+             cc_EvalCKKStoFHEWKeyGen_docs,
+             py::arg("keyPair"),
+             py::arg("lwesk"),
+             py::arg("dim1") = 0,
+             py::arg("L") = 1)
+        .def("EvalCKKStoFHEWPrecompute", &CryptoContextImpl<DCRTPoly>::EvalCKKStoFHEWPrecompute,
+             cc_EvalCKKStoFHEWPrecompute_docs,
+             py::arg("scale") = 1.0)
+        .def("EvalCKKStoFHEW", &CryptoContextImpl<DCRTPoly>::EvalCKKStoFHEW,
+             cc_EvalCKKStoFHEW_docs,
+             py::arg("ciphertext"),
+             py::arg("numCtxts") = 0)
+        .def("EvalFHEWtoCKKSSetup", &CryptoContextImpl<DCRTPoly>::EvalFHEWtoCKKSSetup,
+             cc_EvalFHEWtoCKKSSetup_docs,
+             py::arg("ccLWE"),
+             py::arg("numSlotsCKKS") = 0,
+             py::arg("logQ") = 25)
+        .def("EvalFHEWtoCKKSKeyGen", &CryptoContextImpl<DCRTPoly>::EvalFHEWtoCKKSKeyGen,
+             cc_EvalFHEWtoCKKSKeyGen_docs,
+             py::arg("keyPair"),
+             py::arg("lwesk"),
+             py::arg("numSlots") = 0,
+             py::arg("dim1") = 0,
+             py::arg("L") = 0)
+        .def("EvalFHEWtoCKKS", &CryptoContextImpl<DCRTPoly>::EvalFHEWtoCKKS,
+             cc_EvalFHEWtoCKKS_docs,
+             py::arg("LWECiphertexts"),
+             py::arg("numCtxts") = 0,
+             py::arg("numSlots") = 0,
+             py::arg("p") = 4,
+             py::arg("pmin") = 0.0,
+             py::arg("pmax") = 2.0)
+        .def("EvalSchemeSwitchingSetup", &CryptoContextImpl<DCRTPoly>::EvalSchemeSwitchingSetup,
+             cc_EvalSchemeSwitchingSetup_docs,
+             py::arg("sl") = HEStd_128_classic,
+             py::arg("slBin") = BINFHE_PARAMSET::STD128,
+             py::arg("arbFunc") = false,
+             py::arg("logQ") = 25,
+             py::arg("dynamic") = false,
+             py::arg("numSlotsCKKS") = 0,
+             py::arg("logQswitch") = 27)
+        //void EvalSchemeSwitchingKeyGen(const KeyPair<Element> &keyPair, ConstLWEPrivateKey &lwesk, uint32_t numValues = 0, bool oneHot = true, bool alt = false, uint32_t dim1CF = 0, uint32_t dim1FC = 0, uint32_t LCF = 1, uint32_t LFC = 0)
+        .def("EvalSchemeSwitchingKeyGen", &CryptoContextImpl<DCRTPoly>::EvalSchemeSwitchingKeyGen,
+             cc_EvalSchemeSwitchingKeyGen_docs,
+             py::arg("keyPair"),
+             py::arg("lwesk"),
+             py::arg("numValues") = 0,
+             py::arg("oneHot") = true,
+             py::arg("alt") = false,
+             py::arg("dim1CF") = 0,
+             py::arg("dim1FC") = 0,
+             py::arg("LCF") = 1,
+             py::arg("LFC") = 0)
+        .def("EvalCompareSwitchPrecompute", &CryptoContextImpl<DCRTPoly>::EvalCompareSwitchPrecompute,
+             cc_EvalCompareSwitchPrecompute_docs,
+             py::arg("pLWE") = 0,
+             py::arg("initLevel") = 0,
+             py::arg("scaleSign") = 1.0,
+             py::arg("unit") = false)
+        .def("EvalCompareSchemeSwitching", &CryptoContextImpl<DCRTPoly>::EvalCompareSchemeSwitching,
+             cc_EvalCompareSchemeSwitching_docs,
+             py::arg("ciphertext1"),
+             py::arg("ciphertext2"),
+             py::arg("numCtxts") = 0,
+             py::arg("numSlots") = 0,
+             py::arg("pLWE") = 0,
+             py::arg("scaleSign") = 1.0,
+             py::arg("unit") = false)
+        .def("EvalMinSchemeSwitching", &CryptoContextImpl<DCRTPoly>::EvalMinSchemeSwitching,
+             cc_EvalMinSchemeSwitching_docs,
+             py::arg("ciphertext"),
+             py::arg("publicKey"),
+             py::arg("numValues") = 0,
+             py::arg("numSlots") = 0,
+             py::arg("oneHot") = true,
+             py::arg("pLWE") = 0,
+             py::arg("scaleSign") = 1.0)
+        .def("EvalMinSchemeSwitchingAlt", &CryptoContextImpl<DCRTPoly>::EvalMinSchemeSwitchingAlt,
+             cc_EvalMinSchemeSwitchingAlt_docs,
+             py::arg("ciphertext"),
+             py::arg("publicKey"),
+             py::arg("numValues") = 0,
+             py::arg("numSlots") = 0,
+             py::arg("oneHot") = true,
+             py::arg("pLWE") = 0,
+             py::arg("scaleSign") = 1.0)
+        .def("EvalMaxSchemeSwitching", &CryptoContextImpl<DCRTPoly>::EvalMaxSchemeSwitching,
+             cc_EvalMaxSchemeSwitching_docs,
+             py::arg("ciphertext"),
+             py::arg("publicKey"),
+             py::arg("numValues") = 0,
+             py::arg("numSlots") = 0,
+             py::arg("oneHot") = true,
+             py::arg("pLWE") = 0,
+             py::arg("scaleSign") = 1.0)
+        .def("EvalMaxSchemeSwitchingAlt", &CryptoContextImpl<DCRTPoly>::EvalMaxSchemeSwitchingAlt,
+             cc_EvalMaxSchemeSwitchingAlt_docs,
+             py::arg("ciphertext"),
+             py::arg("publicKey"),
+             py::arg("numValues") = 0,
+             py::arg("numSlots") = 0,
+             py::arg("oneHot") = true,
+             py::arg("pLWE") = 0,
+             py::arg("scaleSign") = 1.0)
         //TODO (Oliveira, R.): Solve pointer handling bug when returning EvalKeyMap objects for the next functions
         .def("EvalAutomorphismKeyGen", &EvalAutomorphismKeyGenWrapper, 
             cc_EvalAutomorphismKeyGen_docs,
@@ -723,7 +877,8 @@ void bind_enums_and_constants(py::module &m)
         .value("LEVELEDSHE", PKESchemeFeature::LEVELEDSHE)
         .value("ADVANCEDSHE", PKESchemeFeature::ADVANCEDSHE)
         .value("MULTIPARTY", PKESchemeFeature::MULTIPARTY)
-        .value("FHE", PKESchemeFeature::FHE);
+        .value("FHE", PKESchemeFeature::FHE)
+        .value("SCHEMESWITCH", PKESchemeFeature::SCHEMESWITCH);
     m.attr("PKE") = py::cast(PKESchemeFeature::PKE);
     m.attr("KEYSWITCH") = py::cast(PKESchemeFeature::KEYSWITCH);
     m.attr("PRE") = py::cast(PKESchemeFeature::PRE);
@@ -731,7 +886,14 @@ void bind_enums_and_constants(py::module &m)
     m.attr("ADVANCEDSHE") = py::cast(PKESchemeFeature::ADVANCEDSHE);
     m.attr("MULTIPARTY") = py::cast(PKESchemeFeature::MULTIPARTY);
     m.attr("FHE") = py::cast(PKESchemeFeature::FHE);
+    m.attr("SCHEMESWITCH") = py::cast(PKESchemeFeature::SCHEMESWITCH);
 
+    // Plaintext enums
+    py::enum_<Format>(m, "Format")
+        .value("EVALUATION", Format::EVALUATION)
+        .value("COEFFICIENT", Format::COEFFICIENT);
+    m.attr("EVALUATION") = py::cast(Format::EVALUATION);
+    m.attr("COEFFICIENT") = py::cast(Format::COEFFICIENT);
     // Serialization Types
     py::class_<SerType::SERJSON>(m, "SERJSON");
     py::class_<SerType::SERBINARY>(m, "SERBINARY");
@@ -825,6 +987,13 @@ void bind_enums_and_constants(py::module &m)
     m.attr("HPSPOVERQ") = py::cast(MultiplicationTechnique::HPSPOVERQ);
     m.attr("HPSPOVERQLEVELED") = py::cast(MultiplicationTechnique::HPSPOVERQLEVELED);
 
+    // Compression Leval
+    py::enum_<COMPRESSION_LEVEL>(m,"COMPRESSION_LEVEL")
+        .value("COMPACT", COMPRESSION_LEVEL::COMPACT)
+        .value("SLACK", COMPRESSION_LEVEL::SLACK);
+    m.attr("COMPACT") = py::cast(COMPRESSION_LEVEL::COMPACT);
+    m.attr("SLACK") = py::cast(COMPRESSION_LEVEL::SLACK);
+        
     /* ---- CORE enums ---- */ 
     // Security Level
     py::enum_<SecurityLevel>(m,"SecurityLevel")
@@ -878,6 +1047,8 @@ void bind_encodings(py::module &m)
         .def("SetScalingFactor", &PlaintextImpl::SetScalingFactor,
             ptx_SetScalingFactor_docs,
             py::arg("sf"))
+        .def("GetSchemeID", &PlaintextImpl::GetSchemeID,
+            ptx_GetSchemeID_docs)
         .def("GetLength", &PlaintextImpl::GetLength,
             ptx_GetLength_docs)
         .def("GetSchemeID", &PlaintextImpl::GetSchemeID,
@@ -893,11 +1064,29 @@ void bind_encodings(py::module &m)
             ptx_Encode_docs)
         .def("Decode", &PlaintextImpl::Decode,
             ptx_Decode_docs)
+        .def("LowBound", &PlaintextImpl::LowBound,
+            ptx_LowBound_docs)
+        .def("HighBound", &PlaintextImpl::HighBound,
+            ptx_HighBound_docs)
+        .def("SetFormat", &PlaintextImpl::SetFormat,
+            ptx_SetFormat_docs,
+            py::arg("fmt"))
+        .def("GetPackedValue", &PlaintextImpl::GetPackedValue)
         .def("GetCKKSPackedValue", &PlaintextImpl::GetCKKSPackedValue,
             ptx_GetCKKSPackedValue_docs)
-
         .def("GetRealPackedValue", &PlaintextImpl::GetRealPackedValue,
             ptx_GetRealPackedValue_docs)
+        .def("GetLevel", &PlaintextImpl::GetLevel)
+        .def("SetLevel", &PlaintextImpl::SetLevel)
+        .def("GetNoiseScaleDeg", &PlaintextImpl::GetNoiseScaleDeg)
+        .def("SetNoiseScaleDeg", &PlaintextImpl::SetNoiseScaleDeg)
+        .def("GetSlots", &PlaintextImpl::GetSlots)
+        .def("SetSlots", &PlaintextImpl::SetSlots)
+        .def("GetLogError", &PlaintextImpl::GetLogError)
+        .def("GetLogPrecision", &PlaintextImpl::GetLogPrecision)
+        .def("GetStringValue", &PlaintextImpl::GetStringValue)
+        .def("SetStringValue", &PlaintextImpl::SetStringValue)
+        .def("SetIntVectorValue", &PlaintextImpl::SetIntVectorValue)
         .def("__repr__", [](const PlaintextImpl &p)
              {
         std::stringstream ss;
@@ -926,8 +1115,8 @@ void bind_ciphertext(py::module &m)
      .def("SetLevel", &CiphertextImpl<DCRTPoly>::SetLevel,
         ctx_SetLevel_docs,
         py::arg("level"))
-    .def("get_ptr",[](const Ciphertext<DCRTPoly> &self){
-        std::cout<< "cryptoparameters shared ptr (python)" << self->GetCryptoContext().get() << std::endl;});
+     .def("Clone", &CiphertextImpl<DCRTPoly>::Clone)
+     .def("RemoveElement", &RemoveElementWrapper, cc_RemoveElement_docs);
     // .def("GetHopLevel", &CiphertextImpl<DCRTPoly>::GetHopLevel)
     // .def("SetHopLevel", &CiphertextImpl<DCRTPoly>::SetHopLevel)
     // .def("GetScalingFactor", &CiphertextImpl<DCRTPoly>::GetScalingFactor)
@@ -949,6 +1138,11 @@ void bind_schemes(py::module &m){
 PYBIND11_MODULE(openfhe, m)
 {
     m.doc() = "Open-Source Fully Homomorphic Encryption Library";
+    // binfhe library
+    bind_binfhe_enums(m);
+    bind_binfhe_context(m);
+    bind_binfhe_keys(m);
+    bind_binfhe_ciphertext(m);
     // pke library
     bind_enums_and_constants(m);
     bind_parameters<CryptoContextBFVRNS>(m,"CCParamsBFVRNS");
@@ -960,9 +1154,5 @@ PYBIND11_MODULE(openfhe, m)
     bind_crypto_context(m);
     bind_serialization(m);
     bind_schemes(m);
-    // binfhe library
-    bind_binfhe_enums(m);
-    bind_binfhe_context(m);
-    bind_binfhe_keys(m);
-    bind_binfhe_ciphertext(m);
+    
 }

+ 68 - 0
src/lib/binfhe/binfhecontext_wrapper.cpp

@@ -50,3 +50,71 @@ LWEPlaintext binfhe_DecryptWrapper(BinFHEContext &self,
     self.Decrypt(sk, ct, &result, p);
     return result;
 }
+
+uint32_t GetnWrapper(BinFHEContext &self)
+{
+    return self.GetParams()->GetLWEParams()->Getn();
+}
+
+const uint64_t GetqWrapper(BinFHEContext &self)
+{
+    return self.GetParams()->GetLWEParams()->Getq().ConvertToInt<uint64_t>();
+}
+
+const uint64_t GetMaxPlaintextSpaceWrapper(BinFHEContext &self)
+{
+    return self.GetMaxPlaintextSpace().ConvertToInt<uint64_t>();
+}
+
+const uint64_t GetBetaWrapper(BinFHEContext &self)
+{
+    return self.GetBeta().ConvertToInt<uint64_t>();
+}
+
+const uint64_t GetLWECiphertextModulusWrapper(LWECiphertext &self)
+{
+    return self->GetModulus().ConvertToInt<uint64_t>();
+}
+
+// Define static variables to hold the state
+py::function static_f;
+
+// Define a static function that uses the static variables
+NativeInteger StaticFunction(NativeInteger m, NativeInteger p) {
+    // Convert the arguments to int
+    uint64_t m_int = m.ConvertToInt<uint64_t>();
+    uint64_t p_int = p.ConvertToInt<uint64_t>();
+    // Call the Python function
+    py::object result_py = static_f(m_int, p_int);
+    // Convert the result to a NativeInteger
+    return NativeInteger(py::cast<uint64_t>(result_py));
+}
+
+std::vector<uint64_t> GenerateLUTviaFunctionWrapper(BinFHEContext &self, py::function f, uint64_t p)
+{
+    NativeInteger p_native_int = NativeInteger(p);
+    static_f = f;
+    std::vector<NativeInteger> result = self.GenerateLUTviaFunction(StaticFunction, p_native_int);
+    std::vector<uint64_t> result_uint64_t;
+    // int size_int = static_cast<int>(result.size());
+    for (const auto& value : result)
+    {
+        result_uint64_t.push_back(value.ConvertToInt<uint64_t>());
+    }
+    return result_uint64_t;
+}
+
+// LWECiphertext EvalFunc(ConstLWECiphertext &ct, const std::vector<NativeInteger> &LUT) const
+LWECiphertext EvalFuncWrapper(BinFHEContext &self, ConstLWECiphertext &ct, const std::vector<uint64_t> &LUT)
+{
+    std::vector<NativeInteger> LUT_native_int;
+    LUT_native_int.reserve(LUT.size());  // Reserve space for the elements
+    for (const auto& value : LUT)
+    {
+        LUT_native_int.push_back(NativeInteger(value));
+    }
+    return self.EvalFunc(ct, LUT_native_int);
+}
+
+
+

+ 37 - 2
src/lib/binfhe_bindings.cpp

@@ -92,10 +92,12 @@ void bind_binfhe_enums(py::module &m)
     py::enum_<BINFHE_METHOD>(m, "BINFHE_METHOD")
         .value("INVALID_METHOD", BINFHE_METHOD::INVALID_METHOD)
         .value("AP", BINFHE_METHOD::AP)
-        .value("GINX", BINFHE_METHOD::GINX);
+        .value("GINX", BINFHE_METHOD::GINX)
+        .value("LMKCDEY", BINFHE_METHOD::LMKCDEY);
     m.attr("INVALID_METHOD") = py::cast(BINFHE_METHOD::INVALID_METHOD);
     m.attr("GINX") = py::cast(BINFHE_METHOD::GINX);
     m.attr("AP") = py::cast(BINFHE_METHOD::AP);
+    m.attr("LMKCDEY") = py::cast(BINFHE_METHOD::LMKCDEY);
 
     py::enum_<KEYGEN_MODE>(m, "KEYGEN_MODE")
         .value("SYM_ENCRYPT", KEYGEN_MODE::SYM_ENCRYPT)
@@ -144,6 +146,7 @@ void bind_binfhe_ciphertext(py::module &m)
     py::class_<LWECiphertextImpl, std::shared_ptr<LWECiphertextImpl>>(m, "LWECiphertext")
         .def(py::init<>())
         .def("GetLength", &LWECiphertextImpl::GetLength)
+        .def("GetModulus", &GetLWECiphertextModulusWrapper)
         .def(py::self == py::self)
         .def(py::self != py::self);
 }
@@ -156,6 +159,15 @@ void bind_binfhe_context(py::module &m)
              binfhe_GenerateBinFHEContext_parset_docs,
              py::arg("set"),
              py::arg("method") = GINX)
+        //void GenerateBinFHEContext(BINFHE_PARAMSET set, bool arbFunc, uint32_t logQ = 11, int64_t N = 0, BINFHE_METHOD method = GINX, bool timeOptimization = false)
+        .def("GenerateBinFHEContext", static_cast<void (BinFHEContext::*)(BINFHE_PARAMSET, bool, uint32_t, int64_t, BINFHE_METHOD, bool)>(&BinFHEContext::GenerateBinFHEContext),
+             binfhe_GenerateBinFHEContext_docs,
+             py::arg("set"),
+             py::arg("arbFunc"),
+             py::arg("logQ") = 11,
+             py::arg("N") = 0,
+             py::arg("method") = GINX,
+             py::arg("timeOptimization") = false)
         .def("KeyGen", &BinFHEContext::KeyGen,
              binfhe_KeyGen_docs)
         .def("BTKeyGen", &BinFHEContext::BTKeyGen,
@@ -182,5 +194,28 @@ void bind_binfhe_context(py::module &m)
              py::arg("ct2"))
         .def("EvalNOT", &BinFHEContext::EvalNOT,
              binfhe_EvalNOT_docs,
-             py::arg("ct"));
+             py::arg("ct"))
+        .def("Getn",&GetnWrapper)
+        .def("Getq",&GetqWrapper)
+        .def("GetMaxPlaintextSpace",&GetMaxPlaintextSpaceWrapper)
+        .def("GetBeta",&GetBetaWrapper)
+        .def("EvalDecomp",&BinFHEContext::EvalDecomp,
+             binfhe_EvalDecomp_docs,
+             py::arg("ct"))
+        .def("EvalFloor",&BinFHEContext::EvalFloor,
+             binfhe_EvalFloor_docs,
+             py::arg("ct"),
+             py::arg("roundbits") = 0)
+        .def("GenerateLUTviaFunction",&GenerateLUTviaFunctionWrapper,
+             binfhe_GenerateLUTviaFunction_docs,
+             py::arg("f"),
+             py::arg("p"))
+        .def("EvalFunc",&EvalFuncWrapper,
+             binfhe_EvalFunc_docs,
+             py::arg("ct"),
+             py::arg("LUT"))
+          .def("EvalSign",&BinFHEContext::EvalSign,
+               binfhe_EvalSign_docs,
+             py::arg("ct"),
+             py::arg("schemeSwitch") = false);
 }

+ 58 - 0
src/lib/pke/cryptocontext_wrapper.cpp

@@ -96,3 +96,61 @@ const PlaintextModulus GetPlaintextModulusWrapper(CryptoContext<DCRTPoly>& self)
 const double GetModulusWrapper(CryptoContext<DCRTPoly>& self){
     return self->GetCryptoParameters()->GetElementParams()->GetModulus().ConvertToDouble();
 }
+
+void RemoveElementWrapper(Ciphertext<DCRTPoly> &self, usint index){
+    self->GetElements().erase(self->GetElements().begin()+index);
+}
+const usint GetDigitSizeWrapper(CryptoContext<DCRTPoly>& self){
+    return self->GetCryptoParameters()->GetDigitSize();
+}
+
+const double GetScalingFactorRealWrapper(CryptoContext<DCRTPoly>& self, uint32_t l){
+    if(self->getSchemeId()==SCHEME::CKKSRNS_SCHEME){
+        const auto cryptoParams = std::dynamic_pointer_cast<CryptoParametersCKKSRNS>(self->GetCryptoParameters());
+        double scFactor = cryptoParams->GetScalingFactorReal(l);
+        return scFactor;
+    }
+    else if(self->getSchemeId()==SCHEME::BFVRNS_SCHEME){
+        const auto cryptoParams = std::dynamic_pointer_cast<CryptoParametersBFVRNS>(self->GetCryptoParameters());
+        double scFactor = cryptoParams->GetScalingFactorReal(l);
+        return scFactor;
+    }
+    else if(self->getSchemeId()==SCHEME::BGVRNS_SCHEME){
+        const auto cryptoParams = std::dynamic_pointer_cast<CryptoParametersBGVRNS>(self->GetCryptoParameters());
+        double scFactor = cryptoParams->GetScalingFactorReal(l);
+        return scFactor;
+    }
+    else{
+        OPENFHE_THROW(not_available_error, "GetScalingFactorRealWrapper: Invalid scheme");
+        return 0;
+    }
+}
+
+const uint64_t GetModulusCKKSWrapper(CryptoContext<DCRTPoly> &self)
+{
+
+    const auto cryptoParams = std::dynamic_pointer_cast<CryptoParametersCKKSRNS>(self->GetCryptoParameters());
+    ILDCRTParams<DCRTPoly::Integer> elementParams = *(cryptoParams->GetElementParams());
+    auto paramsQ = elementParams.GetParams();
+    uint64_t modulus_CKKS_from = paramsQ[0]->GetModulus().ConvertToInt<uint64_t>();
+    return modulus_CKKS_from;
+}
+
+const ScalingTechnique GetScalingTechniqueWrapper(CryptoContext<DCRTPoly> & self){
+    if(self->getSchemeId()==SCHEME::CKKSRNS_SCHEME){
+        const auto cryptoParams = std::dynamic_pointer_cast<CryptoParametersCKKSRNS>(self->GetCryptoParameters());
+        return cryptoParams->GetScalingTechnique();
+    }
+    else if(self->getSchemeId()==SCHEME::BFVRNS_SCHEME){
+        const auto cryptoParams = std::dynamic_pointer_cast<CryptoParametersBFVRNS>(self->GetCryptoParameters());
+        return cryptoParams->GetScalingTechnique();
+    }
+    else if(self->getSchemeId()==SCHEME::BGVRNS_SCHEME){
+        const auto cryptoParams = std::dynamic_pointer_cast<CryptoParametersBGVRNS>(self->GetCryptoParameters());
+        return cryptoParams->GetScalingTechnique();
+    }
+    else{
+        OPENFHE_THROW(not_available_error, "GetScalingTechniqueWrapper: Invalid scheme");
+    }
+
+}