Source

encryptor.ts

import { CipherText, CipherTextConstructorOptions } from './cipher-text'
import { Context } from './context'
import { Exception, SealError } from './exception'
import { MemoryPoolHandle } from './memory-pool-handle'
import { PlainText } from './plain-text'
import { PublicKey } from './public-key'
import { Instance, Library, LoaderOptions } from './seal'
import { SecretKey } from './secret-key'
import { Serializable, SerializableConstructorOptions } from './serializable'

export type EncryptorDependencyOptions = {
  readonly Exception: Exception
  readonly MemoryPoolHandle: MemoryPoolHandle
  readonly CipherText: CipherTextConstructorOptions
  readonly Serializable: SerializableConstructorOptions
}

export type EncryptorDependencies = {
  ({
    Exception,
    MemoryPoolHandle,
    CipherText,
    Serializable
  }: EncryptorDependencyOptions): EncryptorConstructorOptions
}

export type EncryptorConstructorOptions = {
  (context: Context, publicKey: PublicKey, secretKey?: SecretKey): Encryptor
}

export type Encryptor = {
  readonly instance: Instance
  readonly unsafeInject: (instance: Instance) => void
  readonly delete: () => void
  readonly encrypt: (
    plainText: PlainText,
    cipherText?: CipherText,
    pool?: MemoryPoolHandle
  ) => CipherText | void
  readonly encryptSerializable: (
    plainText: PlainText,
    pool?: MemoryPoolHandle
  ) => Serializable
  readonly encryptSymmetric: (
    plainText: PlainText,
    cipherText?: CipherText,
    pool?: MemoryPoolHandle
  ) => CipherText | void
  readonly encryptSymmetricSerializable: (
    plainText: PlainText,
    pool?: MemoryPoolHandle
  ) => Serializable
  readonly encryptZero: (
    cipherText?: CipherText,
    pool?: MemoryPoolHandle
  ) => CipherText | void
  readonly encryptZeroSerializable: (pool?: MemoryPoolHandle) => Serializable
}

const EncryptorConstructor =
  (library: Library): EncryptorDependencies =>
  ({
    Exception,
    MemoryPoolHandle,
    CipherText,
    Serializable
  }: EncryptorDependencyOptions): EncryptorConstructorOptions =>
  (context, publicKey, secretKey): Encryptor => {
    const Constructor = library.Encryptor
    let _instance = constructInstance(context, publicKey, secretKey)

    function constructInstance(
      context: Context,
      publicKey: PublicKey,
      secretKey?: SecretKey
    ) {
      try {
        if (secretKey) {
          return new Constructor(
            context.instance,
            publicKey.instance,
            secretKey.instance
          )
        }
        return new Constructor(context.instance, publicKey.instance)
      } catch (e) {
        throw Exception.safe(e as SealError)
      }
    }
    /**
     * @implements Encryptor
     */

    /**
     * @interface Encryptor
     */
    return {
      /**
       * Get the underlying WASM instance
       *
       * @private
       * @readonly
       * @name Encryptor#instance
       * @type {Instance}
       */
      get instance() {
        return _instance
      },

      /**
       * Inject this object with a raw WASM instance. No type checking is performed.
       *
       * @private
       * @function
       * @name Encryptor#unsafeInject
       * @param {Instance} instance WASM instance
       */
      unsafeInject(instance: Instance) {
        if (_instance) {
          _instance.delete()
          _instance = undefined
        }
        _instance = instance
      },

      /**
       * Delete the underlying WASM instance.
       *
       * Should be called before dereferencing this object to prevent the
       * WASM heap from growing indefinitely.
       * @function
       * @name Encryptor#delete
       */
      delete() {
        if (_instance) {
          _instance.delete()
          _instance = undefined
        }
      },

      /**
       * Encrypts a PlainText and stores the result in the destination parameter.
       * Dynamic memory allocations in the process are allocated from the memory
       * pool pointed to by the given MemoryPoolHandle.
       *
       * @function
       * @name Encryptor#encrypt
       * @param {PlainText} plainText PlainText to encrypt
       * @param {CipherText} [cipherText] CipherText destination to store the encrypted result
       * @param {MemoryPoolHandle} [pool={@link MemoryPoolHandle.global}] MemoryPool to use
       * @returns {CipherText|void} Returns undefined if a CipherText was specified. Otherwise returns a
       * CipherText containing the encrypted result
       */
      encrypt(
        plainText: PlainText,
        cipherText?: CipherText,
        pool: MemoryPoolHandle = MemoryPoolHandle.global
      ): CipherText | void {
        try {
          if (cipherText) {
            _instance.encrypt(plainText.instance, cipherText.instance, pool)
            return
          }
          const cipher = CipherText()
          _instance.encrypt(plainText.instance, cipher.instance, pool)
          return cipher
        } catch (e) {
          throw Exception.safe(e as SealError)
        }
      },

      /**
       * Encrypts a PlainText and returns a CipherText as a Serializable object.
       * Dynamic memory allocations in the process are allocated from the memory
       * pool pointed to by the given MemoryPoolHandle.
       *
       * @function
       * @name Encryptor#encryptSerializable
       * @param {PlainText} plainText PlainText to encrypt
       * @param {MemoryPoolHandle} [pool={@link MemoryPoolHandle.global}] MemoryPool to use
       * @returns {Serializable<CipherText>} A Serializable containing the encrypted result
       */
      encryptSerializable(
        plainText: PlainText,
        pool: MemoryPoolHandle = MemoryPoolHandle.global
      ): Serializable {
        try {
          const temp = Serializable()
          const instance = _instance.encryptSerializable(
            plainText.instance,
            pool
          )
          temp.unsafeInject(instance)
          return temp
        } catch (e) {
          throw Exception.safe(e as SealError)
        }
      },

      /**
       * Encrypts a PlainText with the secret key and stores the result in
       * destination.
       *
       * The encryption parameters for the resulting CipherText
       * correspond to:
       * 1) in BFV or BGV, the highest (data) level in the modulus switching chain,
       * 2) in CKKS, the encryption parameters of the plaintext.
       * Dynamic memory allocations in the process are allocated from the memory
       * pool pointed to by the given MemoryPoolHandle.
       *
       * @function
       * @name Encryptor#encryptSymmetric
       * @param {PlainText} plainText PlainText to encrypt
       * @param {CipherText} [cipherText] CipherText destination to store the encrypted result.
       * @param {MemoryPoolHandle} [pool={@link MemoryPoolHandle.global}] MemoryPool to use
       * @returns {CipherText|void} Returns undefined if a CipherText was specified. Otherwise returns a
       * CipherText containing the encrypted result
       */
      encryptSymmetric(
        plainText: PlainText,
        cipherText?: CipherText,
        pool: MemoryPoolHandle = MemoryPoolHandle.global
      ): CipherText | void {
        try {
          if (cipherText) {
            _instance.encryptSymmetric(
              plainText.instance,
              cipherText.instance,
              pool
            )
            return
          }
          const cipher = CipherText()
          _instance.encryptSymmetric(plainText.instance, cipher.instance, pool)
          return cipher
        } catch (e) {
          throw Exception.safe(e as SealError)
        }
      },

      /**
       * Encrypts a plaintext with the secret key and returns the ciphertext as
       * a serializable object.
       *
       * The encryption parameters for the resulting CipherText
       * correspond to:
       * 1) in BFV or BGV, the highest (data) level in the modulus switching chain,
       * 2) in CKKS, the encryption parameters of the plaintext.
       * Dynamic memory allocations in the process are allocated from the memory
       * pool pointed to by the given MemoryPoolHandle.
       *
       * Half of the ciphertext data is pseudo-randomly generated from a seed to
       * reduce the object size. The resulting serializable object cannot be used
       * directly and is meant to be serialized for the size reduction to have an
       * impact.
       *
       * @function
       * @name Encryptor#encryptSymmetricSerializable
       * @param {PlainText} plainText PlainText to encrypt
       * @param {MemoryPoolHandle} [pool={@link MemoryPoolHandle.global}] MemoryPool to use
       * @returns {Serializable<CipherText>} Returns a Serializable containing the encrypted result
       */
      encryptSymmetricSerializable(
        plainText: PlainText,
        pool: MemoryPoolHandle = MemoryPoolHandle.global
      ): Serializable {
        try {
          const serialized = Serializable()
          const instance = _instance.encryptSymmetricSerializable(
            plainText.instance,
            pool
          )
          serialized.unsafeInject(instance)
          return serialized
        } catch (e) {
          throw Exception.safe(e as SealError)
        }
      },

      /**
       * Encrypts a zero plaintext with the public key and returns the ciphertext
       * as a serializable object.
       *
       * The encryption parameters for the resulting ciphertext correspond to the
       * highest (data) level in the modulus switching chain. Dynamic memory
       * allocations in the process are allocated from the memory pool pointed to
       * by the given MemoryPoolHandle.
       *
       * @function
       * @name Encryptor#encryptZero
       * @param {CipherText} [cipherText] A CipherText to overwrite.
       * @param {MemoryPoolHandle} [pool={@link MemoryPoolHandle.global}] MemoryPool to use
       * @returns {CipherText|void} Returns undefined if a CipherText was specified. Otherwise returns a
       * CipherText containing the encrypted result
       */
      encryptZero(
        cipherText?: CipherText,
        pool: MemoryPoolHandle = MemoryPoolHandle.global
      ): CipherText | void {
        try {
          if (cipherText) {
            _instance.encryptZero(cipherText.instance, pool)
            return
          }
          const cipher = CipherText()
          _instance.encryptZero(cipher.instance, pool)
          return cipher
        } catch (e) {
          throw Exception.safe(e as SealError)
        }
      },

      /**
       * Encrypts a zero plaintext with the public key and stores the result in
       * destination.
       *
       * The encryption parameters for the resulting ciphertext correspond to the
       * highest (data) level in the modulus switching chain. Dynamic memory
       * allocations in the process are allocated from the memory pool pointed to
       * by the given MemoryPoolHandle.
       *
       * @function
       * @name Encryptor#encryptZeroSerializable
       * @param {MemoryPoolHandle} [pool={@link MemoryPoolHandle.global}] MemoryPool to use
       * @returns {Serializable<CipherText>} A CipherText as a serialized object containing the encrypted result
       */
      encryptZeroSerializable(
        pool: MemoryPoolHandle = MemoryPoolHandle.global
      ): Serializable {
        try {
          const serialized = Serializable()
          const instance = _instance.encryptZeroSerializable(pool)
          serialized.unsafeInject(instance)
          return serialized
        } catch (e) {
          throw Exception.safe(e as SealError)
        }
      }
    }
  }

export const EncryptorInit = ({
  loader
}: LoaderOptions): EncryptorDependencies => {
  const library: Library = loader.library
  return EncryptorConstructor(library)
}