Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scalar product between plain vector and cipher vector #176

Closed
yooopan opened this issue Mar 13, 2023 · 3 comments
Closed

scalar product between plain vector and cipher vector #176

yooopan opened this issue Mar 13, 2023 · 3 comments
Labels
Functionality Wether this library supports a certain operation or not

Comments

@yooopan
Copy link
Contributor

yooopan commented Mar 13, 2023

scalar product between plain vector and cipher vector

I need calculate scalar product between plain vector and cipher vector:

import numpy as np
rng = np.random.default_rng(42)

CASE_SELECTOR = 2         # 1 or 2

case_params = {
    1: {'l': 256},         # small l
    2: {'l': 65536},       # large l
}[CASE_SELECTOR]
l = case_params['l']


from Pyfhel import Pyfhel

def get_CKKS_context_scalar_prod(l: int, sec: int=128, use_n_min: bool=True) ->Pyfhel:
    n_min =  2**13
    if use_n_min:           n = n_min    # use n_min regardless of l
    elif 2*l < n_min:       n = n_min    # Smallest
    elif 2*l > 2**15:       n = 2**15    # Largest
    else:                   n = get_closest_power_of_two(2*l)
    context_params = {
        'scheme': 'CKKS',
        'n': n,          # Poly modulus degree. BFV ptxt is a n//2 by 2 matrix.
        'sec': sec,      # Security level.
        'scale': 2**20, 
        'qi_sizes': [60, 20, 60],   # Max number of multiplications = 1
    }
    HE = Pyfhel(context_params)
    return HE


def scalar_product_cc(HE, v1, v2):
    # Encrypt each vector into one (if l <= n_slots) or several ciphertexts (l>n)
    c1 = [HE.encrypt(v1[j:j+HE.get_nSlots()]) for j in range(0,l,HE.get_nSlots())]
    c2 = [HE.encrypt(v2[j:j+HE.get_nSlots()]) for j in range(0,l,HE.get_nSlots())]

    if len(c1)==len(c2)==1:
        cRes = c1[0] @ c2[0]
    else:
        cRes = [~(c1[i]*c2[i]) for i in range(len(c1))]
        for i in range(1,len(cRes)):
            cRes[0] += cRes[i]
        cRes = HE.cumul_add(cRes[0])
    return cRes

def scalar_product_pc(HE, v1, v2):
    # Encrypt each vector into one (if l <= n_slots) or several ciphertexts (l>n)
    # c1 = [HE.encrypt(v1[j:j+HE.get_nSlots()]) for j in range(0,l,HE.get_nSlots())]
    c1 = v1
    c2 = [HE.encrypt(v2[j:j+HE.get_nSlots()]) for j in range(0,l,HE.get_nSlots())]


    if len(c1)==len(c2)==1:
        cRes = c1[0] @ c2[0]
    else:
        cRes = [~(c1[i]*c2[i]) for i in range(len(c1))]
        for i in range(1,len(cRes)):
            cRes[0] += cRes[i]
        cRes = HE.cumul_add(cRes[0])
    return cRes

if __name__ == '__main__':
    HE = get_CKKS_context_scalar_prod(l, sec=128, use_n_min=True)
    HE.keyGen()
    HE.relinKeyGen()
    HE.rotateKeyGen()


    v1 = np.random.rand(768)
    v2 = np.random.rand(768)

    # v1 = rng.normal(loc=0, scale=100, size=l)    # Not bounded!
    # v2 = rng.normal(loc=0, scale=100, size=l)
    print(type(v1))
    spRes = v1@v2
    print("Plain Res:", spRes)

    cRes = scalar_product_cc(HE, v1, v2)
    res = HE.decrypt(cRes)[0]
    print("HE Res:", res)

    cRes = scalar_product_pc(HE, v1, v2)
    res = HE.decrypt(cRes)[0]

    print("HE Res:", res)

output:

Plain Res: 193.53197697047068
HE Res: 193.6078537235427
Traceback (most recent call last):
  File "/home/yaopan/Documents/PPTextSim/he_util.py", line 79, in <module>
    cRes = scalar_product_pc(HE, v1, v2)
  File "/home/yaopan/Documents/PPTextSim/he_util.py", line 56, in scalar_product_pc
    cRes = [~(c1[i]*c2[i]) for i in range(len(c1))]
  File "/home/yaopan/Documents/PPTextSim/he_util.py", line 56, in <listcomp>
    cRes = [~(c1[i]*c2[i]) for i in range(len(c1))]
IndexError: list index out of range

I honestly do not understand how to deal plain vector v1 at step "Encrypt each vector into one (if l <= n_slots) or several ciphertexts (l>n) ".

@yooopan yooopan added the Functionality Wether this library supports a certain operation or not label Mar 13, 2023
@ibarrond
Copy link
Owner

Your best bet is to encode the plaintext vectors:

def scalar_product_pc(HE, v1, v2):
    # Encrypt each vector into one (if l <= n_slots) or several ciphertexts (l>n)
    p1 = [HE.encode(v1[j:j+HE.get_nSlots()]) for j in range(0,l,HE.get_nSlots())]
    c2 = [HE.encrypt(v2[j:j+HE.get_nSlots()]) for j in range(0,l,HE.get_nSlots())]


    if len(p1)==len(c2)==1:
        cRes = p1 [0] @ c2[0]
    else:
        cRes = [~(p1[i]*c2[i]) for i in range(len(c1))]
        for i in range(1,len(cRes)):
            cRes[0] += cRes[i]
        cRes = HE.cumul_add(cRes[0])
    return cRes

@yooopan
Copy link
Contributor Author

yooopan commented Mar 15, 2023

Your best bet is to encode the plaintext vectors:

def scalar_product_pc(HE, v1, v2):
    # Encrypt each vector into one (if l <= n_slots) or several ciphertexts (l>n)
    p1 = [HE.encode(v1[j:j+HE.get_nSlots()]) for j in range(0,l,HE.get_nSlots())]
    c2 = [HE.encrypt(v2[j:j+HE.get_nSlots()]) for j in range(0,l,HE.get_nSlots())]


    if len(p1)==len(c2)==1:
        cRes = p1 [0] @ c2[0]
    else:
        cRes = [~(p1[i]*c2[i]) for i in range(len(c1))]
        for i in range(1,len(cRes)):
            cRes[0] += cRes[i]
        cRes = HE.cumul_add(cRes[0])
    return cRes

I encode v1 and meet RuntimeError: result ciphertext is transparent error:

import numpy as np
rng = np.random.default_rng(42)

CASE_SELECTOR = 2         # 1 or 2

case_params = {
    1: {'l': 256},         # small l
    2: {'l': 65536},       # large l
}[CASE_SELECTOR]
l = case_params['l']


from Pyfhel import Pyfhel

def get_CKKS_context_scalar_prod(l: int, sec: int=128, use_n_min: bool=True) ->Pyfhel:
    n_min =  2**13
    if use_n_min:           n = n_min    # use n_min regardless of l
    elif 2*l < n_min:       n = n_min    # Smallest
    elif 2*l > 2**15:       n = 2**15    # Largest
    else:                   n = get_closest_power_of_two(2*l)
    context_params = {
        'scheme': 'CKKS',
        'n': n,          # Poly modulus degree. BFV ptxt is a n//2 by 2 matrix.
        'sec': sec,      # Security level.
        'scale': 2**20, 
        'qi_sizes': [60, 20, 60],   # Max number of multiplications = 1
    }
    HE = Pyfhel(context_params)
    return HE


def scalar_product_cc(HE, v1, v2):
    # Encrypt each vector into one (if l <= n_slots) or several ciphertexts (l>n)
    c1 = [HE.encrypt(v1[j:j+HE.get_nSlots()]) for j in range(0,l,HE.get_nSlots())]
    c2 = [HE.encrypt(v2[j:j+HE.get_nSlots()]) for j in range(0,l,HE.get_nSlots())]

    if len(c1)==len(c2)==1:
        cRes = c1[0] @ c2[0]
    else:
        cRes = [~(c1[i]*c2[i]) for i in range(len(c1))]
        for i in range(1,len(cRes)):
            cRes[0] += cRes[i]
        cRes = HE.cumul_add(cRes[0])
    return cRes

def scalar_product_pc(HE, v1, v2):
    # Encrypt each vector into one (if l <= n_slots) or several ciphertexts (l>n)
    p1 = [HE.encode(v1[j:j+HE.get_nSlots()]) for j in range(0,l,HE.get_nSlots())]
    c2 = [HE.encrypt(v2[j:j+HE.get_nSlots()]) for j in range(0,l,HE.get_nSlots())]


    if len(p1)==len(c2)==1:
        cRes = p1 [0] @ c2[0]
    else:
        cRes = [~(p1[i]*c2[i]) for i in range(len(p1))]
        for i in range(1,len(cRes)):
            cRes[0] += cRes[i]
        cRes = HE.cumul_add(cRes[0])
    return cRes

if __name__ == '__main__':
    HE = get_CKKS_context_scalar_prod(l, sec=128, use_n_min=True)
    HE.keyGen()
    HE.relinKeyGen()
    HE.rotateKeyGen()


    v1 = np.random.rand(768)
    v2 = np.random.rand(768)


    spRes = v1@v2
    print("Plain Res:", spRes)

    cRes = scalar_product_cc(HE, v1, v2)
    res = HE.decrypt(cRes)[0]
    print("HE Res bwtween cipher and cipher:", res)

    cRes = scalar_product_pc(HE, v1, v2)
    res = HE.decrypt(cRes)[0]

    print("HE Res bwtween plaintext and cipher:", res)

error info:

Plain Res: 198.24648958483448
HE Res bwtween cipher and cipher: 198.24917665919565
---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
Cell In[13], line 79
     76 res = HE.decrypt(cRes)[0]
     77 print("HE Res bwtween cipher and cipher:", res)
---> 79 cRes = scalar_product_pc(HE, v1, v2)
     80 res = HE.decrypt(cRes)[0]
     82 print("HE Res bwtween plaintext and cipher:", res)

Cell In[13], line 55, in scalar_product_pc(HE, v1, v2)
     53     cRes = p1 [0] @ c2[0]
     54 else:
---> 55     cRes = [~(p1[i]*c2[i]) for i in range(len(p1))]
     56     for i in range(1,len(cRes)):
     57         cRes[0] += cRes[i]

Cell In[13], line 55, in <listcomp>(.0)
     53     cRes = p1 [0] @ c2[0]
     54 else:
---> 55     cRes = [~(p1[i]*c2[i]) for i in range(len(p1))]
     56     for i in range(1,len(cRes)):
     57         cRes[0] += cRes[i]

File Pyfhel/PyCtxt.pyx:473, in Pyfhel.PyCtxt.PyCtxt.__rmul__()

File Pyfhel/PyCtxt.pyx:468, in Pyfhel.PyCtxt.PyCtxt.__mul__()

File Pyfhel/Pyfhel.pyx:1224, in Pyfhel.Pyfhel.Pyfhel.multiply_plain()

RuntimeError: result ciphertext is transparent

@ibarrond
Copy link
Owner

ibarrond commented Mar 20, 2023

This basically means that you are multiplying a ciphertext with a plaintext full of zeros. This is bad for security. If you know that the ciphertext is full of zeros, operate in cleartext directly (p1 * p2). If you don't know this and you still wish to proceed despite the security implications, then you can solve the error following #124 . Note that you can also find more info by searching for that error in the SEAL repository.

Looking at your code, you are generating random vectors of size 768, but then you encrypt vectors with a total length of l=65536, meaning that the last 65536-768 positions are filled with zeros, and thus many of your vectors are filled with zeros.

To avoid the problem completely, it suffices to:

  1. Either generate random vectors with the size you are considering in the operations
#[...]
v1 = np.random.rand(l)
v2 = np.random.rand(l)
#[...]
  1. Or set l=768, the actual input length of your vectors
l = 768
HE = get_CKKS_context_scalar_prod(l, sec=128, use_n_min=True)
# [...]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Functionality Wether this library supports a certain operation or not
Projects
None yet
Development

No branches or pull requests

2 participants