Source

plain-text.ts

import { ComprModeType } from './compr-mode-type'
import { INVALID_PLAIN_CONSRUCTOR_OPTIONS } from './constants'
import { Context } from './context'
import { Exception, SealError } from './exception'
import { MemoryPoolHandle } from './memory-pool-handle'
import { ParmsIdType, ParmsIdTypeConstructorOptions } from './parms-id-type'
import { Instance, Library, LoaderOptions } from './seal'
import { VectorConstructorOptions } from './vector'
export type PlainTextDependencyOptions = {
  readonly Exception: Exception
  readonly ComprModeType: ComprModeType
  readonly ParmsIdType: ParmsIdTypeConstructorOptions
  readonly MemoryPoolHandle: MemoryPoolHandle
  readonly Vector: VectorConstructorOptions
}

export type PlainTextDependencies = {
  ({
    Exception,
    ComprModeType,
    ParmsIdType,
    MemoryPoolHandle,
    Vector
  }: PlainTextDependencyOptions): PlainTextConstructorOptions
}

export type PlainTextConstructorOptions = {
  ({
    capacity,
    coeffCount,
    pool
  }?: {
    capacity?: number
    coeffCount?: number
    pool?: MemoryPoolHandle
  }): PlainText
}

export type PlainText = {
  readonly instance: Instance
  readonly unsafeInject: (instance: Instance) => void
  readonly delete: () => void
  readonly reserve: (capacity: number) => void
  readonly shrinkToFit: () => void
  readonly release: () => void
  readonly resize: (coeffCount: number) => void
  readonly setZero: () => void
  readonly isZero: boolean
  readonly capacity: number
  readonly coeffCount: number
  readonly significantCoeffCount: number
  readonly nonzeroCoeffCount: number
  readonly toPolynomial: () => string
  readonly isNttForm: boolean
  readonly parmsId: ParmsIdType
  readonly scale: number
  readonly setScale: (scale: number) => void
  readonly pool: MemoryPoolHandle
  readonly save: (compression?: ComprModeType) => string
  readonly saveArray: (compression?: ComprModeType) => Uint8Array
  readonly load: (context: Context, encoded: string) => void
  readonly loadArray: (context: Context, array: Uint8Array) => void
  readonly copy: (plain: PlainText) => void
  readonly clone: () => PlainText
  readonly move: (plain: PlainText) => void
}

const PlainTextConstructor =
  (library: Library): PlainTextDependencies =>
  ({
    Exception,
    ComprModeType,
    ParmsIdType,
    MemoryPoolHandle,
    Vector
  }: PlainTextDependencyOptions): PlainTextConstructorOptions =>
  ({
    capacity,
    coeffCount,
    pool = MemoryPoolHandle.global
  } = {}): PlainText => {
    // Static methods
    const Constructor = library.Plaintext

    let _instance = construct({
      capacity,
      coeffCount,
      pool
    })

    function construct({
      capacity,
      coeffCount,
      pool = MemoryPoolHandle.global
    }: {
      capacity?: number
      coeffCount?: number
      pool?: MemoryPoolHandle
    }) {
      try {
        if (capacity === undefined && coeffCount === undefined) {
          return new Constructor(pool)
        } else if (capacity === undefined && coeffCount !== undefined) {
          return new Constructor(coeffCount, pool)
        } else if (capacity !== undefined && coeffCount !== undefined) {
          return new Constructor(capacity, coeffCount, pool)
        } else {
          throw new Error(INVALID_PLAIN_CONSRUCTOR_OPTIONS)
        }
      } catch (e) {
        throw Exception.safe(e as SealError)
      }
    }
    /**
     * @implements PlainText
     */

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

      /**
       * Inject this object with a raw WASM instance. No type checking is performed.
       *
       * @private
       * @function
       * @name PlainText#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 PlainText#delete
       */
      delete() {
        if (_instance) {
          _instance.delete()
          _instance = undefined
        }
      },

      /**
       * Allocates enough memory to accommodate the backing array of a plaintext
       * with given capacity.
       *
       * @function
       * @name PlainText#reserve
       * @param {number} capacity The capacity to reserve
       */
      reserve(capacity: number) {
        try {
          return _instance.reserve(capacity)
        } catch (e) {
          throw Exception.safe(e as SealError)
        }
      },

      /**
       * Allocates enough memory to accommodate the backing array of the current
       * PlainText and copies it over to the new location. This function is meant
       * to reduce the memory use of the PlainText to smallest possible and can be
       * particularly important after modulus switching.
       *
       * @function
       * @name PlainText#shrinkToFit
       */
      shrinkToFit() {
        _instance.shrinkToFit()
      },

      /**
       * Resets the PlainText. This function releases any memory allocated by the
       * PlainText, returning it to the memory pool.
       *
       * @function
       * @name PlainText#release
       */
      release() {
        _instance.release()
      },

      /**
       * Resizes the PlainText to have a given coefficient count. The PlainText
       * is automatically reallocated if the new coefficient count does not fit in
       * the current capacity.
       *
       * @function
       * @name PlainText#resize
       * @param {number} coeffCount The number of coefficients in the plaintext polynomial
       */
      resize(coeffCount: number) {
        try {
          _instance.resize(coeffCount)
        } catch (e) {
          throw Exception.safe(e as SealError)
        }
      },

      /**
       * Sets the PlainText polynomial to zero.
       *
       * @function
       * @name PlainText#setZero
       */
      setZero() {
        _instance.setZero()
      },

      /**
       * Whether the current PlainText polynomial has all zero coefficients.
       *
       * @readonly
       * @name PlainText#isZero
       * @type {boolean}
       */
      get isZero() {
        return _instance.isZero()
      },

      /**
       * The capacity of the current allocation.
       *
       * @readonly
       * @name PlainText#capacity
       * @type {number}
       */
      get capacity() {
        return _instance.capacity()
      },

      /**
       * The coefficient count of the current PlainText polynomial.
       *
       * @readonly
       * @name PlainText#coeffCount
       * @type {number}
       */
      get coeffCount() {
        return _instance.coeffCount()
      },

      /**
       * The significant coefficient count of the current PlainText polynomial.
       *
       * @readonly
       * @name PlainText#significantCoeffCount
       * @type {number}
       */
      get significantCoeffCount() {
        return _instance.significantCoeffCount()
      },

      /**
       * Returns the non-zero coefficient count of the current PlainText polynomial.
       *
       * @readonly
       * @name PlainText#nonzeroCoeffCount
       * @type {number}
       */
      get nonzeroCoeffCount() {
        return _instance.nonzeroCoeffCount()
      },

      /**
       * Returns a human-readable string description of the PlainText polynomial.
       *
       * The returned string is of the form "7FFx^3 + 1x^1 + 3" with a format
       * summarized by the following:
       * 1. Terms are listed in order of strictly decreasing exponent
       * 2. Coefficient values are non-negative and in hexadecimal format (hexadecimal
       * letters are in upper-case)
       * 3. Exponents are positive and in decimal format
       * 4. Zero coefficient terms (including the constant term) are omitted unless
       * the polynomial is exactly 0 (see rule 9)
       * 5. Term with the exponent value of one is written as x^1
       * 6. Term with the exponent value of zero (the constant term) is written as
       * just a hexadecimal number without x or exponent
       * 7. Terms are separated exactly by <space>+<space>
       * 8. Other than the +, no other terms have whitespace
       * 9. If the polynomial is exactly 0, the string "0" is returned
       *
       * @function
       * @name PlainText#toPolynomial
       * @throws std::invalid_argument if the PlainText is in NTT transformed form
       * @returns {string} Polynomial string
       */
      toPolynomial(): string {
        try {
          return _instance.toPolynomial()
        } catch (e) {
          throw Exception.safe(e as SealError)
        }
      },

      /**
       * Whether the PlainText is in NTT form.
       *
       * @readonly
       * @name PlainText#isNttForm
       * @type {boolean}
       */
      get isNttForm() {
        return _instance.isNttForm()
      },

      /**
       * The reference to parmsId of the PlainText. The parmsId must remain zero unless the
       * PlainText polynomial is in NTT form.
       *
       * @see {@link EncryptionParameters} for more information about parmsId.
       *
       * @readonly
       * @name PlainText#parmsId
       * @type {ParmsIdType}
       */
      get parmsId() {
        const parms = ParmsIdType()
        parms.inject(_instance.parmsId())
        return parms
      },

      /**
       * The reference to the scale. This is only needed when using the CKKS
       * encryption scheme. The user should have little or no reason to ever change
       * the scale by hand.
       *
       * @readonly
       * @name PlainText#scale
       * @type {number}
       */
      get scale() {
        return _instance.scale()
      },

      /**
       * Sets the PlainText scale. This is only needed when using the
       * CKKS encryption scheme. The user should have little or no reason to ever
       * change the scale by hand.
       *
       * @function
       * @name PlainText#setScale
       * @param {number} scale The scale to set
       */
      setScale(scale: number) {
        _instance.setScale(scale)
      },

      /**
       * The currently used MemoryPoolHandle.
       *
       * @readonly
       * @name PlainText#pool
       * @type {MemoryPoolHandle}
       */
      get pool() {
        return _instance.pool()
      },

      /**
       * Save the PlainText to a base64 string
       *
       * @function
       * @name PlainText#save
       * @param {ComprModeType} [compression={@link ComprModeType.zstd}] The compression mode to use
       * @returns {string} Base64 encoded string
       */
      save(compression: ComprModeType = ComprModeType.zstd): string {
        return _instance.saveToString(compression)
      },

      /**
       * Save the PlainText as a binary Uint8Array
       *
       * @function
       * @name PlainText#saveArray
       * @param {ComprModeType} [compression={@link ComprModeType.zstd}] The compression mode to use
       * @returns {Uint8Array} A byte array containing the PlainText in binary form
       */
      saveArray(compression: ComprModeType = ComprModeType.zstd): Uint8Array {
        const tempVect = Vector()
        const instance = _instance.saveToArray(compression)
        tempVect.unsafeInject(instance)
        tempVect.setType('Uint8Array')
        const tempArr = tempVect.toArray() as Uint8Array
        tempVect.delete()
        return tempArr
      },

      /**
       * Load a PlainText from a base64 string
       *
       * @function
       * @name PlainText#load
       * @param {Context} context Encryption context to enforce
       * @param {string} encoded Base64 encoded string
       */
      load(context: Context, encoded: string) {
        try {
          _instance.loadFromString(context.instance, encoded)
        } catch (e) {
          throw Exception.safe(e as SealError)
        }
      },

      /**
       * Load a PlainText from an Uint8Array holding binary data
       *
       * @function
       * @name PlainText#loadArray
       * @param {Context} context Encryption context to enforce
       * @param {Uint8Array} array TypedArray containing binary data
       */
      loadArray(context: Context, array: Uint8Array) {
        try {
          _instance.loadFromArray(context.instance, array)
        } catch (e) {
          throw Exception.safe(e as SealError)
        }
      },

      /**
       * Copy an existing PlainText and overwrite this instance
       *
       * @function
       * @name PlainText#copy
       * @param {PlainText} plain PlainText to copy
       * @example
       * const plainTextA = seal.PlainText()
       * // ... after encoding some data ...
       * const plainTextB = seal.PlainText()
       * plainTextB.copy(plainTextA)
       * // plainTextB holds a copy of plainTextA
       */
      copy(plain: PlainText) {
        try {
          _instance.copy(plain.instance)
        } catch (e) {
          throw Exception.safe(e as SealError)
        }
      },

      /**
       * Clone and return a new instance of this PlainText
       *
       * @function
       * @name PlainText#clone
       * @returns {PlainText}
       * @example
       * const plainTextA = seal.PlainText()
       * // ... after encoding some data ...
       * const plainTextB = plainTextA.clone()
       * // plainTextB holds a copy of plainTextA
       */
      clone(): PlainText {
        try {
          const clonedInstance = _instance.clone()
          const plain = PlainTextConstructor(library)({
            Exception,
            ComprModeType,
            ParmsIdType,
            MemoryPoolHandle,
            Vector
          })()
          plain.unsafeInject(clonedInstance)
          return plain
        } catch (e) {
          throw Exception.safe(e as SealError)
        }
      },

      /**
       * Move a PlainText into this one and delete the old reference
       *
       * @function
       * @name PlainText#move
       * @param {PlainText} plain PlainText to move
       * @example
       * const plainTextA = seal.PlainText()
       * // ... after encoding some data ...
       * const plainTextB = seal.PlainText()
       * plainTextB.move(plainTextA)
       * // plainTextB holds a the instance of plainTextA.
       * // plainTextA no longer holds an instance
       */
      move(plain: PlainText) {
        try {
          _instance.move(plain.instance)
          // TODO: find optimization
          // This method results in a copy instead of a real move.
          // Therefore, we need to delete the old instance.
          plain.delete()
        } catch (e) {
          throw Exception.safe(e as SealError)
        }
      }
    }
  }

export const PlainTextInit = ({
  loader
}: LoaderOptions): PlainTextDependencies => {
  const library: Library = loader.library
  return PlainTextConstructor(library)
}