· 5 years ago · Apr 06, 2020, 06:26 AM
1const paillier = require('paillier-bigint')
2const { Seal } = require('node-seal')
3const { performance } = require('perf_hooks')
4
5;(async function () {
6 const benchmark = create()
7 await benchmark.performanceTest()
8})()
9
10function create() {
11 let seal = null
12
13 function randomIntInc(low, high) {
14 return Math.floor(Math.random() * (high - low + 1) + low)
15 }
16
17 async function performanceTest() {
18 // Run paillier-bigint
19 await paillierPerformanceTest(paillier)
20
21 // Run node-seal
22 await sealPerformanceTest(seal)
23 }
24
25 async function paillierPerformanceTest(paillier) {
26 let timeStart = 0
27 let timeEnd = 0
28
29 process.stdout.write('Generating private/public keys: ')
30 timeStart = performance.now()
31 const { privateKey, publicKey } = await paillier.generateRandomKeys(3072)
32 timeEnd = performance.now()
33 process.stdout.write(
34 `Done [${Math.round((timeEnd - timeStart) * 1000)} microseconds]\r\n`
35 )
36
37 let timeEncryptSum = 0
38 let timeDecryptSum = 0
39 let timeAddSum = 0
40 let timeMultiplyPlainSum = 0
41
42 /*
43 How many times to run the test?
44 */
45 const count = 50
46
47 /*
48 This number represents is the range ceiling for the values we will encrypt
49 */
50 const plainNumber = 786433
51
52 process.stdout.write('Running tests ')
53 for (let i = 0; i < count; i++) {
54 const randomNumber = BigInt(
55 Math.floor(randomIntInc(0, plainNumber) % plainNumber)
56 )
57 /*
58 [Encryption]
59 */
60 timeStart = performance.now()
61 const cipher = publicKey.encrypt(randomNumber)
62 timeEnd = performance.now()
63 timeEncryptSum += timeEnd - timeStart
64
65 /*
66 [Decryption]
67 */
68 timeStart = performance.now()
69 const plain = privateKey.decrypt(cipher)
70 timeEnd = performance.now()
71 timeDecryptSum += timeEnd - timeStart
72
73 if (plain !== randomNumber) {
74 throw new Error('Decryption failed!')
75 }
76 /*
77 [Add]
78 */
79 timeStart = performance.now()
80 const sum = publicKey.addition(cipher, cipher)
81 timeEnd = performance.now()
82 timeAddSum += timeEnd - timeStart
83
84 if (privateKey.decrypt(sum) !== randomNumber * 2n) {
85 throw new Error('Addition failed!')
86 }
87 /*
88 [Multiply Plain]
89 */
90 timeStart = performance.now()
91 const product = publicKey.multiply(cipher, randomNumber)
92 timeEnd = performance.now()
93 timeMultiplyPlainSum += timeEnd - timeStart
94 if (privateKey.decrypt(product) !== randomNumber * randomNumber) {
95 throw new Error('Addition failed!')
96 }
97 process.stdout.write('.')
98 }
99 process.stdout.write(' Done\r\n\r\n')
100
101 const avgEncrypt = Math.round((timeEncryptSum * 1000) / count)
102 const avgDecrypt = Math.round((timeDecryptSum * 1000) / count)
103 const avgAdd = Math.round((timeAddSum * 1000) / count)
104 const avgMultiplyPlain = Math.round((timeMultiplyPlainSum * 1000) / count)
105
106 console.log(`Average encrypt: ${avgEncrypt} microseconds`)
107 console.log(`Average decrypt: ${avgDecrypt} microseconds`)
108 console.log(`Average add: ${avgAdd} microseconds`)
109 console.log(`Average multiply plain: ${avgMultiplyPlain} microseconds`)
110 console.log('')
111 }
112 async function sealPerformanceTest() {
113 const seal = await Seal()
114 const parms = seal.EncryptionParameters(seal.SchemeType.BFV)
115 let polyModulusDegree = 4096
116 let smallModulus = seal.SmallModulus('786433')
117 let coeffModulus = seal.CoeffModulus.BFVDefault(polyModulusDegree)
118 parms.setPolyModulusDegree(polyModulusDegree)
119 parms.setCoeffModulus(coeffModulus)
120 parms.setPlainModulus(smallModulus)
121 let context = seal.Context(parms)
122
123 let timeStart = 0
124 let timeEnd = 0
125
126 console.log(context.toHuman())
127
128 const plainModulus = parms.plainModulus
129
130 process.stdout.write('Generating secret/public keys: ')
131 timeStart = performance.now()
132 const keyGenerator = seal.KeyGenerator(context)
133 timeEnd = performance.now()
134 process.stdout.write(
135 `Done [${Math.round((timeEnd - timeStart) * 1000)} microseconds]\r\n`
136 )
137 const secretKey = keyGenerator.getSecretKey()
138 const publicKey = keyGenerator.getPublicKey()
139
140 const encryptor = seal.Encryptor(context, publicKey)
141 const decryptor = seal.Decryptor(context, secretKey)
142 const evaluator = seal.Evaluator(context)
143 const batchEncoder = seal.BatchEncoder(context)
144
145 /*
146 These will hold the total times used by each operation.
147 */
148 let timeBatchSum = 0
149 let timeUnbatchSum = 0
150 let timeEncryptSum = 0
151 let timeDecryptSum = 0
152 let timeAddSum = 0
153 let timeMultiplySum = 0
154 let timeMultiplyPlainSum = 0
155
156 /*
157 How many times to run the test?
158 */
159 const count = 50
160
161 /*
162 Populate a vector of values to batch.
163 */
164 const slotCount = batchEncoder.slotCount
165 const array = new Uint32Array(slotCount)
166 const plainNumber = Number(plainModulus.value)
167 for (let i = 0; i < slotCount; i++) {
168 array[i] = Math.floor(randomIntInc(0, plainNumber) % plainNumber)
169 }
170
171 process.stdout.write('Running tests ')
172 for (let i = 0; i < count; i++) {
173 /*
174 [Batching]
175 There is nothing unusual here. We batch our random plaintext matrix
176 into the polynomial. Note how the plaintext we create is of the exactly
177 right size so unnecessary reallocations are avoided.
178 */
179 const plain = seal.PlainText({
180 capacity: parms.polyModulusDegree,
181 coeffCount: 0
182 })
183 plain.reserve(polyModulusDegree)
184 timeStart = performance.now()
185 batchEncoder.encode(array, plain)
186 timeEnd = performance.now()
187 timeBatchSum += timeEnd - timeStart
188
189 /*
190 [Unbatching]
191 We unbatch what we just batched.
192 */
193 timeStart = performance.now()
194 const unbatched = batchEncoder.decode(plain, false)
195 timeEnd = performance.now()
196 timeUnbatchSum += timeEnd - timeStart
197 if (JSON.stringify(unbatched) !== JSON.stringify(array)) {
198 throw new Error('Batch/unbatch failed. Something is wrong.')
199 }
200
201 /*
202 [Encryption]
203 We make sure our ciphertext is already allocated and large enough
204 to hold the encryption with these encryption parameters. We encrypt
205 our random batched matrix here.
206 */
207 const encrypted = seal.CipherText({ context })
208 timeStart = performance.now()
209 encryptor.encrypt(plain, encrypted)
210 timeEnd = performance.now()
211 timeEncryptSum += timeEnd - timeStart
212
213 /*
214 [Decryption]
215 We decrypt what we just encrypted.
216 */
217 const plain2 = seal.PlainText({
218 capacity: parms.polyModulusDegree,
219 coeffCount: 0
220 })
221 plain2.reserve(polyModulusDegree)
222 timeStart = performance.now()
223 decryptor.decrypt(encrypted, plain2)
224 timeEnd = performance.now()
225 timeDecryptSum += timeEnd - timeStart
226 if (plain2.toPolynomial() !== plain.toPolynomial()) {
227 throw new Error('Encrypt/decrypt failed. Something is wrong.')
228 }
229 /*
230 [Add]
231 We create two ciphertexts and perform a few additions with them.
232 */
233 const encrypted1 = seal.CipherText({ context })
234 const encrypted2 = seal.CipherText({ context })
235 const plain3 = batchEncoder.encode(Int32Array.from([i]))
236 const plain4 = batchEncoder.encode(Int32Array.from([i + 1]))
237 encryptor.encrypt(plain3, encrypted1)
238 encryptor.encrypt(plain4, encrypted2)
239 timeStart = performance.now()
240 evaluator.add(encrypted1, encrypted1, encrypted1)
241 evaluator.add(encrypted2, encrypted2, encrypted2)
242 evaluator.add(encrypted1, encrypted2, encrypted1)
243 timeEnd = performance.now()
244 timeAddSum += timeEnd - timeStart
245
246 /*
247 [Multiply]
248 We multiply two ciphertexts. Since the size of the result will be 3,
249 and will overwrite the first argument, we reserve first enough memory
250 to avoid reallocating during multiplication.
251 */
252 encrypted1.reserve(context, 3)
253 timeStart = performance.now()
254 evaluator.multiply(encrypted1, encrypted2, encrypted1)
255 timeEnd = performance.now()
256 timeMultiplySum += timeEnd - timeStart
257
258 /*
259 [Multiply Plain]
260 We multiply a ciphertext with a random plaintext. Recall that
261 multiplyPlain does not change the size of the ciphertext so we use
262 encrypted2 here.
263 */
264 timeStart = performance.now()
265 evaluator.multiplyPlain(encrypted2, plain, encrypted2)
266 timeEnd = performance.now()
267 timeMultiplyPlainSum += timeEnd - timeStart
268
269 // Cleanup
270 plain.delete()
271 plain2.delete()
272 plain3.delete()
273 plain4.delete()
274 encrypted.delete()
275 encrypted1.delete()
276 encrypted2.delete()
277
278 process.stdout.write('.')
279 }
280 process.stdout.write(' Done\r\n\r\n')
281
282 const avgBatch = Math.round((timeBatchSum * 1000) / count)
283 const avgUnbatch = Math.round((timeUnbatchSum * 1000) / count)
284 const avgEncrypt = Math.round((timeEncryptSum * 1000) / count)
285 const avgDecrypt = Math.round((timeDecryptSum * 1000) / count)
286 const avgAdd = Math.round((timeAddSum * 1000) / (3 * count))
287 const avgMultiply = Math.round((timeMultiplySum * 1000) / count)
288 const avgMultiplyPlain = Math.round((timeMultiplyPlainSum * 1000) / count)
289
290 console.log(`Average batch: ${avgBatch} microseconds`)
291 console.log(`Average unbatch: ${avgUnbatch} microseconds`)
292 console.log(`Average encrypt: ${avgEncrypt} microseconds`)
293 console.log(`Average decrypt: ${avgDecrypt} microseconds`)
294 console.log(`Average add: ${avgAdd} microseconds`)
295 console.log(`Average multiply: ${avgMultiply} microseconds`)
296 console.log(`Average multiply plain: ${avgMultiplyPlain} microseconds`)
297 console.log('')
298
299 // Cleanup
300 parms.delete()
301 plainModulus.delete()
302 context.delete()
303 keyGenerator.delete()
304 secretKey.delete()
305 publicKey.delete()
306 evaluator.delete()
307 batchEncoder.delete()
308 encryptor.delete()
309 decryptor.delete()
310 }
311
312 return {
313 performanceTest
314 }
315}