From c7e1e83ad2c94e0d002f9283acc5650ef4046b0f Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 25 Sep 2024 12:58:13 +0800 Subject: [PATCH 1/5] feat: replace elliptice with noble curves --- package-lock.json | 76 ++++++++++++++++++++++++++++++++++++++++------- package.json | 2 +- src/index.ts | 33 +++++++------------- 3 files changed, 76 insertions(+), 35 deletions(-) diff --git a/package-lock.json b/package-lock.json index c2a7615..aa0b8c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "5.0.4", "license": "CC0-1.0", "dependencies": { - "elliptic": "^6.5.7" + "@noble/curves": "^1.6.0" }, "devDependencies": { "@babel/cli": "^7.25.9", @@ -2300,6 +2300,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", @@ -4611,7 +4636,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 +6052,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 +6066,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 +8266,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 +8306,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 +8616,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 +10605,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 +16951,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", @@ -18554,7 +18600,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 +19681,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 +19695,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 +21253,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 +21284,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 +21509,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 +22918,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..ff46b3c 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,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..755c0a1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,4 @@ -import { ec as EC } from "elliptic"; - -const ec = new EC("secp256k1"); +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 @@ -147,7 +145,7 @@ export const getPublic = function (privateKey: Buffer): Buffer { 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 Buffer.from(secp256k1.getPublicKey(privateKey, false)); }; /** @@ -159,7 +157,7 @@ export const getPublicCompressed = function (privateKey: Buffer): Buffer { 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 Buffer.from(secp256k1.getPublicKey(privateKey, compressed)); }; // NOTE(Kagami): We don't use promise shim in Browser implementation @@ -172,13 +170,7 @@ export const sign = async function (privateKey: Buffer, msg: Buffer): Promise 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 Buffer.from(secp256k1.sign(msg, privateKey).toDERRawBytes()); }; export const verify = async function (publicKey: Buffer, msg: Buffer, sig: Buffer): Promise { @@ -191,9 +183,7 @@ 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"); }; @@ -209,10 +199,9 @@ 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()); + // should we unpadde it? + const Px = secp256k1.getSharedSecret(privateKeyA, publicKeyB); + return Buffer.from(Px).subarray(Px.length - 32); }; export const deriveUnpadded = derive; @@ -229,10 +218,8 @@ 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 Buffer.from(Px).subarray(Px.length - 32); }; export const encrypt = async function (publicKeyTo: Buffer, msg: Buffer, opts?: { iv?: Buffer; ephemPrivateKey?: Buffer }): Promise { From d512d5cef8a60d3542b4efdfc00c90197a0f6008 Mon Sep 17 00:00:00 2001 From: ieow Date: Wed, 25 Sep 2024 18:20:07 +0800 Subject: [PATCH 2/5] fix: unpad sharedsecret to match prev elliptic --- src/index.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 755c0a1..a9949df 100644 --- a/src/index.ts +++ b/src/index.ts @@ -199,9 +199,19 @@ export const derive = async function (privateKeyA: Buffer, publicKeyB: Buffer): if (publicKeyB.length === 33) { assert(publicKeyB[0] === 2 || publicKeyB[0] === 3, "Bad public key"); } - // should we unpadde it? - const Px = secp256k1.getSharedSecret(privateKeyA, publicKeyB); - return Buffer.from(Px).subarray(Px.length - 32); + + // 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 Buffer.from(Px).subarray(i); }; export const deriveUnpadded = derive; From 7b495f3df8cc6550ff94dde81927cc2217b9ac21 Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 1 Nov 2024 17:29:35 +0800 Subject: [PATCH 3/5] feat: remove Buffer fix tests --- package-lock.json | 37 ------------ package.json | 2 - src/index.ts | 151 +++++++++++++++++++++++++++++----------------- test/test.js | 64 ++++++++++---------- 4 files changed, 128 insertions(+), 126 deletions(-) diff --git a/package-lock.json b/package-lock.json index aa0b8c2..c58cb3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", @@ -3184,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", @@ -3223,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", @@ -17532,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", @@ -17571,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", diff --git a/package.json b/package.json index ff46b3c..c315f1e 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/index.ts b/src/index.ts index a9949df..253f52d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,17 +1,51 @@ 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 = BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); + +export function hexToUint8Array(hex: string): Uint8Array { + const length = hex.length / 2; // Each byte is represented by two hex characters + const uint8Array = new Uint8Array(length); + + const paddedHex = hex.padStart(length * 2, "0"); // Pad the hex string with zeros + + for (let i = 0; i < length; i++) { + uint8Array[i] = parseInt(paddedHex.substring(i * 2, (i + 1) * 2), 16); // Convert each pair of hex characters to a byte + } + + return uint8Array; +} +export function uint8ArrayToHex(uint8Array: Uint8Array): string { + return Array.from(uint8Array) + .map((byte) => byte.toString(16).padStart(2, "0")) // Convert each byte to hex and pad with zeros + .join(""); // Join all hex strings into one string +} + +export function concatUint8Arrays(arrays: Uint8Array[]): Uint8Array { + // Calculate the total length of the resulting Uint8Array + const totalLength = arrays.reduce((acc, array) => acc + array.length, 0); -const EC_GROUP_ORDER = Buffer.from("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", "hex"); -const ZERO32 = Buffer.alloc(32, 0); + // Create a new Uint8Array with the total length + const result = new Uint8Array(totalLength); + + // Copy each Uint8Array into the result + let offset = 0; + for (const array of arrays) { + result.set(array, offset); + offset += array.length; + } + + return result; +} 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) { @@ -19,23 +53,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; } @@ -50,16 +86,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); @@ -70,10 +106,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", @@ -85,18 +121,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 concatUint8Arrays([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 concatUint8Arrays([firstChunk, secondChunk]); } throw new Error(`Unsupported operation: ${op}`); }; @@ -104,7 +141,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", @@ -114,15 +151,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); } @@ -131,7 +168,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); @@ -139,25 +176,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(secp256k1.getPublicKey(privateKey, false)); + 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(secp256k1.getPublicKey(privateKey, compressed)); + return secp256k1.getPublicKey(privateKey, compressed); }; // NOTE(Kagami): We don't use promise shim in Browser implementation @@ -165,15 +202,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(secp256k1.sign(msg, privateKey).toDERRawBytes()); + 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"); @@ -187,9 +224,9 @@ export const verify = async function (publicKey: Buffer, msg: Buffer, sig: Buffe 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"); @@ -211,14 +248,14 @@ export const derive = async function (privateKeyA: Buffer, publicKeyB: Buffer): i++; } - return Buffer.from(Px).subarray(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,10 +266,14 @@ export const derivePadded = async function (privateKeyA: Buffer, publicKeyB: Buf assert(publicKeyB[0] === 2 || publicKeyB[0] === 3, "Bad public key"); } const Px = secp256k1.getSharedSecret(privateKeyA, publicKeyB); - return Buffer.from(Px).subarray(Px.length - 32); + 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); @@ -246,10 +287,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 = concatUint8Arrays([iv, ephemPublicKey, ciphertext]); + const mac = await hmacSha256Sign(macKey, dataToMac); return { iv, ephemPublicKey, @@ -258,20 +299,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 = concatUint8Arrays([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..a0568ba 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(); }); @@ -91,11 +94,10 @@ describe("ECDSA", function () { }); }); - it("should reject promise on invalid key when verifying", function (done) { + it.skip("should reject promise on invalid key when verifying", function (done) { 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); @@ -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; }); }); From ef949036b76f2f493b40562679f943dda12553c9 Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 1 Nov 2024 17:47:49 +0800 Subject: [PATCH 4/5] fix: remove redundant func --- src/index.ts | 44 +++++--------------------------------------- 1 file changed, 5 insertions(+), 39 deletions(-) diff --git a/src/index.ts b/src/index.ts index 253f52d..e7675eb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +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 || {}; @@ -6,41 +7,6 @@ const subtle = (browserCrypto.subtle || (browserCrypto as any).webkitSubtle) as const EC_GROUP_ORDER = BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); -export function hexToUint8Array(hex: string): Uint8Array { - const length = hex.length / 2; // Each byte is represented by two hex characters - const uint8Array = new Uint8Array(length); - - const paddedHex = hex.padStart(length * 2, "0"); // Pad the hex string with zeros - - for (let i = 0; i < length; i++) { - uint8Array[i] = parseInt(paddedHex.substring(i * 2, (i + 1) * 2), 16); // Convert each pair of hex characters to a byte - } - - return uint8Array; -} -export function uint8ArrayToHex(uint8Array: Uint8Array): string { - return Array.from(uint8Array) - .map((byte) => byte.toString(16).padStart(2, "0")) // Convert each byte to hex and pad with zeros - .join(""); // Join all hex strings into one string -} - -export function concatUint8Arrays(arrays: Uint8Array[]): Uint8Array { - // Calculate the total length of the resulting Uint8Array - const totalLength = arrays.reduce((acc, array) => acc + array.length, 0); - - // Create a new Uint8Array with the total length - const result = new Uint8Array(totalLength); - - // Copy each Uint8Array into the result - let offset = 0; - for (const array of arrays) { - result.set(array, offset); - offset += array.length; - } - - return result; -} - export interface Ecies { iv: Uint8Array; ephemPublicKey: Uint8Array; @@ -128,12 +94,12 @@ function getAes(op: "encrypt" | "decrypt"): AesFunctionType { const firstChunk = cipher.update(data); const secondChunk = cipher.final(); - return concatUint8Arrays([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 concatUint8Arrays([firstChunk, secondChunk]); + return concatBytes(firstChunk, secondChunk); } throw new Error(`Unsupported operation: ${op}`); }; @@ -289,7 +255,7 @@ export const encrypt = async function ( const macKey = hash.slice(32); const data = await aesCbcEncrypt(iv, encryptionKey, msg); const ciphertext = data; - const dataToMac = concatUint8Arrays([iv, ephemPublicKey, ciphertext]); + const dataToMac = concatBytes(iv, ephemPublicKey, ciphertext); const mac = await hmacSha256Sign(macKey, dataToMac); return { iv, @@ -306,7 +272,7 @@ export const decrypt = async function (privateKey: Uint8Array, opts: Ecies, _pad const hash = await sha512(Px); const encryptionKey = hash.slice(0, 32); const macKey = hash.slice(32); - const dataToMac = concatUint8Arrays([opts.iv, opts.ephemPublicKey, opts.ciphertext]); + 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); From d4cb1e4e1c2ea37ec88c10615d808fa2c69c4693 Mon Sep 17 00:00:00 2001 From: ieow Date: Fri, 1 Nov 2024 18:10:03 +0800 Subject: [PATCH 5/5] fix: test --- test/test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test.js b/test/test.js index a0568ba..ffc6b2c 100644 --- a/test/test.js +++ b/test/test.js @@ -94,13 +94,13 @@ describe("ECDSA", function () { }); }); - it.skip("should reject promise on invalid key when verifying", function (done) { + it("should reject promise on invalid key when verifying", function (done) { eccrypto .sign(privateKey, msg) .then(function (sig) { 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();