diff --git a/__tests__/symmetricEncrypt.test.ts b/__tests__/symmetricEncrypt.test.ts index 6c8d46a..147e814 100644 --- a/__tests__/symmetricEncrypt.test.ts +++ b/__tests__/symmetricEncrypt.test.ts @@ -17,7 +17,66 @@ const arraysAreEqual = (arr1: Uint8Array, arr2: Uint8Array): boolean => { }; describe("Encryption and decryption with symmetric key test suite.", () => { - test("Encryption and decryption work.", async () => { + test("End to End encryption and decryption work.", async () => { + const message = await dcrypto.randomBytes(32); + const aliceKeyPair = await dcrypto.keyPair(); + const bobKeyPair = await dcrypto.keyPair(); + + const previousBlockHash = await dcrypto.randomBytes( + crypto_hash_sha512_BYTES, + ); + + const encrypted = await dcrypto.encrypt( + message, + bobKeyPair.publicKey, + aliceKeyPair.secretKey, + previousBlockHash, + ); + const decrypted = await dcrypto.decrypt( + encrypted, + aliceKeyPair.publicKey, + bobKeyPair.secretKey, + previousBlockHash, + ); + + const encryptionMemory = dcrypto.loadWasmMemory.encrypt( + message.length, + crypto_hash_sha512_BYTES, + ); + const encryptionModule = await dcrypto.loadWasmModule({ + wasmMemory: encryptionMemory, + }); + const encryptedWithModule = await dcrypto.encrypt( + message, + bobKeyPair.publicKey, + aliceKeyPair.secretKey, + previousBlockHash, + encryptionModule, + ); + + const decryptionMemory = dcrypto.loadWasmMemory.decrypt( + encrypted.length, + crypto_hash_sha512_BYTES, + ); + const decryptionModule = await dcrypto.loadWasmModule({ + wasmMemory: decryptionMemory, + }); + const decryptedWithModule = await dcrypto.decrypt( + encrypted, + aliceKeyPair.publicKey, + bobKeyPair.secretKey, + previousBlockHash, + decryptionModule, + ); + + expect(decrypted[0]).toBe(message[0]); + expect(decrypted[1]).toBe(message[1]); + expect(decrypted[31]).toBe(message[31]); + expect(arraysAreEqual(encryptedWithModule, encrypted)).toBe(false); + expect(arraysAreEqual(decryptedWithModule, decrypted)).toBe(true); + }); + + test("Encryption and decryption with provided key work.", async () => { const message = await dcrypto.randomBytes(32); const key = await dcrypto.randomBytes(crypto_kx_SESSIONKEYBYTES); diff --git a/examples/browser/index.html b/examples/browser/index.html index 8cf7c2a..7b299c3 100644 --- a/examples/browser/index.html +++ b/examples/browser/index.html @@ -93,6 +93,35 @@ Original : ${uint8ToHex(message)}\n`, ); + const aliceKeyPair = await dcrypto.keyPairFromMnemonic(mnemonic4); + const bobKeyPair = await dcrypto.keyPairFromMnemonic(mnemonic3); + + const e2eencrypted = await dcrypto.encrypt( + message, + bobKeyPair.publicKey, + aliceKeyPair.secretKey, + hash, + ); + + console.log( + `AEAD e2e encrypted box for random message between alice and bob: ${uint8ToHex( + encrypted, + )}`, + ); + + const e2edecrypted = await dcrypto.decrypt( + encrypted, + aliceKeyPair.publicKey, + bobKeyPair.secretKey, + hash, + ); + + console.log( + `E2E decrypted AEAD box should be equal to random message: \n\ + Decrypted: ${uint8ToHex(e2edecrypted)} \n\ + Original : ${uint8ToHex(message)}\n`, + ); + const sharesLen = 255; const threshold = 165; const shares = await dcrypto.splitSecret( diff --git a/package-lock.json b/package-lock.json index 8d78eb3..46db05c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@deliberative/crypto", - "version": "0.6.18", + "version": "0.7.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@deliberative/crypto", - "version": "0.6.18", + "version": "0.7.0", "license": "Apache-2.0", "devDependencies": { "@rollup/plugin-commonjs": "^23.0.0", @@ -1566,9 +1566,9 @@ } }, "node_modules/@types/jest": { - "version": "29.2.3", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.3.tgz", - "integrity": "sha512-6XwoEbmatfyoCjWRX7z0fKMmgYKe9+/HrviJ5k0X/tjJWHGAezZOfYaxqQKuzG/TvQyr+ktjm4jgbk0s4/oF2w==", + "version": "29.2.4", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.4.tgz", + "integrity": "sha512-PipFB04k2qTRPePduVLTRiPzQfvMeLwUN3Z21hsAKaB/W9IIzgB2pizCL466ftJlcyZqnHoC9ZHpxLGl3fS86A==", "dev": true, "dependencies": { "expect": "^29.0.0", @@ -1599,9 +1599,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "18.11.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.10.tgz", - "integrity": "sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ==", + "version": "18.11.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.11.tgz", + "integrity": "sha512-KJ021B1nlQUBLopzZmPBVuGU9un7WJd/W4ya7Ih02B4Uwky5Nja0yGYav2EfYIk0RR2Q9oVhf60S2XR1BCWJ2g==", "dev": true }, "node_modules/@types/prettier": { @@ -1635,9 +1635,9 @@ "dev": true }, "node_modules/@types/yargs": { - "version": "17.0.15", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.15.tgz", - "integrity": "sha512-ZHc4W2dnEQPfhn06TBEdWaiUHEZAocYaiVMfwOipY5jcJt/251wVrKCBWBetGZWO5CF8tdb7L3DmdxVlZ2BOIg==", + "version": "17.0.16", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.16.tgz", + "integrity": "sha512-Mh3OP0oh8X7O7F9m5AplC+XHYLBWuPKNkGVD3gIZFLFebBnuFI2Nz5Sf8WLvwGxECJ8YjifQvFdh79ubODkdug==", "dev": true, "dependencies": { "@types/yargs-parser": "*" @@ -1650,14 +1650,14 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.45.0.tgz", - "integrity": "sha512-CXXHNlf0oL+Yg021cxgOdMHNTXD17rHkq7iW6RFHoybdFgQBjU3yIXhhcPpGwr1CjZlo6ET8C6tzX5juQoXeGA==", + "version": "5.45.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.45.1.tgz", + "integrity": "sha512-cOizjPlKEh0bXdFrBLTrI/J6B/QMlhwE9auOov53tgB+qMukH6/h8YAK/qw+QJGct/PTbdh2lytGyipxCcEtAw==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.45.0", - "@typescript-eslint/type-utils": "5.45.0", - "@typescript-eslint/utils": "5.45.0", + "@typescript-eslint/scope-manager": "5.45.1", + "@typescript-eslint/type-utils": "5.45.1", + "@typescript-eslint/utils": "5.45.1", "debug": "^4.3.4", "ignore": "^5.2.0", "natural-compare-lite": "^1.4.0", @@ -1683,14 +1683,14 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "5.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.45.0.tgz", - "integrity": "sha512-brvs/WSM4fKUmF5Ot/gEve6qYiCMjm6w4HkHPfS6ZNmxTS0m0iNN4yOChImaCkqc1hRwFGqUyanMXuGal6oyyQ==", + "version": "5.45.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.45.1.tgz", + "integrity": "sha512-JQ3Ep8bEOXu16q0ztsatp/iQfDCtvap7sp/DKo7DWltUquj5AfCOpX2zSzJ8YkAVnrQNqQ5R62PBz2UtrfmCkA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "5.45.0", - "@typescript-eslint/types": "5.45.0", - "@typescript-eslint/typescript-estree": "5.45.0", + "@typescript-eslint/scope-manager": "5.45.1", + "@typescript-eslint/types": "5.45.1", + "@typescript-eslint/typescript-estree": "5.45.1", "debug": "^4.3.4" }, "engines": { @@ -1710,13 +1710,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "5.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.45.0.tgz", - "integrity": "sha512-noDMjr87Arp/PuVrtvN3dXiJstQR1+XlQ4R1EvzG+NMgXi8CuMCXpb8JqNtFHKceVSQ985BZhfRdowJzbv4yKw==", + "version": "5.45.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.45.1.tgz", + "integrity": "sha512-D6fCileR6Iai7E35Eb4Kp+k0iW7F1wxXYrOhX/3dywsOJpJAQ20Fwgcf+P/TDtvQ7zcsWsrJaglaQWDhOMsspQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.45.0", - "@typescript-eslint/visitor-keys": "5.45.0" + "@typescript-eslint/types": "5.45.1", + "@typescript-eslint/visitor-keys": "5.45.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1727,13 +1727,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "5.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.45.0.tgz", - "integrity": "sha512-DY7BXVFSIGRGFZ574hTEyLPRiQIvI/9oGcN8t1A7f6zIs6ftbrU0nhyV26ZW//6f85avkwrLag424n+fkuoJ1Q==", + "version": "5.45.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.45.1.tgz", + "integrity": "sha512-aosxFa+0CoYgYEl3aptLe1svP910DJq68nwEJzyQcrtRhC4BN0tJAvZGAe+D0tzjJmFXe+h4leSsiZhwBa2vrA==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "5.45.0", - "@typescript-eslint/utils": "5.45.0", + "@typescript-eslint/typescript-estree": "5.45.1", + "@typescript-eslint/utils": "5.45.1", "debug": "^4.3.4", "tsutils": "^3.21.0" }, @@ -1754,9 +1754,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "5.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.45.0.tgz", - "integrity": "sha512-QQij+u/vgskA66azc9dCmx+rev79PzX8uDHpsqSjEFtfF2gBUTRCpvYMh2gw2ghkJabNkPlSUCimsyBEQZd1DA==", + "version": "5.45.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.45.1.tgz", + "integrity": "sha512-HEW3U0E5dLjUT+nk7b4lLbOherS1U4ap+b9pfu2oGsW3oPu7genRaY9dDv3nMczC1rbnRY2W/D7SN05wYoGImg==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1767,13 +1767,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.45.0.tgz", - "integrity": "sha512-maRhLGSzqUpFcZgXxg1qc/+H0bT36lHK4APhp0AEUVrpSwXiRAomm/JGjSG+kNUio5kAa3uekCYu/47cnGn5EQ==", + "version": "5.45.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.45.1.tgz", + "integrity": "sha512-76NZpmpCzWVrrb0XmYEpbwOz/FENBi+5W7ipVXAsG3OoFrQKJMiaqsBMbvGRyLtPotGqUfcY7Ur8j0dksDJDng==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.45.0", - "@typescript-eslint/visitor-keys": "5.45.0", + "@typescript-eslint/types": "5.45.1", + "@typescript-eslint/visitor-keys": "5.45.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1794,16 +1794,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "5.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.45.0.tgz", - "integrity": "sha512-OUg2JvsVI1oIee/SwiejTot2OxwU8a7UfTFMOdlhD2y+Hl6memUSL4s98bpUTo8EpVEr0lmwlU7JSu/p2QpSvA==", + "version": "5.45.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.45.1.tgz", + "integrity": "sha512-rlbC5VZz68+yjAzQBc4I7KDYVzWG2X/OrqoZrMahYq3u8FFtmQYc+9rovo/7wlJH5kugJ+jQXV5pJMnofGmPRw==", "dev": true, "dependencies": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.45.0", - "@typescript-eslint/types": "5.45.0", - "@typescript-eslint/typescript-estree": "5.45.0", + "@typescript-eslint/scope-manager": "5.45.1", + "@typescript-eslint/types": "5.45.1", + "@typescript-eslint/typescript-estree": "5.45.1", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" @@ -1820,12 +1820,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.45.0.tgz", - "integrity": "sha512-jc6Eccbn2RtQPr1s7th6jJWQHBHI6GBVQkCHoJFQ5UreaKm59Vxw+ynQUPPY2u2Amquc+7tmEoC2G52ApsGNNg==", + "version": "5.45.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.45.1.tgz", + "integrity": "sha512-cy9ln+6rmthYWjH9fmx+5FU/JDpjQb586++x2FZlveq7GdGuLLW9a2Jcst2TGekH82bXpfmRNSwP9tyEs6RjvQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "5.45.0", + "@typescript-eslint/types": "5.45.1", "eslint-visitor-keys": "^3.3.0" }, "engines": { @@ -2582,9 +2582,9 @@ } }, "node_modules/decimal.js": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.2.tgz", - "integrity": "sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", "dev": true }, "node_modules/dedent": { @@ -6505,9 +6505,9 @@ } }, "node_modules/rollup": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.5.1.tgz", - "integrity": "sha512-hdQWTvPeiAbM6SUkxV70HdGUVxsgsc+CLy5fuh4KdgUBJ0SowXiix8gANgXoG3wEuLwfoJhCT2V+WwxfWq9Ikw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.6.0.tgz", + "integrity": "sha512-qCgiBeSu2/AIOKWGFMiRkjPlGlcVwxAjwpGKQZOQYng+83Hip4PjrWHm7EQX1wnrvRqfTytEihRRfLHdX+hR4g==", "dev": true, "bin": { "rollup": "dist/bin/rollup" @@ -6698,6 +6698,7 @@ "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", "dev": true }, "node_modules/spdx-correct": { @@ -8736,9 +8737,9 @@ } }, "@types/jest": { - "version": "29.2.3", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.3.tgz", - "integrity": "sha512-6XwoEbmatfyoCjWRX7z0fKMmgYKe9+/HrviJ5k0X/tjJWHGAezZOfYaxqQKuzG/TvQyr+ktjm4jgbk0s4/oF2w==", + "version": "29.2.4", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.2.4.tgz", + "integrity": "sha512-PipFB04k2qTRPePduVLTRiPzQfvMeLwUN3Z21hsAKaB/W9IIzgB2pizCL466ftJlcyZqnHoC9ZHpxLGl3fS86A==", "dev": true, "requires": { "expect": "^29.0.0", @@ -8769,9 +8770,9 @@ "dev": true }, "@types/node": { - "version": "18.11.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.10.tgz", - "integrity": "sha512-juG3RWMBOqcOuXC643OAdSA525V44cVgGV6dUDuiFtss+8Fk5x1hI93Rsld43VeJVIeqlP9I7Fn9/qaVqoEAuQ==", + "version": "18.11.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.11.tgz", + "integrity": "sha512-KJ021B1nlQUBLopzZmPBVuGU9un7WJd/W4ya7Ih02B4Uwky5Nja0yGYav2EfYIk0RR2Q9oVhf60S2XR1BCWJ2g==", "dev": true }, "@types/prettier": { @@ -8805,9 +8806,9 @@ "dev": true }, "@types/yargs": { - "version": "17.0.15", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.15.tgz", - "integrity": "sha512-ZHc4W2dnEQPfhn06TBEdWaiUHEZAocYaiVMfwOipY5jcJt/251wVrKCBWBetGZWO5CF8tdb7L3DmdxVlZ2BOIg==", + "version": "17.0.16", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.16.tgz", + "integrity": "sha512-Mh3OP0oh8X7O7F9m5AplC+XHYLBWuPKNkGVD3gIZFLFebBnuFI2Nz5Sf8WLvwGxECJ8YjifQvFdh79ubODkdug==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -8820,14 +8821,14 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "5.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.45.0.tgz", - "integrity": "sha512-CXXHNlf0oL+Yg021cxgOdMHNTXD17rHkq7iW6RFHoybdFgQBjU3yIXhhcPpGwr1CjZlo6ET8C6tzX5juQoXeGA==", + "version": "5.45.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.45.1.tgz", + "integrity": "sha512-cOizjPlKEh0bXdFrBLTrI/J6B/QMlhwE9auOov53tgB+qMukH6/h8YAK/qw+QJGct/PTbdh2lytGyipxCcEtAw==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.45.0", - "@typescript-eslint/type-utils": "5.45.0", - "@typescript-eslint/utils": "5.45.0", + "@typescript-eslint/scope-manager": "5.45.1", + "@typescript-eslint/type-utils": "5.45.1", + "@typescript-eslint/utils": "5.45.1", "debug": "^4.3.4", "ignore": "^5.2.0", "natural-compare-lite": "^1.4.0", @@ -8837,53 +8838,53 @@ } }, "@typescript-eslint/parser": { - "version": "5.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.45.0.tgz", - "integrity": "sha512-brvs/WSM4fKUmF5Ot/gEve6qYiCMjm6w4HkHPfS6ZNmxTS0m0iNN4yOChImaCkqc1hRwFGqUyanMXuGal6oyyQ==", + "version": "5.45.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.45.1.tgz", + "integrity": "sha512-JQ3Ep8bEOXu16q0ztsatp/iQfDCtvap7sp/DKo7DWltUquj5AfCOpX2zSzJ8YkAVnrQNqQ5R62PBz2UtrfmCkA==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "5.45.0", - "@typescript-eslint/types": "5.45.0", - "@typescript-eslint/typescript-estree": "5.45.0", + "@typescript-eslint/scope-manager": "5.45.1", + "@typescript-eslint/types": "5.45.1", + "@typescript-eslint/typescript-estree": "5.45.1", "debug": "^4.3.4" } }, "@typescript-eslint/scope-manager": { - "version": "5.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.45.0.tgz", - "integrity": "sha512-noDMjr87Arp/PuVrtvN3dXiJstQR1+XlQ4R1EvzG+NMgXi8CuMCXpb8JqNtFHKceVSQ985BZhfRdowJzbv4yKw==", + "version": "5.45.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.45.1.tgz", + "integrity": "sha512-D6fCileR6Iai7E35Eb4Kp+k0iW7F1wxXYrOhX/3dywsOJpJAQ20Fwgcf+P/TDtvQ7zcsWsrJaglaQWDhOMsspQ==", "dev": true, "requires": { - "@typescript-eslint/types": "5.45.0", - "@typescript-eslint/visitor-keys": "5.45.0" + "@typescript-eslint/types": "5.45.1", + "@typescript-eslint/visitor-keys": "5.45.1" } }, "@typescript-eslint/type-utils": { - "version": "5.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.45.0.tgz", - "integrity": "sha512-DY7BXVFSIGRGFZ574hTEyLPRiQIvI/9oGcN8t1A7f6zIs6ftbrU0nhyV26ZW//6f85avkwrLag424n+fkuoJ1Q==", + "version": "5.45.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.45.1.tgz", + "integrity": "sha512-aosxFa+0CoYgYEl3aptLe1svP910DJq68nwEJzyQcrtRhC4BN0tJAvZGAe+D0tzjJmFXe+h4leSsiZhwBa2vrA==", "dev": true, "requires": { - "@typescript-eslint/typescript-estree": "5.45.0", - "@typescript-eslint/utils": "5.45.0", + "@typescript-eslint/typescript-estree": "5.45.1", + "@typescript-eslint/utils": "5.45.1", "debug": "^4.3.4", "tsutils": "^3.21.0" } }, "@typescript-eslint/types": { - "version": "5.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.45.0.tgz", - "integrity": "sha512-QQij+u/vgskA66azc9dCmx+rev79PzX8uDHpsqSjEFtfF2gBUTRCpvYMh2gw2ghkJabNkPlSUCimsyBEQZd1DA==", + "version": "5.45.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.45.1.tgz", + "integrity": "sha512-HEW3U0E5dLjUT+nk7b4lLbOherS1U4ap+b9pfu2oGsW3oPu7genRaY9dDv3nMczC1rbnRY2W/D7SN05wYoGImg==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "5.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.45.0.tgz", - "integrity": "sha512-maRhLGSzqUpFcZgXxg1qc/+H0bT36lHK4APhp0AEUVrpSwXiRAomm/JGjSG+kNUio5kAa3uekCYu/47cnGn5EQ==", + "version": "5.45.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.45.1.tgz", + "integrity": "sha512-76NZpmpCzWVrrb0XmYEpbwOz/FENBi+5W7ipVXAsG3OoFrQKJMiaqsBMbvGRyLtPotGqUfcY7Ur8j0dksDJDng==", "dev": true, "requires": { - "@typescript-eslint/types": "5.45.0", - "@typescript-eslint/visitor-keys": "5.45.0", + "@typescript-eslint/types": "5.45.1", + "@typescript-eslint/visitor-keys": "5.45.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -8892,28 +8893,28 @@ } }, "@typescript-eslint/utils": { - "version": "5.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.45.0.tgz", - "integrity": "sha512-OUg2JvsVI1oIee/SwiejTot2OxwU8a7UfTFMOdlhD2y+Hl6memUSL4s98bpUTo8EpVEr0lmwlU7JSu/p2QpSvA==", + "version": "5.45.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.45.1.tgz", + "integrity": "sha512-rlbC5VZz68+yjAzQBc4I7KDYVzWG2X/OrqoZrMahYq3u8FFtmQYc+9rovo/7wlJH5kugJ+jQXV5pJMnofGmPRw==", "dev": true, "requires": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.45.0", - "@typescript-eslint/types": "5.45.0", - "@typescript-eslint/typescript-estree": "5.45.0", + "@typescript-eslint/scope-manager": "5.45.1", + "@typescript-eslint/types": "5.45.1", + "@typescript-eslint/typescript-estree": "5.45.1", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" } }, "@typescript-eslint/visitor-keys": { - "version": "5.45.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.45.0.tgz", - "integrity": "sha512-jc6Eccbn2RtQPr1s7th6jJWQHBHI6GBVQkCHoJFQ5UreaKm59Vxw+ynQUPPY2u2Amquc+7tmEoC2G52ApsGNNg==", + "version": "5.45.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.45.1.tgz", + "integrity": "sha512-cy9ln+6rmthYWjH9fmx+5FU/JDpjQb586++x2FZlveq7GdGuLLW9a2Jcst2TGekH82bXpfmRNSwP9tyEs6RjvQ==", "dev": true, "requires": { - "@typescript-eslint/types": "5.45.0", + "@typescript-eslint/types": "5.45.1", "eslint-visitor-keys": "^3.3.0" } }, @@ -9471,9 +9472,9 @@ } }, "decimal.js": { - "version": "10.4.2", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.2.tgz", - "integrity": "sha512-ic1yEvwT6GuvaYwBLLY6/aFFgjZdySKTE8en/fkU3QICTmRtgtSlFn0u0BXN06InZwtfCelR7j8LRiDI/02iGA==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", "dev": true }, "dedent": { @@ -12397,9 +12398,9 @@ } }, "rollup": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.5.1.tgz", - "integrity": "sha512-hdQWTvPeiAbM6SUkxV70HdGUVxsgsc+CLy5fuh4KdgUBJ0SowXiix8gANgXoG3wEuLwfoJhCT2V+WwxfWq9Ikw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.6.0.tgz", + "integrity": "sha512-qCgiBeSu2/AIOKWGFMiRkjPlGlcVwxAjwpGKQZOQYng+83Hip4PjrWHm7EQX1wnrvRqfTytEihRRfLHdX+hR4g==", "dev": true, "requires": { "fsevents": "~2.3.2" diff --git a/package.json b/package.json index a04dd53..9b85311 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@deliberative/crypto", "description": "Libsodium and Shamir secret sharing wasm module for nodejs and the browser.", - "version": "0.7.0", + "version": "0.7.1", "repository": { "type": "git", "url": "https://github.com/deliberative/crypto.git" diff --git a/scripts/dcryptoMethodsModule.d.ts b/scripts/dcryptoMethodsModule.d.ts index a1082af..7f7133c 100644 --- a/scripts/dcryptoMethodsModule.d.ts +++ b/scripts/dcryptoMethodsModule.d.ts @@ -59,6 +59,25 @@ export interface DCryptoMethodsModule extends EmscriptenModule { data: number, // Uint8Array, ): number; + _e2e_encrypt_data( + DATA_LEN: number, + data: number, // Uint8Array, + public_key: number, // Uint8Array, + secret_key: number, // Uint8Array, + ADDITIONAL_DATA_LEN: number, + additional_data: number, // Uint8Array, + encrypted: number, // Uint8Array, + ): number; + _e2e_decrypt_data( + ENCRYPTED_LEN: number, + encrypted_data: number, // Uint8Array, + public_key: number, // Uint8Array, + secret_key: number, // Uint8Array, + ADDITIONAL_DATA_LEN: number, + additional_data: number, // Uint8Array, + data: number, // Uint8Array, + ): number; + _forward_secretbox_encrypt_data( DATA_LEN: number, data: number, // Uint8Array, diff --git a/src/c/dcrypto.c b/src/c/dcrypto.c index 2658622..6e6f83c 100644 --- a/src/c/dcrypto.c +++ b/src/c/dcrypto.c @@ -233,6 +233,144 @@ calculate_forward_secret_nonce( free(nonce_sha512); } +/* Returns (nonce || encrypted_data || auth tag) */ +__attribute__((used)) int +e2e_encrypt_data( + const int DATA_LEN, const uint8_t data[DATA_LEN], + const uint8_t public_key[crypto_sign_ed25519_PUBLICKEYBYTES], + const uint8_t secret_key[crypto_sign_ed25519_SECRETKEYBYTES], + const int ADDITIONAL_DATA_LEN, + const uint8_t additional_data[ADDITIONAL_DATA_LEN], + uint8_t encrypted[crypto_aead_chacha20poly1305_ietf_NPUBBYTES + DATA_LEN + + crypto_aead_chacha20poly1305_ietf_ABYTES]) +{ + /* if (DATA_LEN > 2^12) return -2; // Encrypted data larger than expected + */ + + unsigned long long CIPHERTEXT_LEN + = DATA_LEN + crypto_aead_chacha20poly1305_ietf_ABYTES; + uint8_t *ciphertext = sodium_malloc(CIPHERTEXT_LEN); + + uint8_t *sender_x25519_pk = malloc(crypto_aead_chacha20poly1305_KEYBYTES); + uint8_t *sender_x25519_sk = sodium_malloc(crypto_scalarmult_curve25519_BYTES); + crypto_sign_ed25519_sk_to_curve25519(sender_x25519_sk, secret_key); + crypto_scalarmult_curve25519_base(sender_x25519_pk, sender_x25519_sk); + + uint8_t *receiver_x25519_pk = malloc(crypto_scalarmult_curve25519_BYTES); + int converted_pk + = crypto_sign_ed25519_pk_to_curve25519(receiver_x25519_pk, public_key); + if (converted_pk != 0) + { + free(receiver_x25519_pk); + free(sender_x25519_pk); + sodium_free(sender_x25519_sk); + sodium_free(ciphertext); + + return -1; + } + + uint8_t *server_tx = sodium_malloc(crypto_kx_SESSIONKEYBYTES); + int created = crypto_kx_server_session_keys( + NULL, server_tx, sender_x25519_pk, sender_x25519_sk, receiver_x25519_pk); + sodium_free(sender_x25519_sk); + if (created != 0) + { + free(receiver_x25519_pk); + free(sender_x25519_pk); + sodium_free(server_tx); + sodium_free(ciphertext); + + return -2; + } + + uint8_t *nonce = malloc(crypto_aead_chacha20poly1305_ietf_NPUBBYTES); + calculate_forward_secret_nonce(nonce, sender_x25519_pk, receiver_x25519_pk); + free(sender_x25519_pk); + free(receiver_x25519_pk); + + crypto_aead_chacha20poly1305_ietf_encrypt( + ciphertext, &CIPHERTEXT_LEN, data, DATA_LEN, additional_data, + ADDITIONAL_DATA_LEN, NULL, nonce, server_tx); + sodium_free(server_tx); + + memcpy(encrypted, nonce, crypto_aead_chacha20poly1305_ietf_NPUBBYTES); + free(nonce); + + memcpy(encrypted + crypto_aead_chacha20poly1305_ietf_NPUBBYTES, ciphertext, + CIPHERTEXT_LEN); + sodium_free(ciphertext); + + return 0; +} + +__attribute__((used)) int +e2e_decrypt_data( + const int ENCRYPTED_LEN, const uint8_t encrypted_data[ENCRYPTED_LEN], + const uint8_t public_key[crypto_sign_ed25519_PUBLICKEYBYTES], + const uint8_t secret_key[crypto_sign_ed25519_SECRETKEYBYTES], + const int ADDITIONAL_DATA_LEN, + const uint8_t additional_data[ADDITIONAL_DATA_LEN], + uint8_t data[ENCRYPTED_LEN - crypto_aead_chacha20poly1305_ietf_NPUBBYTES + - crypto_aead_chacha20poly1305_ietf_ABYTES]) +{ + unsigned long long DATA_LEN = ENCRYPTED_LEN + - crypto_aead_chacha20poly1305_ietf_NPUBBYTES + - crypto_aead_chacha20poly1305_ietf_ABYTES; + + uint8_t *sender_x25519_pk = malloc(crypto_scalarmult_curve25519_BYTES); + int converted_pk + = crypto_sign_ed25519_pk_to_curve25519(sender_x25519_pk, public_key); + if (converted_pk != 0) + { + free(sender_x25519_pk); + + return -1; + } + + uint8_t *nonce = malloc(crypto_aead_chacha20poly1305_ietf_NPUBBYTES); + memcpy(nonce, encrypted_data, crypto_aead_chacha20poly1305_ietf_NPUBBYTES); + + uint8_t *receiver_x25519_pk = malloc(crypto_aead_chacha20poly1305_KEYBYTES); + uint8_t *receiver_x25519_sk + = sodium_malloc(crypto_scalarmult_curve25519_BYTES); + crypto_sign_ed25519_sk_to_curve25519(receiver_x25519_sk, secret_key); + crypto_scalarmult_curve25519_base(receiver_x25519_pk, receiver_x25519_sk); + + uint8_t *client_rx = sodium_malloc(crypto_kx_SESSIONKEYBYTES); + int created + = crypto_kx_client_session_keys(client_rx, NULL, receiver_x25519_pk, + receiver_x25519_sk, sender_x25519_pk); + free(receiver_x25519_pk); + sodium_free(receiver_x25519_sk); + free(sender_x25519_pk); + if (created != 0) + { + free(nonce); + sodium_free(client_rx); + + return -1; + } + + int CIPHERTEXT_LEN + = ENCRYPTED_LEN - crypto_aead_chacha20poly1305_ietf_NPUBBYTES; + uint8_t *ciphertext = malloc(CIPHERTEXT_LEN); + memcpy(ciphertext, + encrypted_data + crypto_aead_chacha20poly1305_ietf_NPUBBYTES, + CIPHERTEXT_LEN); + + int decrypted = crypto_aead_chacha20poly1305_ietf_decrypt( + data, &DATA_LEN, NULL, ciphertext, CIPHERTEXT_LEN, additional_data, + ADDITIONAL_DATA_LEN, nonce, client_rx); + + free(ciphertext); + sodium_free(client_rx); + free(nonce); + + if (decrypted == 0) return 0; + + return -2; +} + /* Returns (ephemeral_pk || nonce || encrypted_data || auth tag || signature of * ephemeral_pk) */ __attribute__((used)) int @@ -313,9 +451,9 @@ forward_secretbox_decrypt_data( const uint8_t secret_key[crypto_sign_ed25519_SECRETKEYBYTES], const int ADDITIONAL_DATA_LEN, const uint8_t additional_data[ADDITIONAL_DATA_LEN], - uint8_t data[ENCRYPTED_LEN - crypto_aead_chacha20poly1305_ietf_NPUBBYTES - - crypto_aead_chacha20poly1305_ietf_ABYTES - - crypto_scalarmult_curve25519_BYTES]) + uint8_t data[ENCRYPTED_LEN - crypto_scalarmult_curve25519_BYTES + - crypto_aead_chacha20poly1305_ietf_NPUBBYTES + - crypto_aead_chacha20poly1305_ietf_ABYTES]) { int EPHEMERAL_NONCE_LEN = crypto_scalarmult_curve25519_BYTES + crypto_aead_chacha20poly1305_ietf_NPUBBYTES; diff --git a/src/index.ts b/src/index.ts index 0d2f4b1..a4f7492 100644 --- a/src/index.ts +++ b/src/index.ts @@ -39,8 +39,11 @@ const dcrypto = { encryptForwardSecrecy: asymmetric.encrypt, decryptForwardSecrecy: asymmetric.decrypt, - encryptSymmetricKey: symmetric.encrypt, - decryptSymmetricKey: symmetric.decrypt, + encryptSymmetricKey: symmetric.encryptSymmetricKey, + decryptSymmetricKey: symmetric.decryptSymmetricKey, + + encrypt: symmetric.encrypt, + decrypt: symmetric.decrypt, sha512: hash.sha512, argon2: hash.argon2, @@ -79,8 +82,11 @@ const dcrypto = { encryptForwardSecret: asymmetric.memory.encryptMemory, decryptForwardSecret: asymmetric.memory.decryptMemory, - encryptSymmetricKey: symmetric.memory.encryptMemory, - decryptSymmetricKey: symmetric.memory.decryptMemory, + encryptSymmetricKey: symmetric.memory.encryptSymmetricKeyMemory, + decryptSymmetricKey: symmetric.memory.decryptSymmetricKeyMemory, + + encrypt: symmetric.memory.encryptMemory, + decrypt: symmetric.memory.decryptMemory, sha512: hash.memory.sha512Memory, argon2: hash.memory.argon2Memory, diff --git a/src/symmetric/decrypt.ts b/src/symmetric/decrypt.ts index ed0715a..ce181f9 100644 --- a/src/symmetric/decrypt.ts +++ b/src/symmetric/decrypt.ts @@ -20,13 +20,75 @@ import dcryptoMethodsModule from "../c/build/dcryptoMethodsModule"; import type { DCryptoMethodsModule } from "../c/build/dcryptoMethodsModule"; import { - crypto_kx_SESSIONKEYBYTES, + crypto_sign_ed25519_PUBLICKEYBYTES, + crypto_sign_ed25519_SECRETKEYBYTES, getDecryptedLen, } from "../utils/interfaces"; +/** + * Function that decrypts a box with additional data using the + * crypto_aead_chacha20poly1305_ietf_decrypt function from libsodium and + * computes a symmetric key Uint8Array(32) from the sender's + * Ed25519 public key and the receiver's Ed25519 secret key. + * The X25519 key counterparts are computed in wasm from the libsodium provided + * crypto_sign_ed25519_pk_to_curve25519 and crypto_sign_ed25519_sk_to_curve25519 + * functions. + * The symmetric key for encryption is then computed by crypto_kx_client_session_keys. + * The encrypted box is a Uint8Array[nonce 16 || encrypted_data || auth tag 12]. + * + * If you need to perform bulk decryptions with predictable box + * and additional data sizes then it will be more efficient to preload + * the wasm module and reuse it as follows: + * + * ```ts + * const messageLen = message.length; + * const additionalLen = additionalData.length; + * + * const wasmMemory = dcryptoMemory.decryptMemory(messageLen, additionalLen); + * const wasmModule = await dcryptoMethodsModule({ wasmMemory }); + * ``` + * + * If not all boxes and additional data are equal, you can always just use + * the largest Uint8Arrays as inputs. + * + * ```ts + * import dcrypto from \"@deliberative/crypto\" + * + * const message = new Uint8Array(128).fill(1); + * const additionalData = new Uint8Array(64).fill(2); + * + * const aliceKeyPair = await dcrypto.keyPair(); + * const bobKeyPair = await dcrypto.keyPair(); + * + * const box = await dcrypto.encrypt( + * message, + * bobKeyPair.publicKey, + * aliceKeyPair.secretKey, + * additionalData + * ); + * + * const decrypted = await dcrypto.decrypt( + * box, + * aliceKeyPair.publicKey, + * bobKeyPair.secretKey, + * additionalData + * ); + * + * \/\/ message should be equal to decrypted. + * ``` + * + * @param encrypted - the encrypted box including nonce and auth tag + * @param senderPublicKey - the sender public key + * @param receiverSecretKey - the receiver secret key + * @param additionalData - the additional data for aead + * @param module - wasm module in case of bulk decryptions + * + * @returns The decrypted message + */ const decrypt = async ( encrypted: Uint8Array, - key: Uint8Array, + senderPublicKey: Uint8Array, + receiverSecretKey: Uint8Array, additionalData: Uint8Array, module?: DCryptoMethodsModule, ): Promise => { @@ -35,7 +97,7 @@ const decrypt = async ( const wasmMemory = module ? module.wasmMemory - : libsodiumMemory.decryptMemory(len, additionalLen); + : libsodiumMemory.decryptSymmetricKeyMemory(len, additionalLen); const dcryptoModule = module || (await dcryptoMethodsModule({ wasmMemory })); @@ -49,37 +111,46 @@ const decrypt = async ( ); encryptedArray.set(encrypted); - const ptr2 = dcryptoModule._malloc(crypto_kx_SESSIONKEYBYTES); - const k = new Uint8Array( + const ptr2 = dcryptoModule._malloc(crypto_sign_ed25519_PUBLICKEYBYTES); + const pk = new Uint8Array( dcryptoModule.HEAP8.buffer, ptr2, - crypto_kx_SESSIONKEYBYTES, + crypto_sign_ed25519_PUBLICKEYBYTES, ); - k.set(key); + pk.set(senderPublicKey); - const ptr3 = dcryptoModule._malloc( + const ptr3 = dcryptoModule._malloc(crypto_sign_ed25519_SECRETKEYBYTES); + const sk = new Uint8Array( + dcryptoModule.HEAP8.buffer, + ptr3, + crypto_sign_ed25519_SECRETKEYBYTES, + ); + sk.set(receiverSecretKey); + + const ptr4 = dcryptoModule._malloc( additionalLen * Uint8Array.BYTES_PER_ELEMENT, ); const additional = new Uint8Array( dcryptoModule.HEAP8.buffer, - ptr3, + ptr4, additionalLen * Uint8Array.BYTES_PER_ELEMENT, ); additional.set(additionalData); - const ptr4 = dcryptoModule._malloc( + const ptr5 = dcryptoModule._malloc( decryptedLen * Uint8Array.BYTES_PER_ELEMENT, ); const decrypted = new Uint8Array( dcryptoModule.HEAP8.buffer, - ptr4, + ptr5, decryptedLen * Uint8Array.BYTES_PER_ELEMENT, ); - const result = dcryptoModule._decrypt_data( + const result = dcryptoModule._e2e_decrypt_data( len, encryptedArray.byteOffset, - k.byteOffset, + pk.byteOffset, + sk.byteOffset, additionalLen, additional.byteOffset, decrypted.byteOffset, diff --git a/src/symmetric/decryptSymmetricKey.ts b/src/symmetric/decryptSymmetricKey.ts new file mode 100644 index 0000000..562279f --- /dev/null +++ b/src/symmetric/decryptSymmetricKey.ts @@ -0,0 +1,154 @@ +// Copyright (C) 2022 Deliberative Technologies P.C. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import libsodiumMemory from "./memory"; + +import dcryptoMethodsModule from "../c/build/dcryptoMethodsModule"; + +import type { DCryptoMethodsModule } from "../c/build/dcryptoMethodsModule"; + +import { + crypto_kx_SESSIONKEYBYTES, + getDecryptedLen, +} from "../utils/interfaces"; + +/** + * Function that decrypts a box with additional data using the + * crypto_aead_chacha20poly1305_ietf_decrypt function from libsodium and + * a provided symmetric key in Uint8Array(32) format. + * The encrypted box is a Uint8Array[nonce 16 || encrypted_data || auth tag 12]. + * + * If you need to perform bulk decryptions with predictable box + * and additional data sizes then it will be more efficient to preload + * the wasm module and reuse it as follows: + * + * ```ts + * const messageLen = message.length; + * const additionalLen = additionalData.length; + * + * const wasmMemory = dcryptoMemory.decryptSymmetricKeyMemory(messageLen, additionalLen); + * const wasmModule = await dcryptoMethodsModule({ wasmMemory }); + * ``` + * + * If not all boxes and additional data are equal, you can always just use + * the largest Uint8Arrays as inputs. + * + * ```ts + * import dcrypto from \"@deliberative/crypto\" + * + * const message = new Uint8Array(128).fill(1); + * const symmetricKey = new Uint8Array(32).fill(3); + * const additionalData = new Uint8Array(64).fill(2); + * + * const box = await dcrypto.encryptSymmetricKey( + * message, + * symmetricKey, + * additionalData + * ); + * const decrypted = await dcrypto.decryptSymmetricKey( + * box, + * symmetricKey, + * additionalData + * ); + * + * \/\/ message should be equal to decrypted. + * ``` + * + * @param encrypted - the encrypted box including nonce and auth tag + * @param symmetricKey - the precomputed symmetric key + * @param additionalData - the additional data for aead + * @param module - wasm module in case of bulk decryptions + * + * @returns The decrypted message + */ +const decryptSymmetricKey = async ( + encrypted: Uint8Array, + symmetricKey: Uint8Array, + additionalData: Uint8Array, + module?: DCryptoMethodsModule, +): Promise => { + const len = encrypted.length; + const additionalLen = additionalData.length; + + const wasmMemory = module + ? module.wasmMemory + : libsodiumMemory.decryptSymmetricKeyMemory(len, additionalLen); + + const dcryptoModule = module || (await dcryptoMethodsModule({ wasmMemory })); + + const decryptedLen = getDecryptedLen(len); + + const ptr1 = dcryptoModule._malloc(len * Uint8Array.BYTES_PER_ELEMENT); + const encryptedArray = new Uint8Array( + dcryptoModule.HEAP8.buffer, + ptr1, + len * Uint8Array.BYTES_PER_ELEMENT, + ); + encryptedArray.set(encrypted); + + const ptr2 = dcryptoModule._malloc(crypto_kx_SESSIONKEYBYTES); + const k = new Uint8Array( + dcryptoModule.HEAP8.buffer, + ptr2, + crypto_kx_SESSIONKEYBYTES, + ); + k.set(symmetricKey); + + const ptr3 = dcryptoModule._malloc( + additionalLen * Uint8Array.BYTES_PER_ELEMENT, + ); + const additional = new Uint8Array( + dcryptoModule.HEAP8.buffer, + ptr3, + additionalLen * Uint8Array.BYTES_PER_ELEMENT, + ); + additional.set(additionalData); + + const ptr4 = dcryptoModule._malloc( + decryptedLen * Uint8Array.BYTES_PER_ELEMENT, + ); + const decrypted = new Uint8Array( + dcryptoModule.HEAP8.buffer, + ptr4, + decryptedLen * Uint8Array.BYTES_PER_ELEMENT, + ); + + const result = dcryptoModule._decrypt_data( + len, + encryptedArray.byteOffset, + k.byteOffset, + additionalLen, + additional.byteOffset, + decrypted.byteOffset, + ); + + const decr = new Uint8Array(decrypted); + + dcryptoModule._free(ptr1); + dcryptoModule._free(ptr2); + dcryptoModule._free(ptr3); + dcryptoModule._free(ptr4); + + switch (result) { + case 0: + return decr; + case -1: + throw new Error("Unsuccessful decryption attempt"); + default: + throw new Error("Unexpected error occured"); + } +}; + +export default decryptSymmetricKey; diff --git a/src/symmetric/encrypt.ts b/src/symmetric/encrypt.ts index 6413386..8e603c2 100644 --- a/src/symmetric/encrypt.ts +++ b/src/symmetric/encrypt.ts @@ -20,13 +20,69 @@ import dcryptoMethodsModule from "../c/build/dcryptoMethodsModule"; import type { DCryptoMethodsModule } from "../c/build/dcryptoMethodsModule"; import { - crypto_kx_SESSIONKEYBYTES, - getEncryptedLen, + crypto_sign_ed25519_PUBLICKEYBYTES, + crypto_sign_ed25519_SECRETKEYBYTES, + getE2EEncryptedSecretBoxEncryptedLen, } from "../utils/interfaces"; +/** + * Function that encrypts a message with additional data using + * the crypto_aead_chacha20poly1305_ietf_encrypt operation from + * libsodium and computes a symmetric key Uint8Array(32) from the sender's + * Ed25519 secret key and the receiver's Ed25519 public key. + * The X25519 key counterparts are computed in wasm from the libsodium provided + * crypto_sign_ed25519_pk_to_curve25519 and crypto_sign_ed25519_sk_to_curve25519 + * functions. + * The symmetric key for encryption is then computed by crypto_kx_server_session_keys. + * The nonce is calculated by taking the first half of the + * sha512 hash of a Uint8Array(3 * 32) array with 32 random bytes, the X25519 public key + * and the X25519 secret key. + * The auth tag is generated using Poly1305. + * + * If you need to perform bulk encryptions with predictable message + * and additional data sizes then it will be more efficient to preload + * the wasm module and reuse it as follows: + * + * ```ts + * const messageLen = message.length; + * const additionalLen = additionalData.length; + * + * const wasmMemory = dcryptoMemory.encryptMemory(messageLen, additionalLen); + * const wasmModule = await dcryptoMethodsModule({ wasmMemory }); + * ``` + * + * If not all messages and additional data are equal, you can always just use + * the largest Uint8Arrays as inputs. + * + * ```ts + * import dcrypto from \"@deliberative/crypto\" + * + * const message = new Uint8Array(128).fill(1); + * const additionalData = new Uint8Array(64).fill(2); + * + * const aliceKeyPair = await dcrypto.keyPair(); + * const bobKeyPair = await dcrypto.keyPair(); + * + * const box = await dcrypto.encrypt( + * message, + * bobKeyPair.publicKey, + * aliceKeyPair.secretKey, + * additionalData + * ); + * ``` + * + * @param message - the message to encrypt + * @param receiverPublicKey - the receiver's Ed25519 public key + * @param senderSecretKey - the sender's Ed25519 secret key + * @param additionalData - the additional data for aead + * @param module - wasm module in case of bulk encryptions + * + * @returns Encrypted box [nonce 16 || encrypted_data || auth tag 12] + */ const encrypt = async ( message: Uint8Array, - key: Uint8Array, + receiverPublicKey: Uint8Array, + senderSecretKey: Uint8Array, additionalData: Uint8Array, module?: DCryptoMethodsModule, ): Promise => { @@ -47,39 +103,48 @@ const encrypt = async ( ); dataArray.set(message); - const ptr2 = dcryptoModule._malloc(crypto_kx_SESSIONKEYBYTES); - const k = new Uint8Array( + const ptr2 = dcryptoModule._malloc(crypto_sign_ed25519_PUBLICKEYBYTES); + const pk = new Uint8Array( dcryptoModule.HEAP8.buffer, ptr2, - crypto_kx_SESSIONKEYBYTES, + crypto_sign_ed25519_PUBLICKEYBYTES, ); - k.set(key); + pk.set(receiverPublicKey); - const ptr3 = dcryptoModule._malloc( + const ptr3 = dcryptoModule._malloc(crypto_sign_ed25519_SECRETKEYBYTES); + const sk = new Uint8Array( + dcryptoModule.HEAP8.buffer, + ptr3, + crypto_sign_ed25519_SECRETKEYBYTES, + ); + sk.set(senderSecretKey); + + const ptr4 = dcryptoModule._malloc( additionalLen * Uint8Array.BYTES_PER_ELEMENT, ); const additional = new Uint8Array( dcryptoModule.HEAP8.buffer, - ptr3, + ptr4, additionalLen * Uint8Array.BYTES_PER_ELEMENT, ); additional.set(additionalData); - const sealedBoxLen = getEncryptedLen(len); + const sealedBoxLen = getE2EEncryptedSecretBoxEncryptedLen(len); - const ptr4 = dcryptoModule._malloc( + const ptr5 = dcryptoModule._malloc( sealedBoxLen * Uint8Array.BYTES_PER_ELEMENT, ); const encrypted = new Uint8Array( dcryptoModule.HEAP8.buffer, - ptr4, + ptr5, sealedBoxLen * Uint8Array.BYTES_PER_ELEMENT, ); - const result = dcryptoModule._encrypt_data( + const result = dcryptoModule._e2e_encrypt_data( len, dataArray.byteOffset, - k.byteOffset, + pk.byteOffset, + sk.byteOffset, additionalLen, additional.byteOffset, encrypted.byteOffset, diff --git a/src/symmetric/encryptSymmetricKey.ts b/src/symmetric/encryptSymmetricKey.ts new file mode 100644 index 0000000..440fedd --- /dev/null +++ b/src/symmetric/encryptSymmetricKey.ts @@ -0,0 +1,149 @@ +// Copyright (C) 2022 Deliberative Technologies P.C. +// SPDX-License-Identifier: Apache-2.0 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import dcryptoMemory from "./memory"; + +import dcryptoMethodsModule from "../c/build/dcryptoMethodsModule"; + +import type { DCryptoMethodsModule } from "../c/build/dcryptoMethodsModule"; + +import { + crypto_kx_SESSIONKEYBYTES, + getEncryptedLen, +} from "../utils/interfaces"; + +/** + * Function that encrypts a message with additional data using + * the crypto_aead_chacha20poly1305_ietf_encrypt operation from + * libsodium with a precomputed symmetric key Uint8Array(32). + * The nonce is calculated by taking the second half of the + * sha512 hash of a Uint8Array(64) random array that is produced + * in secure memory on wasm. The auth tag is generated using Poly1305. + * + * If you need to perform bulk encryptions with predictable message + * and additional data sizes then it will be more efficient to preload + * the wasm module and reuse it as follows: + * + * ```ts + * const messageLen = message.length; + * const additionalLen = additionalData.length; + * + * const wasmMemory = dcryptoMemory.encryptSymmetricKeyMemory(messageLen, additionalLen); + * const wasmModule = await dcryptoMethodsModule({ wasmMemory }); + * ``` + * + * If not all messages and additional data are equal, you can always just use + * the largest Uint8Arrays as inputs. + * + * ```ts + * import dcrypto from \"@deliberative/crypto\" + * + * const message = new Uint8Array(128).fill(1); + * const symmetricKey = new Uint8Array(32).fill(3); + * const additionalData = new Uint8Array(64).fill(2); + * + * const box = await dcrypto.encryptSymmetricKey( + * message, + * symmetricKey, + * additionalData + * ); + * ``` + * + * @param message - the message to encrypt + * @param symmetricKey - the precomputed symmetric key + * @param additionalData - the additional data for aead + * @param module - wasm module in case of bulk encryptions + * + * @returns Encrypted box [nonce 16 || encrypted_data || auth tag 12] + */ +const encryptSymmetricKey = async ( + message: Uint8Array, + symmetricKey: Uint8Array, + additionalData: Uint8Array, + module?: DCryptoMethodsModule, +): Promise => { + const len = message.length; + const additionalLen = additionalData.length; + + const wasmMemory = module + ? module.wasmMemory + : dcryptoMemory.encryptSymmetricKeyMemory(len, additionalLen); + + const dcryptoModule = module || (await dcryptoMethodsModule({ wasmMemory })); + + const ptr1 = dcryptoModule._malloc(len * Uint8Array.BYTES_PER_ELEMENT); + const dataArray = new Uint8Array( + dcryptoModule.HEAP8.buffer, + ptr1, + len * Uint8Array.BYTES_PER_ELEMENT, + ); + dataArray.set(message); + + const ptr2 = dcryptoModule._malloc(crypto_kx_SESSIONKEYBYTES); + const k = new Uint8Array( + dcryptoModule.HEAP8.buffer, + ptr2, + crypto_kx_SESSIONKEYBYTES, + ); + k.set(symmetricKey); + + const ptr3 = dcryptoModule._malloc( + additionalLen * Uint8Array.BYTES_PER_ELEMENT, + ); + const additional = new Uint8Array( + dcryptoModule.HEAP8.buffer, + ptr3, + additionalLen * Uint8Array.BYTES_PER_ELEMENT, + ); + additional.set(additionalData); + + const sealedBoxLen = getEncryptedLen(len); + + const ptr4 = dcryptoModule._malloc( + sealedBoxLen * Uint8Array.BYTES_PER_ELEMENT, + ); + const encrypted = new Uint8Array( + dcryptoModule.HEAP8.buffer, + ptr4, + sealedBoxLen * Uint8Array.BYTES_PER_ELEMENT, + ); + + const result = dcryptoModule._encrypt_data( + len, + dataArray.byteOffset, + k.byteOffset, + additionalLen, + additional.byteOffset, + encrypted.byteOffset, + ); + + const enc = new Uint8Array(encrypted); + + dcryptoModule._free(ptr1); + dcryptoModule._free(ptr2); + dcryptoModule._free(ptr3); + dcryptoModule._free(ptr4); + + switch (result) { + case 0: { + return enc; + } + + default: + throw new Error("An unexpected error occured."); + } +}; + +export default encryptSymmetricKey; diff --git a/src/symmetric/index.ts b/src/symmetric/index.ts index e9b11f7..413b1ca 100644 --- a/src/symmetric/index.ts +++ b/src/symmetric/index.ts @@ -15,10 +15,14 @@ import encrypt from "./encrypt"; import decrypt from "./decrypt"; +import encryptSymmetricKey from "./encryptSymmetricKey"; +import decryptSymmetricKey from "./decryptSymmetricKey"; import memory from "./memory"; export default { encrypt, decrypt, + encryptSymmetricKey, + decryptSymmetricKey, memory, }; diff --git a/src/symmetric/memory.ts b/src/symmetric/memory.ts index bc7b2d7..3106eec 100644 --- a/src/symmetric/memory.ts +++ b/src/symmetric/memory.ts @@ -19,14 +19,59 @@ import { crypto_hash_sha512_BYTES, crypto_kx_SESSIONKEYBYTES, crypto_box_x25519_NONCEBYTES, + crypto_box_x25519_PUBLICKEYBYTES, + crypto_box_x25519_SECRETKEYBYTES, crypto_box_poly1305_AUTHTAGBYTES, + crypto_sign_ed25519_PUBLICKEYBYTES, + crypto_sign_ed25519_SECRETKEYBYTES, getEncryptedLen, getDecryptedLen, + getE2EEncryptedSecretBoxEncryptedLen, + getE2EEncryptedSecretBoxDecryptedLen, } from "../utils/interfaces"; const encryptMemory = ( messageLen: number, additionalDataLen: number, +): WebAssembly.Memory => { + const sealedBoxLen = getE2EEncryptedSecretBoxEncryptedLen(messageLen); + const memoryLen = + (messageLen + + crypto_sign_ed25519_PUBLICKEYBYTES + + additionalDataLen + + sealedBoxLen + + 1 * (messageLen + crypto_box_poly1305_AUTHTAGBYTES) + // malloc'd + 2 * crypto_box_x25519_PUBLICKEYBYTES + // malloc'd + 2 * crypto_box_x25519_SECRETKEYBYTES + // malloc'd + crypto_box_x25519_NONCEBYTES) * // malloc'd + Uint8Array.BYTES_PER_ELEMENT; + const pages = memoryLenToPages(memoryLen); + + return new WebAssembly.Memory({ initial: pages, maximum: pages }); +}; + +const decryptMemory = ( + encryptedLen: number, + additionalDataLen: number, +): WebAssembly.Memory => { + const decryptedLen = getE2EEncryptedSecretBoxDecryptedLen(encryptedLen); + const memoryLen = + (encryptedLen + + crypto_sign_ed25519_SECRETKEYBYTES + + additionalDataLen + + decryptedLen + + 2 * crypto_box_x25519_PUBLICKEYBYTES + // malloc'd + crypto_box_x25519_NONCEBYTES + // malloc'd + crypto_box_x25519_SECRETKEYBYTES) * // malloc'd + Uint8Array.BYTES_PER_ELEMENT; + const pages = memoryLenToPages(memoryLen); + + return new WebAssembly.Memory({ initial: pages, maximum: pages }); +}; + +const encryptSymmetricKeyMemory = ( + messageLen: number, + additionalDataLen: number, ): WebAssembly.Memory => { const sealedBoxLen = getEncryptedLen(messageLen); const memoryLen = @@ -43,7 +88,7 @@ const encryptMemory = ( return new WebAssembly.Memory({ initial: pages, maximum: pages }); }; -const decryptMemory = ( +const decryptSymmetricKeyMemory = ( encryptedLen: number, additionalDataLen: number, ): WebAssembly.Memory => { @@ -64,4 +109,6 @@ const decryptMemory = ( export default { encryptMemory, decryptMemory, + encryptSymmetricKeyMemory, + decryptSymmetricKeyMemory, }; diff --git a/src/utils/interfaces.ts b/src/utils/interfaces.ts index f8374dd..e080af0 100644 --- a/src/utils/interfaces.ts +++ b/src/utils/interfaces.ts @@ -83,6 +83,20 @@ export const getForwardSecretBoxDecryptedLen = (encryptedLen: number) => { ); }; +export const getE2EEncryptedSecretBoxEncryptedLen = (dataLen: number) => { + return ( + crypto_box_x25519_NONCEBYTES + dataLen + crypto_box_poly1305_AUTHTAGBYTES + ); +}; + +export const getE2EEncryptedSecretBoxDecryptedLen = (encryptedLen: number) => { + return ( + encryptedLen - + crypto_box_x25519_NONCEBYTES - // nonce + crypto_box_poly1305_AUTHTAGBYTES // authTag + ); +}; + export default { crypto_hash_sha512_BYTES, crypto_secretbox_KEYBYTES, @@ -101,4 +115,6 @@ export default { getDecryptedLen, getForwardSecretBoxEncryptedLen, getForwardSecretBoxDecryptedLen, + getE2EEncryptedSecretBoxEncryptedLen, + getE2EEncryptedSecretBoxDecryptedLen, };