// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Research.SEAL.Tools;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
namespace Microsoft.Research.SEAL
{
///
/// Provides functionality for encoding vectors of complex or real numbers into plaintext
/// polynomials to be encrypted and computed on using the CKKS scheme. If the polynomial
/// modulus degree is N, then CKKSEncoder converts vectors of N/2 complex numbers into
/// plaintext elements. Homomorphic operations performed on such encrypted vectors are
/// applied coefficient (slot-)wise, enabling powerful SIMD functionality for computations
/// that are vectorizable. This functionality is often called "batching" in the homomorphic
/// encryption literature.
///
///
///
/// Mathematical Background
/// Mathematically speaking, if the polynomial modulus is X^N+1, N is a power of two, the
/// CKKSEncoder implements an approximation of the canonical embedding of the ring of
/// integers Z[X]/(X^N+1) into C^(N/2), where C denotes the complex numbers. The Galois
/// group of the extension is (Z/2NZ)* ~= Z/2Z x Z/(N/2) whose action on the primitive roots
/// of unity modulo CoeffModulus is easy to describe. Since the batching slots correspond
/// 1-to-1 to the primitive roots of unity, applying Galois automorphisms on the plaintext
/// acts by permuting the slots. By applying generators of the two cyclic subgroups of the
/// Galois group, we can effectively enable cyclic rotations and complex conjugations of
/// the encrypted complex vectors.
///
///
public class CKKSEncoder : NativeObject
{
///
/// Creates a CKKSEncoder instance initialized with the specified SEALContext.
///
/// The SEALContext
/// if context is null
/// if the context is not set or encryption parameters
/// are not valid
/// if scheme is not SchemeType.CKKS
public CKKSEncoder(SEALContext context)
{
if (null == context)
throw new ArgumentNullException(nameof(context));
if (!context.ParametersSet)
throw new ArgumentException("Encryption parameters are not set correctly");
SEALContext.ContextData contextData = context.FirstContextData;
if (contextData.Parms.Scheme != SchemeType.CKKS)
throw new ArgumentException("Unsupported scheme");
NativeMethods.CKKSEncoder_Create(context.NativePtr, out IntPtr ptr);
NativePtr = ptr;
context_ = context;
}
///
/// Encodes a vector of double-precision floating-point real numbers into a plaintext
/// polynomial.
///
///
/// Append zeros if vector size is less than N/2. Dynamic memory allocations in the process
/// are allocated from the memory pool pointed to by the given MemoryPoolHandle.
///
/// The enumeration of double-precision floating-point numbers
/// to encode
/// parmsId determining the encryption parameters to be used
/// by the result plaintext
/// Scaling parameter defining encoding precision
/// The plaintext polynomial to overwrite with the result
/// The MemoryPoolHandle pointing to a valid memory pool
/// if either values, parmsId or destionation are null.
/// if values has invalid size
/// if parmsId is not valid for the encryption
/// parameters
/// if scale is not strictly positive
/// if encoding is too large for the encryption
/// parameters
/// if pool is uninitialized
public void Encode(IEnumerable values, ParmsId parmsId,
double scale, Plaintext destination, MemoryPoolHandle pool = null)
{
if (null == values)
throw new ArgumentNullException(nameof(values));
if (null == parmsId)
throw new ArgumentNullException(nameof(parmsId));
if (null == destination)
throw new ArgumentNullException(nameof(destination));
IntPtr poolPtr = pool?.NativePtr ?? IntPtr.Zero;
double[] valuearray = values.ToArray();
NativeMethods.CKKSEncoder_EncodeDouble(NativePtr, (ulong)valuearray.LongLength, valuearray,
parmsId.Block, scale, destination.NativePtr, poolPtr);
}
///
/// Encodes a vector of double-precision floating-point complex numbers into a plaintext
/// polynomial.
///
///
/// Append zeros if vector size is less than N/2. Dynamic memory allocations in the process
/// are allocated from the memory pool pointed to by the given MemoryPoolHandle.
///
/// The enumeration of double-precision complex numbers
/// to encode
/// parmsId determining the encryption parameters to be used
/// by the result plaintext
/// Scaling parameter defining encoding precision
/// The plaintext polynomial to overwrite with the result
/// The MemoryPoolHandle pointing to a valid memory pool
/// if either values, parmsId or destionation are null.
/// if values has invalid size
/// if parmsId is not valid for the encryption
/// parameters
/// if scale is not strictly positive
/// if encoding is too large for the encryption
/// parameters
/// if pool is uninitialized
public void Encode(IEnumerable values, ParmsId parmsId,
double scale, Plaintext destination, MemoryPoolHandle pool = null)
{
if (null == values)
throw new ArgumentNullException(nameof(values));
if (null == parmsId)
throw new ArgumentNullException(nameof(parmsId));
if (null == destination)
throw new ArgumentNullException(nameof(destination));
IntPtr poolPtr = pool?.NativePtr ?? IntPtr.Zero;
double[] valuearray = new double[values.LongCount() * 2];
ulong idx = 0;
foreach(Complex complex in values)
{
valuearray[idx++] = complex.Real;
valuearray[idx++] = complex.Imaginary;
}
// Note that we should pass values.Count as the length instead of valuearray.Length,
// since we are using two doubles in the array per element.
NativeMethods.CKKSEncoder_EncodeComplex(NativePtr, (ulong)values.LongCount(), valuearray,
parmsId.Block, scale, destination.NativePtr, poolPtr);
}
///
/// Encodes a vector of double-precision floating-point real numbers into a plaintext
/// polynomial.
///
///
/// Append zeros if vector size is less than N/2. Dynamic memory allocations in the process
/// are allocated from the memory pool pointed to by the given MemoryPoolHandle.
/// The encryption parameters used are the top level parameters for the given context.
///
/// The enumeration of double-precision floating-point numbers
/// to encode
/// Scaling parameter defining encoding precision
/// The plaintext polynomial to overwrite with the result
/// The MemoryPoolHandle pointing to a valid memory pool
/// if either values or destionation are null.
/// if values has invalid size
/// if scale is not strictly positive
/// if encoding is too large for the encryption
/// parameters
/// if pool is uninitialized
public void Encode(IEnumerable values, double scale,
Plaintext destination, MemoryPoolHandle pool = null)
{
Encode(values, context_.FirstParmsId, scale, destination, pool);
}
///
/// Encodes a vector of double-precision floating-point complex numbers into a plaintext
/// polynomial.
///
///
/// Append zeros if vector size is less than N/2. Dynamic memory allocations in the process
/// are allocated from the memory pool pointed to by the given MemoryPoolHandle.
/// The encryption parameters used are the top level parameters for the given context.
///
/// The enumeration of double-precision floating-point numbers
/// to encode
/// Scaling parameter defining encoding precision
/// The plaintext polynomial to overwrite with the result
/// The MemoryPoolHandle pointing to a valid memory pool
/// if either values or destionation are null.
/// if values has invalid size
/// if scale is not strictly positive
/// if encoding is too large for the encryption
/// parameters
/// if pool is uninitialized
public void Encode(IEnumerable values, double scale,
Plaintext destination, MemoryPoolHandle pool = null)
{
Encode(values, context_.FirstParmsId, scale, destination, pool);
}
///
/// Encodes a double-precision floating-point real number into a plaintext polynomial.
///
///
/// The number repeats for N/2 times to fill all slots. Dynamic memory allocations in the
/// process are allocated from the memory pool pointed to by the given MemoryPoolHandle.
///
/// The double-precision floating-point number to encode
/// parmsId determining the encryption parameters to be used
/// by the result plaintext
/// Scaling parameter defining encoding precision
/// The plaintext polynomial to overwrite with the result
/// The MemoryPoolHandle pointing to a valid memory pool
/// if either parmsId or destination are null
/// if parmsId is not valid for the encryption
/// parameters
/// if scale is not strictly positive
/// if encoding is too large for the encryption
/// parameters
/// if pool is uninitialized
public void Encode(double value, ParmsId parmsId,
double scale, Plaintext destination,
MemoryPoolHandle pool = null)
{
if (null == parmsId)
throw new ArgumentNullException(nameof(parmsId));
if (null == destination)
throw new ArgumentNullException(nameof(destination));
IntPtr poolPtr = pool?.NativePtr ?? IntPtr.Zero;
NativeMethods.CKKSEncoder_Encode(NativePtr, value, parmsId.Block, scale, destination.NativePtr, poolPtr);
}
///
/// Encodes a double-precision floating-point real number into a plaintext polynomial.
///
///
/// The number repeats for N/2 times to fill all slots. Dynamic memory allocations in the
/// process are allocated from the memory pool pointed to by the given MemoryPoolHandle.
/// The encryption parameters used are the top level parameters for the given context.
///
/// The double-precision floating-point number to encode
/// Scaling parameter defining encoding precision
/// The plaintext polynomial to overwrite with the result
/// The MemoryPoolHandle pointing to a valid memory pool
/// if destination is null
/// if scale is not strictly positive
/// if encoding is too large for the encryption
/// parameters
/// if pool is uninitialized
public void Encode(double value, double scale, Plaintext destination,
MemoryPoolHandle pool = null)
{
Encode(value, context_.FirstParmsId, scale, destination, pool);
}
///
/// Encodes a double-precision floating-point complex number into a plaintext polynomial.
///
///
/// The number repeats for N/2 times to fill all slots. Dynamic memory allocations in the
/// process are allocated from the memory pool pointed to by the given MemoryPoolHandle.
///
/// The double-precision complex number to encode
/// parmsId determining the encryption parameters to be used
/// by the result plaintext
/// Scaling parameter defining encoding precision
/// The plaintext polynomial to overwrite with the result
/// The MemoryPoolHandle pointing to a valid memory pool
/// if either parmsId or destination are null
/// if parmsId is not valid for the encryption
/// parameters
/// if scale is not strictly positive
/// if encoding is too large for the encryption
/// parameters
/// if pool is uninitialized
public void Encode(Complex value, ParmsId parmsId, double scale,
Plaintext destination, MemoryPoolHandle pool = null)
{
if (null == parmsId)
throw new ArgumentNullException(nameof(parmsId));
if (null == destination)
throw new ArgumentNullException(nameof(destination));
IntPtr poolPtr = pool?.NativePtr ?? IntPtr.Zero;
NativeMethods.CKKSEncoder_Encode(NativePtr, value.Real, value.Imaginary, parmsId.Block, scale, destination.NativePtr, poolPtr);
}
///
/// Encodes a double-precision floating-point complex number into a plaintext polynomial.
///
///
/// The number repeats for N/2 times to fill all slots. Dynamic memory allocations in the
/// process are allocated from the memory pool pointed to by the given MemoryPoolHandle.
/// The encryption parameters used are the top level parameters for the given context.
///
/// The double-precision complex number to encode
/// Scaling parameter defining encoding precision
/// The plaintext polynomial to overwrite with the result
/// The MemoryPoolHandle pointing to a valid memory pool
/// if destination is null
/// if scale is not strictly positive
/// if encoding is too large for the encryption
/// parameters
/// if pool is uninitialized
public void Encode(Complex value, double scale, Plaintext destination,
MemoryPoolHandle pool = null)
{
Encode(value, context_.FirstParmsId, scale, destination, pool);
}
///
/// Encodes an integer number into a plaintext polynomial without any scaling.
///
///
/// The number repeats for N/2 times to fill all slots.
///
/// The integer number to encode
/// parmsId determining the encryption parameters to be used
/// by the result plaintext
/// The plaintext polynomial to overwrite with the result
/// if either parmsId or destionation are null
/// if parmsId is not valid for the encryption
/// parameters
public void Encode(long value, ParmsId parmsId, Plaintext destination)
{
if (null == parmsId)
throw new ArgumentNullException(nameof(parmsId));
if (null == destination)
throw new ArgumentNullException(nameof(destination));
NativeMethods.CKKSEncoder_Encode(NativePtr, value, parmsId.Block, destination.NativePtr);
}
///
/// Encodes an integer number into a plaintext polynomial without any scaling.
///
///
/// The number repeats for N/2 times to fill all slots. The encryption parameters used are
/// the top level parameters for the given context.
///
/// The integer number to encode
/// The plaintext polynomial to overwrite with the result
/// if destination is null
public void Encode(long value, Plaintext destination)
{
Encode(value, context_.FirstParmsId, destination);
}
///
/// Decodes a plaintext polynomial into double-precision floating-point real numbers.
///
///
/// Dynamic memory allocations in the process are allocated from the memory pool pointed to
/// by the given MemoryPoolHandle.
///
/// plain The plaintext to decode
/// The collection to be overwritten with the values in the slots
/// The MemoryPoolHandle pointing to a valid memory pool
/// if either plain or destination are null
/// if plain is not in NTT form or is invalid for the
/// encryption parameters
/// if pool is uninitialized
public void Decode(Plaintext plain, ICollection destination,
MemoryPoolHandle pool = null)
{
if (null == plain)
throw new ArgumentNullException(nameof(plain));
if (null == destination)
throw new ArgumentNullException(nameof(destination));
IntPtr poolPtr = pool?.NativePtr ?? IntPtr.Zero;
ulong destCount = 0;
// Allocate a big enough array to hold the result
double[] destArray = new double[SlotCount];
NativeMethods.CKKSEncoder_DecodeDouble(NativePtr, plain.NativePtr, ref destCount, destArray, poolPtr);
// Transfer result to actual destination; only destArray many slots were filled
destination.Clear();
for (ulong i = 0; i < destCount; i++)
{
destination.Add(destArray[i]);
}
}
///
/// Decodes a plaintext polynomial into double-precision floating-point complex numbers.
///
///
/// Dynamic memory allocations in the process are allocated from the memory pool pointed to
/// by the given MemoryPoolHandle.
///
/// plain The plaintext to decode
/// The collection to be overwritten with the values in the slots
/// The MemoryPoolHandle pointing to a valid memory pool
/// if either plain or destination are null
/// if plain is not in NTT form or is invalid for the
/// encryption parameters
/// if pool is uninitialized
public void Decode(Plaintext plain, ICollection destination,
MemoryPoolHandle pool = null)
{
if (null == plain)
throw new ArgumentNullException(nameof(plain));
if (null == destination)
throw new ArgumentNullException(nameof(destination));
IntPtr poolPtr = pool?.NativePtr ?? IntPtr.Zero;
ulong destCount = 0;
// Allocate a big enough array to hold the result
double[] destArray = new double[SlotCount * 2];
NativeMethods.CKKSEncoder_DecodeComplex(NativePtr, plain.NativePtr, ref destCount, destArray, poolPtr);
// Transfer result to actual destination
destination.Clear();
for (ulong i = 0; i < destCount; i++)
{
destination.Add(new Complex(destArray[i * 2], destArray[i * 2 + 1]));
}
}
///
/// Returns the number of complex numbers encoded.
///
public ulong SlotCount
{
get
{
NativeMethods.CKKSEncoder_SlotCount(NativePtr, out ulong slotCount);
return slotCount;
}
}
///
/// SEALContext for this encoder
///
private readonly SEALContext context_ = null;
///
/// Destroy native object
///
protected override void DestroyNativeObject()
{
NativeMethods.CKKSEncoder_Destroy(NativePtr);
}
}
}