// 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); } } }