From 2d7155ae8e4b9d5a2500735455eb9022abce08b6 Mon Sep 17 00:00:00 2001 From: Andrei Lapets Date: Sun, 12 Jan 2025 19:09:52 -0500 Subject: [PATCH] test: significantly expand/refactor and optimize tests --- tests/nilql.test.ts | 604 ++++++++++++++++++++++++++++++-------------- 1 file changed, 421 insertions(+), 183 deletions(-) diff --git a/tests/nilql.test.ts b/tests/nilql.test.ts index b3c9a8b..c8c625f 100644 --- a/tests/nilql.test.ts +++ b/tests/nilql.test.ts @@ -15,7 +15,7 @@ function apiNilql() { } /** - * Tests verifying the presence of exports. + * Test that the exported classes and functions match the expected API. */ describe("namespace", () => { test("nilql API has all methods", () => { @@ -26,30 +26,87 @@ describe("namespace", () => { }); /** - * Tests verifying that methods return objects (or throw errors) having the expected - * types. + * Precomputed constants that can be reused to reduce running time of tests. */ -describe("input ranges and errors", () => { - test("types SecretKey", async () => { - const cluster = { nodes: [{}] }; - const secretKey = await nilql.SecretKey.generate(cluster, { match: true }); - expect(secretKey).toBeInstanceOf(Object); - }); +const secretKeyForSumWithOneNode = await nilql.SecretKey.generate( + { nodes: [{}] }, + { sum: true }, +); + +/** + * Tests of methods of cryptographic key classes. + */ +describe("methods of cryptographic key classes", () => { + const clusters = [{ nodes: [{}] }, { nodes: [{}, {}, {}] }]; + for (const cluster of clusters) { + test("generate, dump, JSONify, and load key for store operation", async () => { + const secretKey = await nilql.SecretKey.generate(cluster, { + store: true, + }); + + const secretKeyObject = JSON.parse(JSON.stringify(secretKey.dump())); + const secretKeyLoaded = nilql.SecretKey.load(secretKeyObject); + + const plaintext = "abc"; + const ciphertext = await nilql.encrypt(secretKey, plaintext); + const decrypted = await nilql.decrypt(secretKeyLoaded, ciphertext); + expect(plaintext).toEqual(decrypted); + }); + + test("generate, dump, JSONify, and load key for match operation", async () => { + const secretKey = await nilql.SecretKey.generate(cluster, { + match: true, + }); + + const secretKeyObject = JSON.parse(JSON.stringify(secretKey.dump())); + const secretKeyLoaded = nilql.SecretKey.load(secretKeyObject); + + const plaintext = "abc"; + const ciphertext = await nilql.encrypt(secretKey, plaintext); + const ciphertextViaLoaded = await nilql.encrypt( + secretKeyLoaded, + plaintext, + ); + expect(ciphertextViaLoaded).toEqual(ciphertext); + }); + } - test("types SecretKey", async () => { + test("generate, dump, JSONify, and load keys for sum operation with single node", async () => { const cluster = { nodes: [{}] }; - const secretKey = await nilql.SecretKey.generate(cluster, { sum: true }); - expect(secretKey).toBeInstanceOf(Object); + const secretKey = secretKeyForSumWithOneNode; + const publicKey = await nilql.PublicKey.generate(secretKey); + + const secretKeyObject = JSON.parse(JSON.stringify(secretKey.dump())); + const secretKeyLoaded = nilql.SecretKey.load(secretKeyObject); + + const publicKeyObject = JSON.parse(JSON.stringify(publicKey.dump())); + const publicKeyLoaded = nilql.PublicKey.load(publicKeyObject); + + const plaintext = BigInt(123); + const ciphertext = await nilql.encrypt(publicKeyLoaded, plaintext); + const decrypted = await nilql.decrypt(secretKeyLoaded, ciphertext); + expect(plaintext).toEqual(decrypted); }); - test("types PublicKey", async () => { - const cluster = { nodes: [{}] }; + test("generate, dump, JSONify, and load key for sum operation with multiple nodes", async () => { + const cluster = { nodes: [{}, {}, {}] }; const secretKey = await nilql.SecretKey.generate(cluster, { sum: true }); - const publicKey = await nilql.PublicKey.generate(secretKey); - expect(publicKey).toBeInstanceOf(Object); + + const secretKeyObject = JSON.parse(JSON.stringify(secretKey.dump())); + const secretKeyLoaded = nilql.SecretKey.load(secretKeyObject); + + const plaintext = BigInt(123); + const ciphertext = await nilql.encrypt(secretKey, plaintext); + const decrypted = await nilql.decrypt(secretKeyLoaded, ciphertext); + expect(plaintext).toEqual(decrypted); }); +}); - test("errors SecretKey", async () => { +/** + * Tests of errors thrown by methods of cryptographic key classes. + */ +describe("errors involving methods of cryptographic key classes", () => { + test("errors in secret key generation", async () => { try { const secretKey = await nilql.SecretKey.generate(null, null); } catch (e) { @@ -86,153 +143,296 @@ describe("input ranges and errors", () => { } }); - test("errors PublicKey", async () => { - const cluster = { nodes: [{}, {}] }; + test("errors in secret key dumping and loading", async () => { try { - const secretKey = await nilql.SecretKey.generate(cluster, { sum: true }); - const publicKey = await nilql.PublicKey.generate(secretKey); + const secretKey = await nilql.SecretKey.generate( + { nodes: [{}, {}, {}] }, + { sum: true }, + ); + const secretKeyObject = secretKey.dump() as { + material: object; + cluster: object; + operations?: object; + }; + nilql.SecretKey.load({ + material: secretKeyObject.material, + cluster: secretKeyObject.cluster, + }); } catch (e) { expect(e).toStrictEqual( - TypeError("cannot create public key for supplied secret key"), + TypeError("invalid object representation of a secret key"), ); } - }); - test("errors encryption for match operation", async () => { - const cluster = { nodes: [{}] }; - const secretKey = await nilql.SecretKey.generate(cluster, { match: true }); - const plaintext = "x".repeat(4097); + try { + const secretKey = secretKeyForSumWithOneNode; + const secretKeyObject = secretKey.dump() as { + material: { pub?: object; mu: object; lam: object }; + cluster: object; + operations: object; + }; + secretKeyObject.material.pub = undefined; + nilql.SecretKey.load({ + material: { + mu: secretKeyObject.material.mu, + lam: secretKeyObject.material.lam, + }, + cluster: secretKeyObject.cluster, + operations: secretKeyObject.operations, + }); + } catch (e) { + expect(e).toStrictEqual( + TypeError("invalid object representation of a secret key"), + ); + } try { - await nilql.encrypt(secretKey, plaintext); + const secretKey = secretKeyForSumWithOneNode; + const secretKeyObject = secretKey.dump() as { + material: { pub: object; mu: object | number; lam: object }; + cluster: object; + operations: object; + }; + secretKeyObject.material.mu = 123; + nilql.SecretKey.load(secretKeyObject); } catch (e) { expect(e).toStrictEqual( - TypeError( - "plaintext string must be possible to encode in 4096 bytes or fewer", - ), + TypeError("invalid object representation of a secret key"), ); } - }); - test("errors encryption for sum operation", async () => { - const cluster = { nodes: [{}] }; - const secretKey = await nilql.SecretKey.generate(cluster, { sum: true }); - const publicKey = await nilql.PublicKey.generate(secretKey); + try { + const secretKey = secretKeyForSumWithOneNode; + const secretKeyObject = secretKey.dump() as { + material: { + pub: { n: string | number; g: string }; + mu: object; + lam: object; + }; + cluster: object; + operations: object; + }; + secretKeyObject.material.pub.n = 123; + nilql.SecretKey.load(secretKeyObject); + } catch (e) { + expect(e).toStrictEqual( + TypeError("invalid object representation of a secret key"), + ); + } + }, 10000); - for (const plaintext of [ - -123, - BigInt(-123), - (-2) ** 31, - 2 ** 31 - 1, - -BigInt(2 ** 31), - BigInt(2 ** 31) - BigInt(1), - ]) { - try { - await nilql.encrypt(publicKey, plaintext); - } catch (e) { - expect(e).toStrictEqual( - TypeError("numeric plaintext must be a valid 32-bit signed integer"), - ); - } + test("errors in public key generation", async () => { + const cluster = { nodes: [{}, {}, {}] }; + try { + const secretKey = await nilql.SecretKey.generate(cluster, { sum: true }); + const publicKey = await nilql.PublicKey.generate(secretKey); + } catch (e) { + expect(e).toStrictEqual( + TypeError("cannot create public key for supplied secret key"), + ); } }); - test("errors decryption", async () => { - const cluster = { nodes: [{}] }; - const secretKey = await nilql.SecretKey.generate(cluster, { sum: true }); - const secretKeyAlt = await nilql.SecretKey.generate(cluster, { sum: true }); + test("errors in public key dumping and loading", async () => { + const secretKey = secretKeyForSumWithOneNode; const publicKey = await nilql.PublicKey.generate(secretKey); - const plaintextNumber = 123; - const ciphertextFromNumber = (await nilql.encrypt( - publicKey, - plaintextNumber, - )) as bigint; + try { + const publicKeyObject = publicKey.dump() as { + material: { n: string; g: string }; + cluster?: object; + operations: object; + }; + nilql.PublicKey.load({ + material: publicKeyObject.material, + operations: publicKeyObject.operations, + }); + } catch (e) { + expect(e).toStrictEqual( + TypeError("invalid object representation of a public key"), + ); + } try { - await nilql.decrypt(secretKeyAlt, ciphertextFromNumber); + const publicKeyObject = publicKey.dump() as { + material: { n?: string; g: string }; + cluster: object; + operations: object; + }; + nilql.PublicKey.load({ + material: { g: publicKeyObject.material.g }, + cluster: publicKeyObject.cluster, + operations: publicKeyObject.operations, + }); } catch (e) { expect(e).toStrictEqual( - TypeError("ciphertext cannot be decrypted using supplied secret key"), + TypeError("invalid object representation of a public key"), + ); + } + + try { + const publicKeyObject = publicKey.dump() as { + material: { n: string; g: string | number }; + cluster: object; + operations: object; + }; + publicKeyObject.material.g = 123; + nilql.PublicKey.load(publicKeyObject); + } catch (e) { + expect(e).toStrictEqual( + TypeError("invalid object representation of a public key"), ); } }); }); /** - * Tests of dumping and loading methods of cryptographic key classes. + * Tests of the functional properties of encryption/decryption functions. */ -describe("dumping and loading of cryptographic keys", () => { +describe("encryption and decryption functions", () => { const clusters = [{ nodes: [{}] }, { nodes: [{}, {}, {}] }]; - for (const cluster of clusters) { - test("dump and load key for store operation", async () => { + test(`encryption and decryption for store operation (${cluster.nodes.length})`, async () => { const secretKey = await nilql.SecretKey.generate(cluster, { store: true, }); - const secretKeyObject = secretKey.dump(); - const secretKeyLoaded = nilql.SecretKey.load(secretKeyObject); + const plaintextNumber = 123; + const ciphertextFromNumber = await nilql.encrypt( + secretKey, + plaintextNumber, + ); + const decryptedFromNumber = Number( + await nilql.decrypt(secretKey, ciphertextFromNumber), + ); + expect(plaintextNumber).toEqual(decryptedFromNumber); - const plaintext = "abc"; - const ciphertext = await nilql.encrypt(secretKey, plaintext); - const decrypted = await nilql.decrypt(secretKeyLoaded, ciphertext); - expect(plaintext).toEqual(decrypted); + const plaintextBigInt = BigInt(123); + const ciphertextFromBigInt = await nilql.encrypt( + secretKey, + plaintextBigInt, + ); + const decryptedFromBigInt = (await nilql.decrypt( + secretKey, + ciphertextFromBigInt, + )) as bigint; + expect(plaintextBigInt).toEqual(decryptedFromBigInt); + + const plaintextString = "abc"; + const ciphertextFromString = await nilql.encrypt( + secretKey, + plaintextString, + ); + const decryptedFromString = (await nilql.decrypt( + secretKey, + ciphertextFromString, + )) as string; + expect(plaintextString).toEqual(decryptedFromString); }); - test("dump and load key for match operation", async () => { + test(`encryption of number for match operation (${cluster.nodes.length})`, async () => { const secretKey = await nilql.SecretKey.generate(cluster, { match: true, }); - const secretKeyObject = secretKey.dump(); - const secretKeyLoaded = nilql.SecretKey.load(secretKeyObject); + const plaintextNumber = 123; + const ciphertextFromNumber = (await nilql.encrypt( + secretKey, + plaintextNumber, + )) as string; - const plaintext = "abc"; - const ciphertext = await nilql.encrypt(secretKey, plaintext); - const ciphertextViaLoaded = await nilql.encrypt( - secretKeyLoaded, - plaintext, - ); - expect(ciphertextViaLoaded).toEqual(ciphertext); + const plaintextBigInt = BigInt(123); + const ciphertextFromBigInt = (await nilql.encrypt( + secretKey, + plaintextBigInt, + )) as string; + + expect(ciphertextFromNumber).toEqual(ciphertextFromBigInt); + }); + + test("encryption of string for match operation", async () => { + const secKeyOne = await nilql.SecretKey.generate(cluster, { + match: true, + }); + const secKeyTwo = await nilql.SecretKey.generate(cluster, { + match: true, + }); + + const plaintextOne = "ABC"; + const plaintextTwo = "ABC"; + const plaintextThree = "abc"; + const ciphertextOne = await nilql.encrypt(secKeyOne, plaintextOne); + const ciphertextTwo = await nilql.encrypt(secKeyOne, plaintextTwo); + const ciphertextThree = await nilql.encrypt(secKeyOne, plaintextThree); + const ciphertextFour = await nilql.encrypt(secKeyTwo, plaintextThree); + expect(ciphertextOne).toEqual(ciphertextTwo); + expect(ciphertextOne).not.toEqual(ciphertextThree); + expect(ciphertextThree).not.toEqual(ciphertextFour); }); } - test("dump and load keys for sum operation with single node", async () => { + test("encryption and decryption for sum operation with single node", async () => { const cluster = { nodes: [{}] }; - const secretKey = await nilql.SecretKey.generate(cluster, { sum: true }); + const secretKey = secretKeyForSumWithOneNode; const publicKey = await nilql.PublicKey.generate(secretKey); - const secretKeyObject = secretKey.dump(); - const secretKeyLoaded = nilql.SecretKey.load(secretKeyObject); - - const publicKeyObject = publicKey.dump(); - const publicKeyLoaded = nilql.PublicKey.load(publicKeyObject); + const plaintextNumber = 123; + const ciphertextFromNumber = await nilql.encrypt( + publicKey, + plaintextNumber, + ); + const decryptedFromNumber = await nilql.decrypt( + secretKey, + ciphertextFromNumber, + ); + expect(BigInt(plaintextNumber)).toEqual(decryptedFromNumber); - const plaintext = BigInt(123); - const ciphertext = await nilql.encrypt(publicKeyLoaded, plaintext); - const decrypted = await nilql.decrypt(secretKeyLoaded, ciphertext); - expect(plaintext).toEqual(decrypted); + const plaintextBigInt = BigInt(123); + const ciphertextFromBigInt = await nilql.encrypt( + publicKey, + plaintextBigInt, + ); + const decryptedFromBigInt = await nilql.decrypt( + secretKey, + ciphertextFromBigInt, + ); + expect(plaintextBigInt).toEqual(decryptedFromBigInt); }); - test("dump and load key for sum operation with multiple nodes", async () => { - const cluster = { nodes: [{}, {}, {}] }; - const secretKey = await nilql.SecretKey.generate(cluster, { sum: true }); + test("encryption and decryption for sum operation with multiple nodes", async () => { + const secretKey = await nilql.SecretKey.generate( + { nodes: [{}, {}, {}] }, + { sum: true }, + ); - const secretKeyObject = secretKey.dump(); - const secretKeyLoaded = nilql.SecretKey.load(secretKeyObject); + const plaintextNumber = 123; + const ciphertextFromNumber = await nilql.encrypt( + secretKey, + plaintextNumber, + ); + const decryptedFromNumber = await nilql.decrypt( + secretKey, + ciphertextFromNumber, + ); + expect(BigInt(plaintextNumber)).toEqual(decryptedFromNumber); - const plaintext = BigInt(123); - const ciphertext = await nilql.encrypt(secretKey, plaintext); - const decrypted = await nilql.decrypt(secretKeyLoaded, ciphertext); - expect(plaintext).toEqual(decrypted); + const plaintextBigInt = BigInt(123); + const ciphertextFromBigInt = await nilql.encrypt( + secretKey, + plaintextBigInt, + ); + const decryptedFromBigInt = await nilql.decrypt( + secretKey, + ciphertextFromBigInt, + ); + expect(plaintextBigInt).toEqual(decryptedFromBigInt); }); }); /** - * Representation compatibility/portability tests. + * Tests of the portable representation of ciphertexts. */ -describe("representation", () => { +describe("portable representation of ciphertexts", () => { test("secret share representation for store operation with multiple nodes", async () => { const cluster = { nodes: [{}, {}, {}] }; const secretKey = await nilql.SecretKey.generate(cluster, { store: true }); @@ -253,79 +453,125 @@ describe("representation", () => { }); /** - * Tests of functional properties of primitive operators. + * Tests verifying that encryption/decryption methods return expected errors. */ -describe("functionalities", () => { - test("secret key creation for sum operation", async () => { +describe("errors involving encryption and decryption functions", () => { + test("errors in encryption for store operation", async () => { const cluster = { nodes: [{}] }; - const s = await nilql.SecretKey.generate(cluster, { sum: true }); - expect(s.material).not.toBeNull(); - }); - - test("public key creation for sum operation", async () => { - const cluster = { nodes: [{}] }; - const s = await nilql.SecretKey.generate(cluster, { sum: true }); - const p = await nilql.PublicKey.generate(s); - expect(p.material).not.toBeNull(); - }); + const secretKey = await nilql.SecretKey.generate(cluster, { store: true }); + const plaintext = "x".repeat(4097); - test("secret key creation for match operation", async () => { - const cluster = { nodes: [{}] }; - const s = await nilql.SecretKey.generate(cluster, { match: true }); - expect(s.material).not.toBeNull(); + try { + await nilql.encrypt(secretKey, plaintext); + } catch (e) { + expect(e).toStrictEqual( + TypeError( + "plaintext string must be possible to encode in 4096 bytes or fewer", + ), + ); + } }); - test("encryption of number for match operation", async () => { + test("errors in encryption for match operation", async () => { const cluster = { nodes: [{}] }; const secretKey = await nilql.SecretKey.generate(cluster, { match: true }); + const plaintext = "x".repeat(4097); - const plaintextNumber = 123; - const ciphertextFromNumber = (await nilql.encrypt( - secretKey, - plaintextNumber, - )) as string; - expect(ciphertextFromNumber.length > 64).toEqual(true); - - const plaintextBigInt = BigInt(123); - const ciphertextFromBigInt = (await nilql.encrypt( - secretKey, - plaintextBigInt, - )) as string; - expect(ciphertextFromBigInt.length > 64).toEqual(true); + try { + await nilql.encrypt(secretKey, plaintext); + } catch (e) { + expect(e).toStrictEqual( + TypeError( + "plaintext string must be possible to encode in 4096 bytes or fewer", + ), + ); + } }); - test("encryption of string for match operation", async () => { - const cluster = { nodes: [{}] }; - const secretKey = await nilql.SecretKey.generate(cluster, { match: true }); + test("errors in encryption for sum operation", async () => { + const secretKey = secretKeyForSumWithOneNode; + const publicKey = await nilql.PublicKey.generate(secretKey); + + try { + await nilql.encrypt(publicKey, "ABC"); + } catch (e) { + expect(e).toStrictEqual( + TypeError( + "plaintext to encrypt for sum operation must be number or bigint", + ), + ); + } - const plaintext = "ABC"; - const ciphertext = (await nilql.encrypt(secretKey, plaintext)) as string; - expect(ciphertext.length > 64).toEqual(true); + for (const plaintext of [ + -123, + BigInt(-123), + (-2) ** 31, + 2 ** 31 - 1, + -BigInt(2 ** 31), + BigInt(2 ** 31) - BigInt(1), + ]) { + try { + await nilql.encrypt(publicKey, plaintext); + } catch (e) { + expect(e).toStrictEqual( + TypeError("numeric plaintext must be a valid 32-bit signed integer"), + ); + } + } }); - test("encryption for sum operation", async () => { - const cluster = { nodes: [{}] }; - const secretKey = await nilql.SecretKey.generate(cluster, { sum: true }); - const publicKey = await nilql.PublicKey.generate(secretKey); + test("errors in decryption due to cluster size mismatch", async () => { + const secretKeyOne = await nilql.SecretKey.generate( + { nodes: [{}] }, + { store: true }, + ); + const secretKeyTwo = await nilql.SecretKey.generate( + { nodes: [{}, {}] }, + { store: true }, + ); + const secretKeyThree = await nilql.SecretKey.generate( + { nodes: [{}, {}, {}] }, + { store: true }, + ); + + const ciphertextOne = await nilql.encrypt(secretKeyOne, 123); + const ciphertextTwo = await nilql.encrypt(secretKeyTwo, 123); - const plaintextNumber = 123; - const ciphertextFromNumber = (await nilql.encrypt( - publicKey, - plaintextNumber, - )) as bigint; - expect(ciphertextFromNumber).not.toBeNull(); + try { + await nilql.decrypt(secretKeyOne, ciphertextTwo); + } catch (e) { + expect(e).toStrictEqual( + TypeError( + "secret key requires a valid ciphertext from a single-node cluster", + ), + ); + } - const plaintextBigInt = BigInt(123); - const ciphertextFromBigInt = (await nilql.encrypt( - publicKey, - plaintextBigInt, - )) as bigint; - expect(ciphertextFromBigInt).not.toBeNull(); + try { + await nilql.decrypt(secretKeyOne, ciphertextOne); + } catch (e) { + expect(e).toStrictEqual( + TypeError( + "secret key requires a valid ciphertext from a multi-node cluster", + ), + ); + } + + try { + await nilql.decrypt(secretKeyThree, ciphertextTwo); + } catch (e) { + expect(e).toStrictEqual( + TypeError( + "secret key and ciphertext must have the same associated cluster size", + ), + ); + } }); - test("decryption for sum operation", async () => { + test("errors in decryption for sum operation", async () => { const cluster = { nodes: [{}] }; - const secretKey = await nilql.SecretKey.generate(cluster, { sum: true }); + const secretKey = secretKeyForSumWithOneNode; + const secretKeyAlt = await nilql.SecretKey.generate(cluster, { sum: true }); const publicKey = await nilql.PublicKey.generate(secretKey); const plaintextNumber = 123; @@ -333,29 +579,21 @@ describe("functionalities", () => { publicKey, plaintextNumber, )) as bigint; - const decryptedFromNumber = (await nilql.decrypt( - secretKey, - ciphertextFromNumber, - )) as bigint; - expect(BigInt(plaintextNumber) === decryptedFromNumber).toEqual(true); - const plaintextBigInt = BigInt(123); - const ciphertextFromBigInt = (await nilql.encrypt( - publicKey, - plaintextBigInt, - )) as bigint; - const decryptedFromBigInt = (await nilql.decrypt( - secretKey, - ciphertextFromBigInt, - )) as bigint; - expect(plaintextBigInt === decryptedFromBigInt).toEqual(true); + try { + await nilql.decrypt(secretKeyAlt, ciphertextFromNumber); + } catch (e) { + expect(e).toStrictEqual( + TypeError("ciphertext cannot be decrypted using supplied secret key"), + ); + } }, 10000); }); /** - * End-to-end workflow tests. + * Tests involving end-to-end workflows. */ -describe("workflows", () => { +describe("end-to-end workflows", () => { const clusters = [{ nodes: [{}] }, { nodes: [{}, {}, {}, {}, {}] }]; const plaintexts = [ @@ -394,9 +632,12 @@ describe("workflows", () => { for (const number of numbers) { test(`end-to-end workflow for sum operation: ${number}`, async () => { - const secretKey = await nilql.SecretKey.generate(cluster, { - sum: true, - }); + const secretKey = + cluster.nodes.length === 1 + ? secretKeyForSumWithOneNode + : await nilql.SecretKey.generate(cluster, { + sum: true, + }); const ciphertext = await nilql.encrypt(secretKey, number); const decrypted = await nilql.decrypt(secretKey, ciphertext); expect(BigInt(number)).toEqual(BigInt(decrypted)); @@ -406,10 +647,7 @@ describe("workflows", () => { for (const number of numbers) { test(`end-to-end workflow for sum operation: ${number}`, async () => { - const secretKey = await nilql.SecretKey.generate( - { nodes: [{}] }, - { sum: true }, - ); + const secretKey = secretKeyForSumWithOneNode; const publicKey = await nilql.PublicKey.generate(secretKey); const ciphertext = await nilql.encrypt(publicKey, number); const decrypted = await nilql.decrypt(secretKey, ciphertext);