diff --git a/package-lock.json b/package-lock.json index c2a7615..bf9cef2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@toruslabs/eccrypto", - "version": "5.0.4", + "version": "6.0.0-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@toruslabs/eccrypto", - "version": "5.0.4", + "version": "6.0.0-0", "license": "CC0-1.0", "dependencies": { - "elliptic": "^6.5.7" + "@noble/curves": "^1.6.0" }, "devDependencies": { "@babel/cli": "^7.25.9", @@ -22,7 +22,6 @@ "@toruslabs/torus-scripts": "^6.1.5", "@types/buffer-equal": "^1.0.2", "@types/chai": "^4.3.16", - "@types/elliptic": "^6.4.18", "browserify": "^17.0.1", "buffer-equal": "^1.0.1", "chai": "^4.3.7", @@ -2300,6 +2299,31 @@ "dev": true, "optional": true }, + "node_modules/@noble/curves": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz", + "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==", + "dependencies": { + "@noble/hashes": "1.5.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -3159,15 +3183,6 @@ "node": ">=12" } }, - "node_modules/@types/bn.js": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", - "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/buffer-equal": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/buffer-equal/-/buffer-equal-1.0.2.tgz", @@ -3198,15 +3213,6 @@ "@types/node": "*" } }, - "node_modules/@types/elliptic": { - "version": "6.4.18", - "resolved": "https://registry.npmjs.org/@types/elliptic/-/elliptic-6.4.18.tgz", - "integrity": "sha512-UseG6H5vjRiNpQvrhy4VF/JXdA3V/Fp5amvveaL+fs28BZ6xIKJBPnUPRlEaZpysD9MbpfaLi8lbl7PGUAkpWw==", - "dev": true, - "dependencies": { - "@types/bn.js": "*" - } - }, "node_modules/@types/eslint": { "version": "8.56.10", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", @@ -4611,7 +4617,8 @@ "node_modules/brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true }, "node_modules/browser-pack": { "version": "6.1.0", @@ -6026,6 +6033,7 @@ "version": "6.5.7", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "dev": true, "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -6039,7 +6047,8 @@ "node_modules/elliptic/node_modules/bn.js": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -8238,6 +8247,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, "dependencies": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" @@ -8277,6 +8287,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, "dependencies": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -8586,7 +8597,8 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "node_modules/ini": { "version": "4.1.1", @@ -10574,12 +10586,14 @@ "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true }, "node_modules/minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true }, "node_modules/minimatch": { "version": "3.1.2", @@ -16918,6 +16932,19 @@ "dev": true, "optional": true }, + "@noble/curves": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.6.0.tgz", + "integrity": "sha512-TlaHRXDehJuRNR9TfZDNQ45mMEd5dwUwmicsafcIX4SsNiqnCHKjE/1alYPd/lDRVhxdhUAlv8uEhMCI5zjIJQ==", + "requires": { + "@noble/hashes": "1.5.0" + } + }, + "@noble/hashes": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==" + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -17486,15 +17513,6 @@ } } }, - "@types/bn.js": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.1.1.tgz", - "integrity": "sha512-qNrYbZqMx0uJAfKnKclPh+dTwK33KfLHYqtyODwd5HnXOjnkhc4qgn3BrK6RWyGZm5+sIFE7Q7Vz6QQtJB7w7g==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/buffer-equal": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@types/buffer-equal/-/buffer-equal-1.0.2.tgz", @@ -17525,15 +17543,6 @@ "@types/node": "*" } }, - "@types/elliptic": { - "version": "6.4.18", - "resolved": "https://registry.npmjs.org/@types/elliptic/-/elliptic-6.4.18.tgz", - "integrity": "sha512-UseG6H5vjRiNpQvrhy4VF/JXdA3V/Fp5amvveaL+fs28BZ6xIKJBPnUPRlEaZpysD9MbpfaLi8lbl7PGUAkpWw==", - "dev": true, - "requires": { - "@types/bn.js": "*" - } - }, "@types/eslint": { "version": "8.56.10", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", @@ -18554,7 +18563,8 @@ "brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "dev": true }, "browser-pack": { "version": "6.1.0", @@ -19634,6 +19644,7 @@ "version": "6.5.7", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "dev": true, "requires": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -19647,7 +19658,8 @@ "bn.js": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "dev": true } } }, @@ -21204,6 +21216,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, "requires": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" @@ -21234,6 +21247,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dev": true, "requires": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -21458,7 +21472,8 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true }, "ini": { "version": "4.1.1", @@ -22866,12 +22881,14 @@ "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true }, "minimalistic-crypto-utils": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "dev": true }, "minimatch": { "version": "3.1.2", diff --git a/package.json b/package.json index 67bc396..86dea51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@toruslabs/eccrypto", - "version": "5.0.4", + "version": "6.0.0-0", "description": "JavaScript Elliptic curve cryptography library, includes fix to browser.js so that encrypt/decrypt works", "main": "./dist/lib.cjs/index.js", "module": "./dist/lib.esm/index.js", @@ -32,7 +32,6 @@ "cryptography", "secp256k1", "K-256", - "elliptic", "curve" ], "author": "Torus Labs", @@ -52,7 +51,6 @@ "@toruslabs/torus-scripts": "^6.1.5", "@types/buffer-equal": "^1.0.2", "@types/chai": "^4.3.16", - "@types/elliptic": "^6.4.18", "browserify": "^17.0.1", "buffer-equal": "^1.0.1", "chai": "^4.3.7", @@ -74,6 +72,6 @@ "npm": ">=9.x" }, "dependencies": { - "elliptic": "^6.5.7" + "@noble/curves": "^1.6.0" } } diff --git a/src/index.ts b/src/index.ts index e20d707..e7675eb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,19 +1,17 @@ -import { ec as EC } from "elliptic"; - -const ec = new EC("secp256k1"); +import { concatBytes } from "@noble/curves/abstract/utils"; +import { secp256k1 } from "@noble/curves/secp256k1"; // eslint-disable-next-line @typescript-eslint/no-explicit-any, n/no-unsupported-features/node-builtins const browserCrypto = globalThis.crypto || (globalThis as any).msCrypto || {}; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const subtle = browserCrypto.subtle || (browserCrypto as any).webkitSubtle; +// eslint-disable-next-line @typescript-eslint/no-explicit-any, n/no-unsupported-features/node-builtins +const subtle = (browserCrypto.subtle || (browserCrypto as any).webkitSubtle) as SubtleCrypto; -const EC_GROUP_ORDER = Buffer.from("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", "hex"); -const ZERO32 = Buffer.alloc(32, 0); +const EC_GROUP_ORDER = BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); export interface Ecies { - iv: Buffer; - ephemPublicKey: Buffer; - ciphertext: Buffer; - mac: Buffer; + iv: Uint8Array; + ephemPublicKey: Uint8Array; + ciphertext: Uint8Array; + mac: Uint8Array; } function assert(condition: boolean, message: string) { @@ -21,23 +19,25 @@ function assert(condition: boolean, message: string) { throw new Error(message || "Assertion failed"); } } -function isScalar(x: Buffer): boolean { - return Buffer.isBuffer(x) && x.length === 32; +export function uint8ArrayToBigInt(arr: Uint8Array): bigint { + let result = 0n; + for (let i = 0; i < arr.length; i++) { + result = (result << 8n) | BigInt(arr[i]); + } + return result; } -function isValidPrivateKey(privateKey: Buffer): boolean { - if (!isScalar(privateKey)) { - return false; - } +function isValidPrivateKey(privateKey: Uint8Array): boolean { + const privateKeyBigInt = uint8ArrayToBigInt(privateKey); return ( - privateKey.compare(ZERO32) > 0 && + privateKeyBigInt > 0n && // > 0 - privateKey.compare(EC_GROUP_ORDER) < 0 + privateKeyBigInt < EC_GROUP_ORDER ); // < G } // Compare two buffers in constant time to prevent timing attacks. -function equalConstTime(b1: Buffer, b2: Buffer): boolean { +function equalConstTime(b1: Uint8Array, b2: Uint8Array): boolean { if (b1.length !== b2.length) { return false; } @@ -52,16 +52,16 @@ function equalConstTime(b1: Buffer, b2: Buffer): boolean { /* 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: number): Buffer { +function randomBytes(size: number): Uint8Array { if (typeof browserCrypto.getRandomValues === "undefined") { - return Buffer.from(browserCrypto.randomBytes(size)); + return browserCrypto.randomBytes(size); } const arr = new Uint8Array(size); browserCrypto.getRandomValues(arr); - return Buffer.from(arr); + return arr; } -async function sha512(msg: Buffer): Promise { +async function sha512(msg: Uint8Array): Promise { if (!browserCrypto.createHash) { const hash = await subtle.digest("SHA-512", msg); const result = new Uint8Array(hash); @@ -72,10 +72,10 @@ async function sha512(msg: Buffer): Promise { return new Uint8Array(result); } -type AesFunctionType = (iv: Buffer, key: Buffer, data: Buffer) => Promise; +type AesFunctionType = (iv: Uint8Array, key: Uint8Array, data: Uint8Array) => Promise; function getAes(op: "encrypt" | "decrypt"): AesFunctionType { - return async function (iv: Buffer, key: Buffer, data: Buffer) { + return async function (iv: Uint8Array, key: Uint8Array, data: Uint8Array) { if (subtle && subtle[op] && subtle.importKey) { const importAlgorithm = { name: "AES-CBC", @@ -87,18 +87,19 @@ function getAes(op: "encrypt" | "decrypt"): AesFunctionType { }; // 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)); + return 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]); + + return concatBytes(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]); + return concatBytes(firstChunk, secondChunk); } throw new Error(`Unsupported operation: ${op}`); }; @@ -106,7 +107,7 @@ function getAes(op: "encrypt" | "decrypt"): AesFunctionType { const aesCbcEncrypt = getAes("encrypt"); const aesCbcDecrypt = getAes("decrypt"); -async function hmacSha256Sign(key: Buffer, msg: Buffer): Promise { +async function hmacSha256Sign(key: Uint8Array, msg: Uint8Array): Promise { if (!browserCrypto.createHmac) { const importAlgorithm = { name: "HMAC", @@ -116,15 +117,15 @@ async function hmacSha256Sign(key: Buffer, msg: Buffer): Promise { }; 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)); + const result = new Uint8Array(sig); return result; } - const hmac = browserCrypto.createHmac("sha256", Buffer.from(key)); + const hmac = browserCrypto.createHmac("sha256", key); hmac.update(msg); const result = hmac.digest(); return result; } -async function hmacSha256Verify(key: Buffer, msg: Buffer, sig: Buffer): Promise { +async function hmacSha256Verify(key: Uint8Array, msg: Uint8Array, sig: Uint8Array): Promise { const expectedSig = await hmacSha256Sign(key, msg); return equalConstTime(expectedSig, sig); } @@ -133,7 +134,7 @@ async function hmacSha256Verify(key: Buffer, msg: Buffer, sig: Buffer): Promise< * Generate a new valid private key. Will use the window.crypto or window.msCrypto as source * depending on your browser. */ -export const generatePrivate = function (): Buffer { +export const generatePrivate = function (): Uint8Array { let privateKey = randomBytes(32); while (!isValidPrivateKey(privateKey)) { privateKey = randomBytes(32); @@ -141,25 +142,25 @@ export const generatePrivate = function (): Buffer { return privateKey; }; -export const getPublic = function (privateKey: Buffer): Buffer { +export const getPublic = function (privateKey: Uint8Array): Uint8Array { // 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")); + return secp256k1.getPublicKey(privateKey, false); }; /** * Get compressed version of public key. */ -export const getPublicCompressed = function (privateKey: Buffer): Buffer { +export const getPublicCompressed = function (privateKey: Uint8Array): Uint8Array { // 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")); + return secp256k1.getPublicKey(privateKey, compressed); }; // NOTE(Kagami): We don't use promise shim in Browser implementation @@ -167,21 +168,15 @@ export const getPublicCompressed = function (privateKey: Buffer): Buffer { // ) and we can use only new browsers // because of the WebCryptoAPI (see // ). -export const sign = async function (privateKey: Buffer, msg: Buffer): Promise { +export const sign = async function (privateKey: Uint8Array, msg: Uint8Array): Promise { 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() - ); + return secp256k1.sign(msg, privateKey).toDERRawBytes(); }; -export const verify = async function (publicKey: Buffer, msg: Buffer, sig: Buffer): Promise { +export const verify = async function (publicKey: Uint8Array, msg: Uint8Array, sig: Uint8Array): Promise { assert(publicKey.length === 65 || publicKey.length === 33, "Bad public key"); if (publicKey.length === 65) { assert(publicKey[0] === 4, "Bad public key"); @@ -191,15 +186,13 @@ export const verify = async function (publicKey: Buffer, msg: Buffer, sig: Buffe } 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; - } + if (secp256k1.verify(sig, msg, publicKey)) return null; throw new Error("Bad signature"); }; -export const derive = async function (privateKeyA: Buffer, publicKeyB: Buffer): Promise { - assert(Buffer.isBuffer(privateKeyA), "Bad private key"); - assert(Buffer.isBuffer(publicKeyB), "Bad public key"); +export const derive = async function (privateKeyA: Uint8Array, publicKeyB: Uint8Array): Promise { + // 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"); @@ -209,17 +202,26 @@ export const derive = async function (privateKeyA: Buffer, publicKeyB: Buffer): 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()); + + // unpad to match previous implementation + // elliptic return BN and we return Buffer(BN.toArray()) + // match by unpadding + const sharedSecret = secp256k1.getSharedSecret(privateKeyA, publicKeyB); + const Px = sharedSecret.subarray(sharedSecret.length - 32); + + let i = 0; + while (i < Px.length && Px[i] === 0) { + i++; + } + + return Px.subarray(i); }; export const deriveUnpadded = derive; -export const derivePadded = async function (privateKeyA: Buffer, publicKeyB: Buffer): Promise { - assert(Buffer.isBuffer(privateKeyA), "Bad private key"); - assert(Buffer.isBuffer(publicKeyB), "Bad public key"); +export const derivePadded = async function (privateKeyA: Uint8Array, publicKeyB: Uint8Array): Promise { + // 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"); @@ -229,13 +231,15 @@ export const derivePadded = async function (privateKeyA: Buffer, publicKeyB: Buf 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 Px = secp256k1.getSharedSecret(privateKeyA, publicKeyB); + return Px.subarray(Px.length - 32); }; -export const encrypt = async function (publicKeyTo: Buffer, msg: Buffer, opts?: { iv?: Buffer; ephemPrivateKey?: Buffer }): Promise { +export const encrypt = async function ( + publicKeyTo: Uint8Array, + msg: Uint8Array, + opts?: { iv?: Uint8Array; ephemPrivateKey?: Uint8Array } +): Promise { opts = opts || {}; let ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32); @@ -249,10 +253,10 @@ export const encrypt = async function (publicKeyTo: Buffer, msg: Buffer, opts?: 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 data = await aesCbcEncrypt(iv, encryptionKey, msg); const ciphertext = data; - const dataToMac = Buffer.concat([iv, ephemPublicKey, ciphertext]); - const mac = await hmacSha256Sign(Buffer.from(macKey), dataToMac); + const dataToMac = concatBytes(iv, ephemPublicKey, ciphertext); + const mac = await hmacSha256Sign(macKey, dataToMac); return { iv, ephemPublicKey, @@ -261,20 +265,20 @@ export const encrypt = async function (publicKeyTo: Buffer, msg: Buffer, opts?: }; }; -export const decrypt = async function (privateKey: Buffer, opts: Ecies, _padding?: boolean): Promise { +export const decrypt = async function (privateKey: Uint8Array, opts: Ecies, _padding?: boolean): Promise { const padding = _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); + const dataToMac = concatBytes(opts.iv, opts.ephemPublicKey, opts.ciphertext); + const macGood = await hmacSha256Verify(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)); + const msg = await aesCbcDecrypt(opts.iv, encryptionKey, opts.ciphertext); + return new Uint8Array(msg); }; diff --git a/test/test.js b/test/test.js index 6ed8497..ffc6b2c 100644 --- a/test/test.js +++ b/test/test.js @@ -5,12 +5,13 @@ const { expect } = require("chai"); const { createHash } = require("crypto"); const bufferEqual = require("buffer-equal"); const eccrypto = require("../dist/eccrypto.cjs"); +const { hexToBytes } = require("@noble/curves/abstract/utils"); const msg = createHash("sha256").update("test").digest(); const otherMsg = createHash("sha256").update("test2").digest(); const shortMsg = createHash("sha1").update("test").digest(); -const privateKey = Buffer.alloc(32); +const privateKey = new Uint8Array(32); // Buffer.alloc(32); privateKey.fill(1); const publicKey = eccrypto.getPublic(privateKey); const publicKeyCompressed = eccrypto.getPublicCompressed(privateKey); @@ -27,15 +28,14 @@ const publicKeyBCompressed = eccrypto.getPublicCompressed(privateKeyB); describe("Key conversion", function () { it("should allow to convert private key to public", function () { - expect(Buffer.isBuffer(publicKey)).to.equal(true); - expect(publicKey.toString("hex")).to.equal( - "041b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f70beaf8f588b541507fed6a642c5ab42dfdf8120a7f639de5122d47a69a8e8d1" + // expect(Buffer.isBuffer(publicKey)).to.equal(true); + expect(publicKey).to.deep.equal( + hexToBytes("041b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f70beaf8f588b541507fed6a642c5ab42dfdf8120a7f639de5122d47a69a8e8d1") ); }); it("shouwld allow to convert private key to compressed public", function () { - expect(Buffer.isBuffer(publicKeyCompressed)).to.equal(true); - expect(publicKeyCompressed.toString("hex")).to.equal("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f"); + expect(publicKeyCompressed).to.deep.equal(hexToBytes("031b84c5567b126440995d3ed5aaba0565d71e1834604819ff9c17f5e9d5dd078f")); }); it("should throw on invalid private key", function () { @@ -47,9 +47,11 @@ describe("Key conversion", function () { describe("ECDSA", function () { it("should allow to sign and verify message", function () { return eccrypto.sign(privateKey, msg).then(function (sig) { - expect(Buffer.isBuffer(sig)).to.equal(true); - expect(sig.toString("hex")).to.equal( - "3044022078c15897a34de6566a0d396fdef660698c59fef56d34ee36bef14ad89ee0f6f8022016e02e8b7285d93feafafbe745702f142973a77d5c2fa6293596357e17b3b47c" + // expect(Buffer.isBuffer(sig)).to.equal(true); + expect(sig).to.deep.equal( + hexToBytes( + "3044022078c15897a34de6566a0d396fdef660698c59fef56d34ee36bef14ad89ee0f6f8022016e02e8b7285d93feafafbe745702f142973a77d5c2fa6293596357e17b3b47c" + ) ); return eccrypto.verify(publicKey, msg, sig); }); @@ -57,9 +59,11 @@ describe("ECDSA", function () { it("should allow to sign and verify message using a compressed public key", function () { return eccrypto.sign(privateKey, msg).then(function (sig) { - expect(Buffer.isBuffer(sig)).to.equal(true); - expect(sig.toString("hex")).to.equal( - "3044022078c15897a34de6566a0d396fdef660698c59fef56d34ee36bef14ad89ee0f6f8022016e02e8b7285d93feafafbe745702f142973a77d5c2fa6293596357e17b3b47c" + // expect(Buffer.isBuffer(sig)).to.equal(true); + expect(sig).to.deep.equal( + hexToBytes( + "3044022078c15897a34de6566a0d396fdef660698c59fef56d34ee36bef14ad89ee0f6f8022016e02e8b7285d93feafafbe745702f142973a77d5c2fa6293596357e17b3b47c" + ) ); return eccrypto.verify(publicKeyCompressed, msg, sig); }); @@ -69,7 +73,6 @@ describe("ECDSA", function () { eccrypto .sign(privateKey, msg) .then(function (sig) { - expect(Buffer.isBuffer(sig)).to.equal(true); eccrypto.verify(publicKey, otherMsg, sig).catch(function () { done(); }); @@ -95,10 +98,9 @@ describe("ECDSA", function () { eccrypto .sign(privateKey, msg) .then(function (sig) { - expect(Buffer.isBuffer(sig)).to.equal(true); eccrypto.verify(Buffer.from("test"), msg, sig).catch(function () { - const badKey = Buffer.alloc(65); - publicKey.copy(badKey); + const badKey = new Uint8Array(65); + badKey.set(publicKey); badKey[0] ^= 1; eccrypto.verify(badKey, msg, sig).catch(function () { done(); @@ -113,7 +115,6 @@ describe("ECDSA", function () { eccrypto .sign(privateKey, msg) .then(function (sig) { - expect(Buffer.isBuffer(sig)).to.equal(true); sig[0] ^= 1; eccrypto.verify(publicKey, msg, sig).catch(function () { done(); @@ -125,9 +126,10 @@ describe("ECDSA", function () { it("should allow to sign and verify messages less than 32 bytes", function () { return eccrypto.sign(privateKey, shortMsg).then(function (sig) { - expect(Buffer.isBuffer(sig)).to.equal(true); - expect(sig.toString("hex")).to.equal( - "304402204737396b697e5a3400e3aedd203d8be89879f97708647252bd0c17752ff4c8f302201d52ef234de82ce0719679fa220334c83b80e21b8505a781d32d94a27d9310aa" + expect(sig).to.deep.equal( + hexToBytes( + "304402204737396b697e5a3400e3aedd203d8be89879f97708647252bd0c17752ff4c8f302201d52ef234de82ce0719679fa220334c83b80e21b8505a781d32d94a27d9310aa" + ) ); return eccrypto.verify(publicKey, shortMsg, sig); }); @@ -185,15 +187,12 @@ describe("ECDH", function () { it("should derive shared secret from privkey A and compressed pubkey B", function () { return eccrypto.derive(privateKeyA, publicKeyBCompressed).then(function (Px) { - expect(Buffer.isBuffer(Px)).to.equal(true); expect(Px.length).to.equal(32); - expect(Px.toString("hex")).to.equal("aca78f27d5f23b2e7254a0bb8df128e7c0f922d47ccac72814501e07b7291886"); + expect(Px).to.deep.equal(hexToBytes("aca78f27d5f23b2e7254a0bb8df128e7c0f922d47ccac72814501e07b7291886")); return eccrypto .derive(privateKeyB, publicKeyA) .then(function (Px2) { - expect(Buffer.isBuffer(Px2)).to.equal(true); expect(Px2.length).to.equal(32); - expect(bufferEqual(Px, Px2)).to.equal(true); return null; }) .catch(() => {}); @@ -229,8 +228,8 @@ describe("ECIES", function () { ephemPublicKey = eccrypto.getPublic(ephemPrivateKey); iv = Buffer.alloc(16); iv.fill(5); - ciphertext = Buffer.from("bbf3f0e7486b552b0e2ba9c4ca8c4579", "hex"); - mac = Buffer.from("dbb14a9b53dbd6b763dba24dc99520f570cdf8095a8571db4bf501b535fda1ed", "hex"); + ciphertext = hexToBytes("bbf3f0e7486b552b0e2ba9c4ca8c4579"); + mac = hexToBytes("dbb14a9b53dbd6b763dba24dc99520f570cdf8095a8571db4bf501b535fda1ed"); encOpts = { ephemPrivateKey, iv }; decOpts = { iv, ephemPublicKey, ciphertext, mac }; }); @@ -278,17 +277,18 @@ describe("ECIES", function () { return eccrypto.decrypt(privateKeyA, enc); }) .then(function (message) { - expect(message.toString()).to.equal("message size that is greater than 15 for sure =)"); + const textdecoder = new TextDecoder(); + expect(textdecoder.decode(message)).to.equal("message size that is greater than 15 for sure =)"); return null; }); }); it("should encrypt with compressed public key", function () { return eccrypto.encrypt(publicKeyBCompressed, Buffer.from("test"), encOpts).then(function (enc) { - expect(bufferEqual(enc.iv, iv)).to.equal(true); - expect(bufferEqual(enc.ephemPublicKey, ephemPublicKey)).to.equal(true); - expect(bufferEqual(enc.ciphertext, ciphertext)).to.equal(true); - expect(bufferEqual(enc.mac, mac)).to.equal(true); + expect(enc.iv).to.equal(iv); + expect(enc.ephemPublicKey).to.deep.equal(ephemPublicKey); + expect(enc.ciphertext).to.deep.equal(ciphertext); + expect(enc.mac).to.deep.equal(mac); return null; }); }); @@ -300,7 +300,7 @@ describe("ECIES", function () { return eccrypto.decrypt(privateKeyA, enc); }) .then(function (message) { - expect(message.toString()).to.equal("to a"); + expect(new TextDecoder().decode(message)).to.equal("to a"); return null; }); }); @@ -314,7 +314,7 @@ describe("ECIES", function () { return eccrypto.decrypt(privKey, enc); }) .then(function (message) { - expect(message.toString()).to.equal("generated private key"); + expect(message).to.deep.equal(new TextEncoder().encode("generated private key")); return null; }); });