Package Exports
- node-seal
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (node-seal) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
node-seal
This is a library wrapper for the Web Assembly port of the C++ Microsoft SEAL library.
It contains high level functions to make using this library easy. There are default parameters which can be customized and overridden for advanced use cases.
Source will be posted on a public repository in the future with plans on also releasing the C++ fork from Microsoft SEAL to generate the Web Assembly.
Microsoft SEAL
Microsoft SEAL is an easy-to-use homomorphic encryption library developed by researchers in the Cryptography Research group at Microsoft Research. Microsoft SEAL is written in modern standard C++ and has no external dependencies, making it easy to compile and run in many different environments.
For more information about the Microsoft SEAL project, see http://sealcrypto.org.
License
Microsoft SEAL (and node-seal
) are licensed under the MIT license.
Installation
npm:
npm install node-seal
yarn:
yarn install node-seal
At this time, the library is not available on a CDN. This is a TODO.
Caveats
Our goal is to allow client-side and server-side Javascript to use a well established homomorphic library. However, there will be several limitations due to transitions from C++ to Javascript.
Limitations:
Dealing with 2^53 numbers (not true 64 bit). JS Arrays may infer the wrong type definition for the elements inside. For consistent results, use a TypedArray.
Generating large keys and saving them in the browser could be problematic. We can control NodeJS heap size, but not inside a user's browser.
Saving keys is very memory intensive especially for
computationLevel
s above low. This is because there's currently no way (that we have found) to use streams across JS and WASM, so the strings have to be buffered completely in RAM and they can be very, very large. This holds especially true forGaloisKeys
.Using this library in client frontend maybe difficult to integrate. If you use webpack, it will need to be tweaked. Please refer to the webpack configuration in this library.
Performance is less than the C++ native library despite being converted to Web Assembly. This is mainly due to poorly optimized SIMD, random number generator, slow memory allocations, etc. We have not benchmarked them directly, but the slowdown is noticeable.
By default, we encrypt/decrypt arrays (typed) of data. If you're encrypting a single integer (Int32/UInt32) you will receive back a TypedArray of length 1 containing the decrypted result. We do this because we want to have batching mode enabled for both
BFV
andCKKS
schemes.If you specify a JS Array with JS Numbers and the elements are greater than the bounds of an Int32/UInt32, the data will be treated as a C++ 'double' and may cause undesirable results. Why? For users who want to get started with both Scheme Types with some small test data before optimizing.
While initializing the library asynchronous, the core is synchronous and will block the main thread. For now, we advise to put this library in a NodeJS
child_process
or browserWeb Workers
and writing the necessary adapter logic.
Usage
There are a lot of assumptions made to help ease the burden of learning SEAL all at once. You can refer to the sample code below.
For those who are curious about the security of Microsoft SEAL, please refer to HomomorphicEncryption.org
Basics
There are two Scheme Types that SEAL supports:
BFV
operates on Int32/UInt32CKKS
operates on JS Float (Number.MIN_SAFE_INTEGER to Number.MAX_SAFE_INTEGER)
BFV
is used to encrypt/decrypt on real integers whereas CKKS
is used for floats.
However, the main difference really is in how CKKS
delivers an approximate result
back to you. Ex. A decrypted CKKS
cipher may be a few decimals off depending on
several factors. If 100% accuracy is needed, use BFV
.
There are several Keys in SEAL:
PublicKey
used to encrypt dataSecretKey
used to decrypt dataRelinKeys
used to extend the number of homomorphic evaluations on a given cipherGaloisKeys
used to perform matrix rotations on a cipher
You may generate and share RelinKeys
and GaloisKeys
with a 3rd party where
they could be used in an untrusted execution environment. You may also share the PublicKey
as the name implies. This allows 3rd parties to perform homomorphic evaluations on the
ciphers that were encrypted by the SecretKey
. Never share your SecretKey
unless you want
others to decrypt the data.
Note on homomorphic evaluations:
Microsoft SEAL is not a fully homomorphic encryption library and as such, encrypted ciphers
have a limit on the total number of evaluations performed before decryption fails. When designing a
leveled homomorphic algorithm, you can test the limits to see where decryption fails and where
you can increase the computationLevel
or manually tweak the parameters.
Steps:
- Import the library
- Create encryption parameters and initialize the context (sets the library to work in a given constraint of parameters)
- Create or load previously generated public/secret keys
- Create some data to encrypt. Save it, send it to a 3rd party for evaluation, or evaluate locally
- Decrypt the encrypted cipher result
Example
CommonJS
(async () => {
// Due to limitations with how the WASM file is loaded,
// we need to await on the main library in order to have
// a fully instanciated instance. This limitation mostly
// because of how chrome limits the size of synchronously
// loaded WASM files. Therefore, loading must be done
// asynchronously.
// If loading in the browser, this line is not needed.
const { Seal } = require('node-seal')
const Crypt = await Seal
// There are 3 different computationLevel's that have been predefined
// for ease of use. 'low', 'medium', and 'high'. The computation levels
// allow for more homomorphic operations __on__ encrypted cipherText's
// at the cost of more CPU/memory.
//
// Security is by default 128 bits, but can be changed to 192 or 256 bits again
// at the cost of more CPU/memory.
//
// The computation level and security settings that you choose
// here limit the total number of elements in an array as well
// as their min/max values.
const parms = Crypt.createParams({computationLevel: 'low', security: 128})
// BFV schemeType allows for pure Integer arithmetic
Crypt.initialize({...parms, schemeType: 'BFV'})
// Generate public and secret keys
Crypt.genKeys()
// Optionally, create RelinKeys
Crypt.genRelinKeys()
// Save the keys so we don't have to generate them again
// They will be base64 strings
const publicKey = Crypt.savePublicKey()
const secretKey = Crypt.saveSecretKey()
// You can skip `Crypt.genKeys()` by loading them instead
Crypt.loadPublicKey({encoded: publicKey})
Crypt.loadSecretKey({encoded: secretKey})
// Create some values in an array. Note the limitations of the array
// size, and value size
const step = parms.plainModulus / parms.polyDegree
// Could be a regular JS array or a TypedArray
const value = Int32Array.from([1, 2, 3])
// Encrypt the data
// We auto detect the 'type' for JS Arrays, but if the hint is specified we will convert it
// TypedArrays will set the type automatically.
const oldCipherText = Crypt.encrypt({value})
// You can save the cipherText for later as a base64 string
const savedRawCipher = oldCipherText.save()
// And reload it later using the helper
const cipherText = Crypt.reviveCipher({encoded: savedRawCipher})
// Createt a copy to sum later
const cipherText2 = Crypt.reviveCipher({encoded: savedRawCipher})
// But you will need to reinitialize some values. It would be best
// to also serialize this data in combination with the raw cipherText
// so that you may retrieve all the related information in one go.
cipherText.setSchemeType({scheme: oldCipherText.getSchemeType()})
cipherText.setVectorSize({size: oldCipherText.getVectorSize()})
cipherText.setVectorType({type: oldCipherText.getVectorType()})
cipherText2.setSchemeType({scheme: oldCipherText.getSchemeType()})
cipherText2.setVectorSize({size: oldCipherText.getVectorSize()})
cipherText2.setVectorType({type: oldCipherText.getVectorType()})
// Send the encrypted data to a 3rd party for
// homomorphic operations. But we need more
// metadata of the cipherText to help
// facilitate homomorphic operations involving
// optional matrix rotations, etc.
const cipherObject = {
cipherText: cipherText.save(), // gets the base64 string representation of the cipher
schemeType: cipherText.getSchemeType(),
vector: {
size: cipherText.getVectorSize(),
type: cipherText.getVectorType(),
}
}
const resultCipher = Crypt.add({a: cipherText, b: cipherText2})
// Attempt decryption now, or after relinearization
// const resultInt32Array = Crypt.decrypt({cipherText: resultCipher})
// (Optional) Relinearize the cipher
const relinearizedCipher = Crypt.relinearize({cipherText: resultCipher})
// Decrypt the result which returns a TypedArray
const resultInt32Array = Crypt.decrypt({cipherText: relinearizedCipher})
console.log('resultInt32Array', resultInt32Array)
// resultInt32Array Int32Array(3) [2, 4, 6]
})()
Testing
You can find the list of tests in package.json
. They can be useful to see different
parameters and how they affect execution time. Some of the tests will
take a long time to complete and consume a lot of memory.