@toruslabs/eccrypto
Advanced tools
@@ -237,3 +237,3 @@ 'use strict'; | ||
| const decrypt = async function (privateKey, opts, _padding) { | ||
| const padding = _padding !== null && _padding !== void 0 ? _padding : false; | ||
| const padding = _padding !== null && _padding !== undefined ? _padding : false; | ||
| const deriveLocal = padding ? derivePadded : deriveUnpadded; | ||
@@ -240,0 +240,0 @@ const Px = await deriveLocal(privateKey, opts.ephemPublicKey); |
@@ -235,3 +235,3 @@ import { ec as ec$1 } from 'elliptic'; | ||
| const decrypt = async function (privateKey, opts, _padding) { | ||
| const padding = _padding !== null && _padding !== void 0 ? _padding : false; | ||
| const padding = _padding !== null && _padding !== undefined ? _padding : false; | ||
| const deriveLocal = padding ? derivePadded : deriveUnpadded; | ||
@@ -238,0 +238,0 @@ const Px = await deriveLocal(privateKey, opts.ephemPublicKey); |
+16
-18
| { | ||
| "name": "@toruslabs/eccrypto", | ||
| "version": "6.0.0", | ||
| "version": "6.0.1", | ||
| "description": "JavaScript Elliptic curve cryptography library, includes fix to browser.js so that encrypt/decrypt works", | ||
@@ -15,6 +15,3 @@ "main": "./dist/lib.cjs/index.js", | ||
| "test:node": "vitest run --config test/configs/node.config.mts --coverage", | ||
| "test:chrome": "vitest run --config test/configs/chrome.config.mts --coverage", | ||
| "test:firefox": "vitest run --config test/configs/firefox.config.mts --coverage", | ||
| "test:safari": "vitest run --config test/configs/safari.config.mts --coverage", | ||
| "test:browsers": "npm run test:chrome && npm run test:firefox && npm run test:safari" | ||
| "test:browsers": "vitest run --config test/configs/browsers.config.mts --coverage" | ||
| }, | ||
@@ -47,18 +44,19 @@ "repository": { | ||
| "devDependencies": { | ||
| "@babel/runtime": "^7.26.0", | ||
| "@toruslabs/config": "^3.0.0", | ||
| "@toruslabs/eslint-config-node": "^4.0.2", | ||
| "@toruslabs/eslint-config-typescript": "^4.0.2", | ||
| "@toruslabs/torus-scripts": "^7.0.2", | ||
| "@types/buffer-equal": "^1.0.2", | ||
| "@babel/runtime": "^7.26.9", | ||
| "@toruslabs/config": "^3.0.1", | ||
| "@toruslabs/eslint-config-node": "^4.0.4", | ||
| "@toruslabs/eslint-config-typescript": "^4.0.4", | ||
| "@toruslabs/torus-scripts": "^7.0.4", | ||
| "@types/elliptic": "^6.4.18", | ||
| "@vitest/browser": "^2.1.8", | ||
| "@vitest/coverage-istanbul": "^2.1.8", | ||
| "@vitest/browser": "^3.0.7", | ||
| "@vitest/coverage-istanbul": "^3.0.7", | ||
| "browserify": "^17.0.1", | ||
| "buffer-equal": "^1.0.1", | ||
| "eslint": "^9.17.0", | ||
| "playwright": "^1.49.1", | ||
| "typescript": "^5.7.2", | ||
| "vitest": "^2.1.8" | ||
| "eslint": "^9.21.0", | ||
| "playwright": "^1.50.1", | ||
| "typescript": "^5.7.3", | ||
| "vitest": "^3.0.7" | ||
| }, | ||
| "overrides": { | ||
| "esbuild": "^0.25.0" | ||
| }, | ||
| "engines": { | ||
@@ -65,0 +63,0 @@ "node": ">=20.x", |
| /******/ (() => { // webpackBootstrap | ||
| /******/ "use strict"; | ||
| /******/ // The require scope | ||
| /******/ var __webpack_require__ = {}; | ||
| /******/ | ||
| /************************************************************************/ | ||
| /******/ /* webpack/runtime/define property getters */ | ||
| /******/ (() => { | ||
| /******/ // define getter functions for harmony exports | ||
| /******/ __webpack_require__.d = (exports, definition) => { | ||
| /******/ for(var key in definition) { | ||
| /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { | ||
| /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); | ||
| /******/ } | ||
| /******/ } | ||
| /******/ }; | ||
| /******/ })(); | ||
| /******/ | ||
| /******/ /* webpack/runtime/hasOwnProperty shorthand */ | ||
| /******/ (() => { | ||
| /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) | ||
| /******/ })(); | ||
| /******/ | ||
| /******/ /* webpack/runtime/make namespace object */ | ||
| /******/ (() => { | ||
| /******/ // define __esModule on exports | ||
| /******/ __webpack_require__.r = (exports) => { | ||
| /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { | ||
| /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); | ||
| /******/ } | ||
| /******/ Object.defineProperty(exports, '__esModule', { value: true }); | ||
| /******/ }; | ||
| /******/ })(); | ||
| /******/ | ||
| /************************************************************************/ | ||
| var __webpack_exports__ = {}; | ||
| // ESM COMPAT FLAG | ||
| __webpack_require__.r(__webpack_exports__); | ||
| // EXPORTS | ||
| __webpack_require__.d(__webpack_exports__, { | ||
| decrypt: () => (/* binding */ decrypt), | ||
| derive: () => (/* binding */ derive), | ||
| derivePadded: () => (/* binding */ derivePadded), | ||
| deriveUnpadded: () => (/* binding */ deriveUnpadded), | ||
| encrypt: () => (/* binding */ encrypt), | ||
| generatePrivate: () => (/* binding */ generatePrivate), | ||
| getPublic: () => (/* binding */ getPublic), | ||
| getPublicCompressed: () => (/* binding */ getPublicCompressed), | ||
| sign: () => (/* binding */ sign), | ||
| verify: () => (/* binding */ verify) | ||
| }); | ||
| ;// external "elliptic" | ||
| const external_elliptic_namespaceObject = require("elliptic"); | ||
| ;// ./src/index.ts | ||
| const ec = new external_elliptic_namespaceObject.ec("secp256k1"); | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any, n/no-unsupported-features/node-builtins | ||
| const browserCrypto = globalThis.crypto || globalThis.msCrypto || {}; | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const subtle = browserCrypto.subtle || browserCrypto.webkitSubtle; | ||
| const EC_GROUP_ORDER = Buffer.from("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", "hex"); | ||
| const ZERO32 = Buffer.alloc(32, 0); | ||
| function assert(condition, message) { | ||
| if (!condition) { | ||
| throw new Error(message || "Assertion failed"); | ||
| } | ||
| } | ||
| function isScalar(x) { | ||
| return Buffer.isBuffer(x) && x.length === 32; | ||
| } | ||
| function isValidPrivateKey(privateKey) { | ||
| if (!isScalar(privateKey)) { | ||
| return false; | ||
| } | ||
| return privateKey.compare(ZERO32) > 0 && | ||
| // > 0 | ||
| privateKey.compare(EC_GROUP_ORDER) < 0; // < G | ||
| } | ||
| // Compare two buffers in constant time to prevent timing attacks. | ||
| function equalConstTime(b1, b2) { | ||
| if (b1.length !== b2.length) { | ||
| return false; | ||
| } | ||
| let res = 0; | ||
| for (let i = 0; i < b1.length; i++) { | ||
| res |= b1[i] ^ b2[i]; // jshint ignore:line | ||
| } | ||
| return res === 0; | ||
| } | ||
| /* This must check if we're in the browser or | ||
| not, since the functions are different and does | ||
| not convert using browserify */ | ||
| function randomBytes(size) { | ||
| if (typeof browserCrypto.getRandomValues === "undefined") { | ||
| return Buffer.from(browserCrypto.randomBytes(size)); | ||
| } | ||
| const arr = new Uint8Array(size); | ||
| browserCrypto.getRandomValues(arr); | ||
| return Buffer.from(arr); | ||
| } | ||
| async function sha512(msg) { | ||
| if (!browserCrypto.createHash) { | ||
| const hash = await subtle.digest("SHA-512", msg); | ||
| const result = new Uint8Array(hash); | ||
| return result; | ||
| } | ||
| const hash = browserCrypto.createHash("sha512"); | ||
| const result = hash.update(msg).digest(); | ||
| return new Uint8Array(result); | ||
| } | ||
| function getAes(op) { | ||
| return async function (iv, key, data) { | ||
| if (subtle && subtle[op] && subtle.importKey) { | ||
| const importAlgorithm = { | ||
| name: "AES-CBC" | ||
| }; | ||
| const cryptoKey = await subtle.importKey("raw", key, importAlgorithm, false, [op]); | ||
| const encAlgorithm = { | ||
| name: "AES-CBC", | ||
| iv | ||
| }; | ||
| // encrypt and decrypt ops are not implemented in react-native-quick-crypto yet. | ||
| const result = await subtle[op](encAlgorithm, cryptoKey, data); | ||
| return Buffer.from(new Uint8Array(result)); | ||
| } else if (op === "encrypt" && browserCrypto.createCipheriv) { | ||
| // This is available if crypto is polyfilled in react native environment | ||
| const cipher = browserCrypto.createCipheriv("aes-256-cbc", key, iv); | ||
| const firstChunk = cipher.update(data); | ||
| const secondChunk = cipher.final(); | ||
| return Buffer.concat([firstChunk, secondChunk]); | ||
| } else if (op === "decrypt" && browserCrypto.createDecipheriv) { | ||
| const decipher = browserCrypto.createDecipheriv("aes-256-cbc", key, iv); | ||
| const firstChunk = decipher.update(data); | ||
| const secondChunk = decipher.final(); | ||
| return Buffer.concat([firstChunk, secondChunk]); | ||
| } | ||
| throw new Error(`Unsupported operation: ${op}`); | ||
| }; | ||
| } | ||
| const aesCbcEncrypt = getAes("encrypt"); | ||
| const aesCbcDecrypt = getAes("decrypt"); | ||
| async function hmacSha256Sign(key, msg) { | ||
| if (!browserCrypto.createHmac) { | ||
| const importAlgorithm = { | ||
| name: "HMAC", | ||
| hash: { | ||
| name: "SHA-256" | ||
| } | ||
| }; | ||
| const cryptoKey = await subtle.importKey("raw", new Uint8Array(key), importAlgorithm, false, ["sign", "verify"]); | ||
| const sig = await subtle.sign("HMAC", cryptoKey, msg); | ||
| const result = Buffer.from(new Uint8Array(sig)); | ||
| return result; | ||
| } | ||
| const hmac = browserCrypto.createHmac("sha256", Buffer.from(key)); | ||
| hmac.update(msg); | ||
| const result = hmac.digest(); | ||
| return result; | ||
| } | ||
| async function hmacSha256Verify(key, msg, sig) { | ||
| const expectedSig = await hmacSha256Sign(key, msg); | ||
| return equalConstTime(expectedSig, sig); | ||
| } | ||
| /** | ||
| * Generate a new valid private key. Will use the window.crypto or window.msCrypto as source | ||
| * depending on your browser. | ||
| */ | ||
| const generatePrivate = function () { | ||
| let privateKey = randomBytes(32); | ||
| while (!isValidPrivateKey(privateKey)) { | ||
| privateKey = randomBytes(32); | ||
| } | ||
| return privateKey; | ||
| }; | ||
| const getPublic = function (privateKey) { | ||
| // This function has sync API so we throw an error immediately. | ||
| assert(privateKey.length === 32, "Bad private key"); | ||
| assert(isValidPrivateKey(privateKey), "Bad private key"); | ||
| // XXX(Kagami): `elliptic.utils.encode` returns array for every | ||
| // encoding except `hex`. | ||
| return Buffer.from(ec.keyFromPrivate(privateKey).getPublic("array")); | ||
| }; | ||
| /** | ||
| * Get compressed version of public key. | ||
| */ | ||
| const getPublicCompressed = function (privateKey) { | ||
| // jshint ignore:line | ||
| assert(privateKey.length === 32, "Bad private key"); | ||
| assert(isValidPrivateKey(privateKey), "Bad private key"); | ||
| // See https://github.com/wanderer/secp256k1-node/issues/46 | ||
| const compressed = true; | ||
| return Buffer.from(ec.keyFromPrivate(privateKey).getPublic(compressed, "array")); | ||
| }; | ||
| // NOTE(Kagami): We don't use promise shim in Browser implementation | ||
| // because it's supported natively in new browsers (see | ||
| // <http://caniuse.com/#feat=promises>) and we can use only new browsers | ||
| // because of the WebCryptoAPI (see | ||
| // <http://caniuse.com/#feat=cryptography>). | ||
| const sign = async function (privateKey, msg) { | ||
| assert(privateKey.length === 32, "Bad private key"); | ||
| assert(isValidPrivateKey(privateKey), "Bad private key"); | ||
| assert(msg.length > 0, "Message should not be empty"); | ||
| assert(msg.length <= 32, "Message is too long"); | ||
| return Buffer.from(ec.sign(msg, privateKey, { | ||
| canonical: true | ||
| }).toDER()); | ||
| }; | ||
| const verify = async function (publicKey, msg, sig) { | ||
| assert(publicKey.length === 65 || publicKey.length === 33, "Bad public key"); | ||
| if (publicKey.length === 65) { | ||
| assert(publicKey[0] === 4, "Bad public key"); | ||
| } | ||
| if (publicKey.length === 33) { | ||
| assert(publicKey[0] === 2 || publicKey[0] === 3, "Bad public key"); | ||
| } | ||
| assert(msg.length > 0, "Message should not be empty"); | ||
| assert(msg.length <= 32, "Message is too long"); | ||
| if (ec.verify(msg, sig, publicKey)) { | ||
| return null; | ||
| } | ||
| throw new Error("Bad signature"); | ||
| }; | ||
| const derive = async function (privateKeyA, publicKeyB) { | ||
| assert(Buffer.isBuffer(privateKeyA), "Bad private key"); | ||
| assert(Buffer.isBuffer(publicKeyB), "Bad public key"); | ||
| assert(privateKeyA.length === 32, "Bad private key"); | ||
| assert(isValidPrivateKey(privateKeyA), "Bad private key"); | ||
| assert(publicKeyB.length === 65 || publicKeyB.length === 33, "Bad public key"); | ||
| if (publicKeyB.length === 65) { | ||
| assert(publicKeyB[0] === 4, "Bad public key"); | ||
| } | ||
| if (publicKeyB.length === 33) { | ||
| assert(publicKeyB[0] === 2 || publicKeyB[0] === 3, "Bad public key"); | ||
| } | ||
| const keyA = ec.keyFromPrivate(privateKeyA); | ||
| const keyB = ec.keyFromPublic(publicKeyB); | ||
| const Px = keyA.derive(keyB.getPublic()); // BN instance | ||
| return Buffer.from(Px.toArray()); | ||
| }; | ||
| const deriveUnpadded = derive; | ||
| const derivePadded = async function (privateKeyA, publicKeyB) { | ||
| assert(Buffer.isBuffer(privateKeyA), "Bad private key"); | ||
| assert(Buffer.isBuffer(publicKeyB), "Bad public key"); | ||
| assert(privateKeyA.length === 32, "Bad private key"); | ||
| assert(isValidPrivateKey(privateKeyA), "Bad private key"); | ||
| assert(publicKeyB.length === 65 || publicKeyB.length === 33, "Bad public key"); | ||
| if (publicKeyB.length === 65) { | ||
| assert(publicKeyB[0] === 4, "Bad public key"); | ||
| } | ||
| if (publicKeyB.length === 33) { | ||
| assert(publicKeyB[0] === 2 || publicKeyB[0] === 3, "Bad public key"); | ||
| } | ||
| const keyA = ec.keyFromPrivate(privateKeyA); | ||
| const keyB = ec.keyFromPublic(publicKeyB); | ||
| const Px = keyA.derive(keyB.getPublic()); // BN instance | ||
| return Buffer.from(Px.toString(16, 64), "hex"); | ||
| }; | ||
| const encrypt = async function (publicKeyTo, msg, opts) { | ||
| opts = opts || {}; | ||
| let ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32); | ||
| // There is a very unlikely possibility that it is not a valid key | ||
| while (!isValidPrivateKey(ephemPrivateKey)) { | ||
| ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32); | ||
| } | ||
| const ephemPublicKey = getPublic(ephemPrivateKey); | ||
| const Px = await deriveUnpadded(ephemPrivateKey, publicKeyTo); | ||
| const hash = await sha512(Px); | ||
| const iv = opts.iv || randomBytes(16); | ||
| const encryptionKey = hash.slice(0, 32); | ||
| const macKey = hash.slice(32); | ||
| const data = await aesCbcEncrypt(iv, Buffer.from(encryptionKey), msg); | ||
| const ciphertext = data; | ||
| const dataToMac = Buffer.concat([iv, ephemPublicKey, ciphertext]); | ||
| const mac = await hmacSha256Sign(Buffer.from(macKey), dataToMac); | ||
| return { | ||
| iv, | ||
| ephemPublicKey, | ||
| ciphertext, | ||
| mac | ||
| }; | ||
| }; | ||
| const decrypt = async function (privateKey, opts, _padding) { | ||
| const padding = _padding !== null && _padding !== void 0 ? _padding : false; | ||
| const deriveLocal = padding ? derivePadded : deriveUnpadded; | ||
| const Px = await deriveLocal(privateKey, opts.ephemPublicKey); | ||
| const hash = await sha512(Px); | ||
| const encryptionKey = hash.slice(0, 32); | ||
| const macKey = hash.slice(32); | ||
| const dataToMac = Buffer.concat([opts.iv, opts.ephemPublicKey, opts.ciphertext]); | ||
| const macGood = await hmacSha256Verify(Buffer.from(macKey), dataToMac, opts.mac); | ||
| if (!macGood && padding === false) { | ||
| return decrypt(privateKey, opts, true); | ||
| } else if (!macGood && padding === true) { | ||
| throw new Error("bad MAC after trying padded"); | ||
| } | ||
| const msg = await aesCbcDecrypt(opts.iv, Buffer.from(encryptionKey), opts.ciphertext); | ||
| return Buffer.from(new Uint8Array(msg)); | ||
| }; | ||
| module.exports = __webpack_exports__; | ||
| /******/ })() | ||
| ; |
| import { ec as ec$1 } from 'elliptic'; | ||
| const ec = new ec$1("secp256k1"); | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any, n/no-unsupported-features/node-builtins | ||
| const browserCrypto = globalThis.crypto || globalThis.msCrypto || {}; | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| const subtle = browserCrypto.subtle || browserCrypto.webkitSubtle; | ||
| const EC_GROUP_ORDER = Buffer.from("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", "hex"); | ||
| const ZERO32 = Buffer.alloc(32, 0); | ||
| function assert(condition, message) { | ||
| if (!condition) { | ||
| throw new Error(message || "Assertion failed"); | ||
| } | ||
| } | ||
| function isScalar(x) { | ||
| return Buffer.isBuffer(x) && x.length === 32; | ||
| } | ||
| function isValidPrivateKey(privateKey) { | ||
| if (!isScalar(privateKey)) { | ||
| return false; | ||
| } | ||
| return privateKey.compare(ZERO32) > 0 && | ||
| // > 0 | ||
| privateKey.compare(EC_GROUP_ORDER) < 0; // < G | ||
| } | ||
| // Compare two buffers in constant time to prevent timing attacks. | ||
| function equalConstTime(b1, b2) { | ||
| if (b1.length !== b2.length) { | ||
| return false; | ||
| } | ||
| let res = 0; | ||
| for (let i = 0; i < b1.length; i++) { | ||
| res |= b1[i] ^ b2[i]; // jshint ignore:line | ||
| } | ||
| return res === 0; | ||
| } | ||
| /* This must check if we're in the browser or | ||
| not, since the functions are different and does | ||
| not convert using browserify */ | ||
| function randomBytes(size) { | ||
| if (typeof browserCrypto.getRandomValues === "undefined") { | ||
| return Buffer.from(browserCrypto.randomBytes(size)); | ||
| } | ||
| const arr = new Uint8Array(size); | ||
| browserCrypto.getRandomValues(arr); | ||
| return Buffer.from(arr); | ||
| } | ||
| async function sha512(msg) { | ||
| if (!browserCrypto.createHash) { | ||
| const hash = await subtle.digest("SHA-512", msg); | ||
| const result = new Uint8Array(hash); | ||
| return result; | ||
| } | ||
| const hash = browserCrypto.createHash("sha512"); | ||
| const result = hash.update(msg).digest(); | ||
| return new Uint8Array(result); | ||
| } | ||
| function getAes(op) { | ||
| return async function (iv, key, data) { | ||
| if (subtle && subtle[op] && subtle.importKey) { | ||
| const importAlgorithm = { | ||
| name: "AES-CBC" | ||
| }; | ||
| const cryptoKey = await subtle.importKey("raw", key, importAlgorithm, false, [op]); | ||
| const encAlgorithm = { | ||
| name: "AES-CBC", | ||
| iv | ||
| }; | ||
| // encrypt and decrypt ops are not implemented in react-native-quick-crypto yet. | ||
| const result = await subtle[op](encAlgorithm, cryptoKey, data); | ||
| return Buffer.from(new Uint8Array(result)); | ||
| } else if (op === "encrypt" && browserCrypto.createCipheriv) { | ||
| // This is available if crypto is polyfilled in react native environment | ||
| const cipher = browserCrypto.createCipheriv("aes-256-cbc", key, iv); | ||
| const firstChunk = cipher.update(data); | ||
| const secondChunk = cipher.final(); | ||
| return Buffer.concat([firstChunk, secondChunk]); | ||
| } else if (op === "decrypt" && browserCrypto.createDecipheriv) { | ||
| const decipher = browserCrypto.createDecipheriv("aes-256-cbc", key, iv); | ||
| const firstChunk = decipher.update(data); | ||
| const secondChunk = decipher.final(); | ||
| return Buffer.concat([firstChunk, secondChunk]); | ||
| } | ||
| throw new Error(`Unsupported operation: ${op}`); | ||
| }; | ||
| } | ||
| const aesCbcEncrypt = getAes("encrypt"); | ||
| const aesCbcDecrypt = getAes("decrypt"); | ||
| async function hmacSha256Sign(key, msg) { | ||
| if (!browserCrypto.createHmac) { | ||
| const importAlgorithm = { | ||
| name: "HMAC", | ||
| hash: { | ||
| name: "SHA-256" | ||
| } | ||
| }; | ||
| const cryptoKey = await subtle.importKey("raw", new Uint8Array(key), importAlgorithm, false, ["sign", "verify"]); | ||
| const sig = await subtle.sign("HMAC", cryptoKey, msg); | ||
| const result = Buffer.from(new Uint8Array(sig)); | ||
| return result; | ||
| } | ||
| const hmac = browserCrypto.createHmac("sha256", Buffer.from(key)); | ||
| hmac.update(msg); | ||
| const result = hmac.digest(); | ||
| return result; | ||
| } | ||
| async function hmacSha256Verify(key, msg, sig) { | ||
| const expectedSig = await hmacSha256Sign(key, msg); | ||
| return equalConstTime(expectedSig, sig); | ||
| } | ||
| /** | ||
| * Generate a new valid private key. Will use the window.crypto or window.msCrypto as source | ||
| * depending on your browser. | ||
| */ | ||
| const generatePrivate = function () { | ||
| let privateKey = randomBytes(32); | ||
| while (!isValidPrivateKey(privateKey)) { | ||
| privateKey = randomBytes(32); | ||
| } | ||
| return privateKey; | ||
| }; | ||
| const getPublic = function (privateKey) { | ||
| // This function has sync API so we throw an error immediately. | ||
| assert(privateKey.length === 32, "Bad private key"); | ||
| assert(isValidPrivateKey(privateKey), "Bad private key"); | ||
| // XXX(Kagami): `elliptic.utils.encode` returns array for every | ||
| // encoding except `hex`. | ||
| return Buffer.from(ec.keyFromPrivate(privateKey).getPublic("array")); | ||
| }; | ||
| /** | ||
| * Get compressed version of public key. | ||
| */ | ||
| const getPublicCompressed = function (privateKey) { | ||
| // jshint ignore:line | ||
| assert(privateKey.length === 32, "Bad private key"); | ||
| assert(isValidPrivateKey(privateKey), "Bad private key"); | ||
| // See https://github.com/wanderer/secp256k1-node/issues/46 | ||
| const compressed = true; | ||
| return Buffer.from(ec.keyFromPrivate(privateKey).getPublic(compressed, "array")); | ||
| }; | ||
| // NOTE(Kagami): We don't use promise shim in Browser implementation | ||
| // because it's supported natively in new browsers (see | ||
| // <http://caniuse.com/#feat=promises>) and we can use only new browsers | ||
| // because of the WebCryptoAPI (see | ||
| // <http://caniuse.com/#feat=cryptography>). | ||
| const sign = async function (privateKey, msg) { | ||
| assert(privateKey.length === 32, "Bad private key"); | ||
| assert(isValidPrivateKey(privateKey), "Bad private key"); | ||
| assert(msg.length > 0, "Message should not be empty"); | ||
| assert(msg.length <= 32, "Message is too long"); | ||
| return Buffer.from(ec.sign(msg, privateKey, { | ||
| canonical: true | ||
| }).toDER()); | ||
| }; | ||
| const verify = async function (publicKey, msg, sig) { | ||
| assert(publicKey.length === 65 || publicKey.length === 33, "Bad public key"); | ||
| if (publicKey.length === 65) { | ||
| assert(publicKey[0] === 4, "Bad public key"); | ||
| } | ||
| if (publicKey.length === 33) { | ||
| assert(publicKey[0] === 2 || publicKey[0] === 3, "Bad public key"); | ||
| } | ||
| assert(msg.length > 0, "Message should not be empty"); | ||
| assert(msg.length <= 32, "Message is too long"); | ||
| if (ec.verify(msg, sig, publicKey)) { | ||
| return null; | ||
| } | ||
| throw new Error("Bad signature"); | ||
| }; | ||
| const derive = async function (privateKeyA, publicKeyB) { | ||
| assert(Buffer.isBuffer(privateKeyA), "Bad private key"); | ||
| assert(Buffer.isBuffer(publicKeyB), "Bad public key"); | ||
| assert(privateKeyA.length === 32, "Bad private key"); | ||
| assert(isValidPrivateKey(privateKeyA), "Bad private key"); | ||
| assert(publicKeyB.length === 65 || publicKeyB.length === 33, "Bad public key"); | ||
| if (publicKeyB.length === 65) { | ||
| assert(publicKeyB[0] === 4, "Bad public key"); | ||
| } | ||
| if (publicKeyB.length === 33) { | ||
| assert(publicKeyB[0] === 2 || publicKeyB[0] === 3, "Bad public key"); | ||
| } | ||
| const keyA = ec.keyFromPrivate(privateKeyA); | ||
| const keyB = ec.keyFromPublic(publicKeyB); | ||
| const Px = keyA.derive(keyB.getPublic()); // BN instance | ||
| return Buffer.from(Px.toArray()); | ||
| }; | ||
| const deriveUnpadded = derive; | ||
| const derivePadded = async function (privateKeyA, publicKeyB) { | ||
| assert(Buffer.isBuffer(privateKeyA), "Bad private key"); | ||
| assert(Buffer.isBuffer(publicKeyB), "Bad public key"); | ||
| assert(privateKeyA.length === 32, "Bad private key"); | ||
| assert(isValidPrivateKey(privateKeyA), "Bad private key"); | ||
| assert(publicKeyB.length === 65 || publicKeyB.length === 33, "Bad public key"); | ||
| if (publicKeyB.length === 65) { | ||
| assert(publicKeyB[0] === 4, "Bad public key"); | ||
| } | ||
| if (publicKeyB.length === 33) { | ||
| assert(publicKeyB[0] === 2 || publicKeyB[0] === 3, "Bad public key"); | ||
| } | ||
| const keyA = ec.keyFromPrivate(privateKeyA); | ||
| const keyB = ec.keyFromPublic(publicKeyB); | ||
| const Px = keyA.derive(keyB.getPublic()); // BN instance | ||
| return Buffer.from(Px.toString(16, 64), "hex"); | ||
| }; | ||
| const encrypt = async function (publicKeyTo, msg, opts) { | ||
| opts = opts || {}; | ||
| let ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32); | ||
| // There is a very unlikely possibility that it is not a valid key | ||
| while (!isValidPrivateKey(ephemPrivateKey)) { | ||
| ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32); | ||
| } | ||
| const ephemPublicKey = getPublic(ephemPrivateKey); | ||
| const Px = await deriveUnpadded(ephemPrivateKey, publicKeyTo); | ||
| const hash = await sha512(Px); | ||
| const iv = opts.iv || randomBytes(16); | ||
| const encryptionKey = hash.slice(0, 32); | ||
| const macKey = hash.slice(32); | ||
| const data = await aesCbcEncrypt(iv, Buffer.from(encryptionKey), msg); | ||
| const ciphertext = data; | ||
| const dataToMac = Buffer.concat([iv, ephemPublicKey, ciphertext]); | ||
| const mac = await hmacSha256Sign(Buffer.from(macKey), dataToMac); | ||
| return { | ||
| iv, | ||
| ephemPublicKey, | ||
| ciphertext, | ||
| mac | ||
| }; | ||
| }; | ||
| const decrypt = async function (privateKey, opts, _padding) { | ||
| const padding = _padding !== null && _padding !== void 0 ? _padding : false; | ||
| const deriveLocal = padding ? derivePadded : deriveUnpadded; | ||
| const Px = await deriveLocal(privateKey, opts.ephemPublicKey); | ||
| const hash = await sha512(Px); | ||
| const encryptionKey = hash.slice(0, 32); | ||
| const macKey = hash.slice(32); | ||
| const dataToMac = Buffer.concat([opts.iv, opts.ephemPublicKey, opts.ciphertext]); | ||
| const macGood = await hmacSha256Verify(Buffer.from(macKey), dataToMac, opts.mac); | ||
| if (!macGood && padding === false) { | ||
| return decrypt(privateKey, opts, true); | ||
| } else if (!macGood && padding === true) { | ||
| throw new Error("bad MAC after trying padded"); | ||
| } | ||
| const msg = await aesCbcDecrypt(opts.iv, Buffer.from(encryptionKey), opts.ciphertext); | ||
| return Buffer.from(new Uint8Array(msg)); | ||
| }; | ||
| export { decrypt, derive, derivePadded, deriveUnpadded, encrypt, generatePrivate, getPublic, getPublicCompressed, sign, verify }; |
13
-13.33%196017
-10.03%8
-20%527
-50.79%