Sign In

@toruslabs/eccrypto

Package Overview
Dependencies
Maintainers
5
Versions
25
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@toruslabs/eccrypto - npm Package Compare versions

Comparing version
6.2.0
to
7.0.0
+76
-97
dist/lib.cjs/index.js
'use strict';
var elliptic = require('elliptic');
var secp256k1_js = require('@noble/curves/secp256k1.js');
var utils_js = require('@noble/curves/utils.js');
const ec = new elliptic.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
// eslint-disable-next-line @typescript-eslint/no-explicit-any, n/no-unsupported-features/node-builtins
const subtle = browserCrypto.subtle || browserCrypto.webkitSubtle;
const EC_GROUP_ORDER = Buffer.from("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", "hex");
const ZERO32 = Buffer.alloc(32, 0);
const SECP256K1_GROUP_ORDER = BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141");
function assert(condition, message) {

@@ -17,24 +16,11 @@ if (!condition) {

}
function isScalar(x) {
return Buffer.isBuffer(x) && x.length === 32;
}
function isValidPrivateKey(privateKey) {
if (!isScalar(privateKey)) {
if (privateKey.length !== 32) {
return false;
}
return privateKey.compare(ZERO32) > 0 &&
const privateKeyBigInt = utils_js.bytesToNumberBE(privateKey);
return privateKeyBigInt > 0n &&
// > 0
privateKey.compare(EC_GROUP_ORDER) < 0; // < G
privateKeyBigInt < SECP256K1_GROUP_ORDER; // < 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

@@ -45,7 +31,7 @@ not, since the functions are different and does

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;
}

@@ -75,3 +61,3 @@ async function sha512(msg) {

const result = await subtle[op](encAlgorithm, cryptoKey, data);
return Buffer.from(new Uint8Array(result));
return new Uint8Array(result);
} else if (op === "encrypt" && browserCrypto.createCipheriv) {

@@ -82,3 +68,3 @@ // This is available if crypto is polyfilled in react native environment

const secondChunk = cipher.final();
return Buffer.concat([firstChunk, secondChunk]);
return utils_js.concatBytes(firstChunk, secondChunk);
} else if (op === "decrypt" && browserCrypto.createDecipheriv) {

@@ -88,3 +74,3 @@ const decipher = browserCrypto.createDecipheriv("aes-256-cbc", key, iv);

const secondChunk = decipher.final();
return Buffer.concat([firstChunk, secondChunk]);
return utils_js.concatBytes(firstChunk, secondChunk);
}

@@ -104,8 +90,8 @@ throw new Error(`Unsupported operation: ${op}`);

};
const cryptoKey = await subtle.importKey("raw", new Uint8Array(key), importAlgorithm, false, ["sign", "verify"]);
const cryptoKey = await subtle.importKey("raw", 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);

@@ -117,4 +103,15 @@ const result = hmac.digest();

const expectedSig = await hmacSha256Sign(key, msg);
return equalConstTime(expectedSig, sig);
return utils_js.equalBytes(expectedSig, sig);
}
function assertValidPrivateKey(privateKey) {
assert(isValidPrivateKey(privateKey), "Bad private key");
}
function assertValidPublicKey(publicKey) {
const isValid = secp256k1_js.secp256k1.utils.isValidPublicKey(publicKey, true) || secp256k1_js.secp256k1.utils.isValidPublicKey(publicKey, false);
assert(isValid, "Bad public key");
}
function assertValidMessage(msg) {
assert(msg.length > 0, "Message should not be empty");
assert(msg.length <= 32, "Message is too long");
}
/**

@@ -132,7 +129,4 @@ * Generate a new valid private key. Will use the window.crypto or window.msCrypto as source

const getPublic = function (privateKey) {
// This function has sync API so we throw an error immediately.
assertPrivateKey(privateKey);
// XXX(Kagami): `elliptic.utils.encode` returns array for every
// encoding except `hex`.
return Buffer.from(ec.keyFromPrivate(privateKey).getPublic("array"));
assertValidPrivateKey(privateKey);
return secp256k1_js.secp256k1.getPublicKey(privateKey, false);
};

@@ -143,62 +137,45 @@ /**

const getPublicCompressed = function (privateKey) {
assertPrivateKey(privateKey);
// See https://github.com/wanderer/secp256k1-node/issues/46
const compressed = true;
return Buffer.from(ec.keyFromPrivate(privateKey).getPublic(compressed, "array"));
assertValidPrivateKey(privateKey);
return secp256k1_js.secp256k1.getPublicKey(privateKey);
};
// 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) {
assertPrivateKey(privateKey);
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());
assertValidPrivateKey(privateKey);
assertValidMessage(msg);
const sig = secp256k1_js.secp256k1.sign(msg, privateKey, {
prehash: false,
format: "der"
});
return sig;
};
const assertPublicKey = function (publicKey) {
assert(publicKey.length === 65 || publicKey.length === 33, "Bad public key: expected 65 or 33 bytes, got " + publicKey.length);
if (publicKey.length === 65) {
assert(publicKey[0] === 4, "Bad public key: expected first byte 4, got " + publicKey[0]);
}
if (publicKey.length === 33) {
assert(publicKey[0] === 2 || publicKey[0] === 3, "Bad public key: expected first byte 2 or 3, got " + publicKey[0]);
}
};
const assertPrivateKey = function (privateKey) {
assert(Buffer.isBuffer(privateKey), "Bad private key: expected Buffer");
assert(privateKey.length === 32, "Bad private key: expected 32 bytes, got " + privateKey.length);
assert(isValidPrivateKey(privateKey), "Bad private key: out of range");
};
const verify = async function (publicKey, msg, sig) {
assertPublicKey(publicKey);
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;
}
assertValidPublicKey(publicKey);
assertValidMessage(msg);
if (secp256k1_js.secp256k1.verify(sig, msg, publicKey, {
prehash: false,
format: "der"
})) return null;
throw new Error("Bad signature");
};
const derive = async function (privateKeyA, publicKeyB, padding) {
assertPrivateKey(privateKeyA);
assertPublicKey(publicKeyB);
const keyA = ec.keyFromPrivate(privateKeyA);
const keyB = ec.keyFromPublic(publicKeyB);
const Px = keyA.derive(keyB.getPublic()); // BN instance
if (padding) {
return Buffer.from(Px.toString(16, 64), "hex");
}
return Buffer.from(Px.toArray());
const derive = async function (privateKeyA, publicKeyB) {
assertValidPrivateKey(privateKeyA);
assertValidPublicKey(publicKeyB);
// Strip leading zeros for backwards compatibility with older versions
// that used elliptic's BN.toArray() which didn't include leading zeros.
// Use derivePadded() if you need a fixed 32-byte output.
const sharedSecret = secp256k1_js.secp256k1.getSharedSecret(privateKeyA, publicKeyB);
const Px = sharedSecret.subarray(1);
const i = Px.findIndex(byte => byte !== 0);
return Px.subarray(i);
};
const deriveUnpadded = derive;
const derivePadded = async function (privateKeyA, publicKeyB) {
return derive(privateKeyA, publicKeyB, true);
assertValidPrivateKey(privateKeyA);
assertValidPublicKey(publicKeyB);
const sharedSecret = secp256k1_js.secp256k1.getSharedSecret(privateKeyA, publicKeyB);
return sharedSecret.subarray(1);
};
const deriveUnpadded = async function (privateKeyA, publicKeyB) {
return derive(privateKeyA, publicKeyB, false);
};
const encrypt = async function (publicKeyTo, msg, opts) {
var _opts$padding;
opts = opts || {};
const padding = (_opts$padding = opts.padding) !== null && _opts$padding !== void 0 ? _opts$padding : true;
let ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32);

@@ -210,3 +187,4 @@ // There is a very unlikely possibility that it is not a valid key

const ephemPublicKey = getPublic(ephemPrivateKey);
const Px = await derive(ephemPrivateKey, publicKeyTo, opts.padding);
const deriveLocal = padding ? derivePadded : deriveUnpadded;
const Px = await deriveLocal(ephemPrivateKey, publicKeyTo);
const hash = await sha512(Px);

@@ -216,6 +194,5 @@ const iv = opts.iv || randomBytes(16);

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);
const ciphertext = await aesCbcEncrypt(iv, encryptionKey, msg);
const dataToMac = utils_js.concatBytes(iv, ephemPublicKey, ciphertext);
const mac = await hmacSha256Sign(macKey, dataToMac);
return {

@@ -228,10 +205,12 @@ iv,

};
const decrypt = async function (privateKey, opts, padding) {
const Px = await derive(privateKey, opts.ephemPublicKey, padding);
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) {
const dataToMac = utils_js.concatBytes(opts.iv, opts.ephemPublicKey, opts.ciphertext);
const macGood = await hmacSha256Verify(macKey, dataToMac, opts.mac);
if (!macGood && padding === false) {
return decrypt(privateKey, opts, true);

@@ -241,4 +220,4 @@ } else if (!macGood && padding === true) {

}
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 msg;
};

@@ -245,0 +224,0 @@

export interface Ecies {
iv: Buffer;
ephemPublicKey: Buffer;
ciphertext: Buffer;
mac: Buffer;
iv: Uint8Array;
ephemPublicKey: Uint8Array;
ciphertext: Uint8Array;
mac: Uint8Array;
}

@@ -11,18 +11,18 @@ /**

*/
export declare const generatePrivate: () => Buffer;
export declare const getPublic: (privateKey: Buffer) => Buffer;
export declare const generatePrivate: () => Uint8Array;
export declare const getPublic: (privateKey: Uint8Array) => Uint8Array;
/**
* Get compressed version of public key.
*/
export declare const getPublicCompressed: (privateKey: Buffer) => Buffer;
export declare const sign: (privateKey: Buffer, msg: Buffer) => Promise<Buffer>;
export declare const verify: (publicKey: Buffer, msg: Buffer, sig: Buffer) => Promise<null>;
export declare const derive: (privateKeyA: Buffer, publicKeyB: Buffer, padding?: boolean) => Promise<Buffer>;
export declare const derivePadded: (privateKeyA: Buffer, publicKeyB: Buffer) => Promise<Buffer>;
export declare const deriveUnpadded: (privateKeyA: Buffer, publicKeyB: Buffer) => Promise<Buffer>;
export declare const encrypt: (publicKeyTo: Buffer, msg: Buffer, opts?: {
iv?: Buffer;
ephemPrivateKey?: Buffer;
export declare const getPublicCompressed: (privateKey: Uint8Array) => Uint8Array;
export declare const sign: (privateKey: Uint8Array, msg: Uint8Array) => Promise<Uint8Array>;
export declare const verify: (publicKey: Uint8Array, msg: Uint8Array, sig: Uint8Array) => Promise<null>;
export declare const derive: (privateKeyA: Uint8Array, publicKeyB: Uint8Array) => Promise<Uint8Array>;
export declare const deriveUnpadded: (privateKeyA: Uint8Array, publicKeyB: Uint8Array) => Promise<Uint8Array>;
export declare const derivePadded: (privateKeyA: Uint8Array, publicKeyB: Uint8Array) => Promise<Uint8Array>;
export declare const encrypt: (publicKeyTo: Uint8Array, msg: Uint8Array, opts?: {
iv?: Uint8Array;
ephemPrivateKey?: Uint8Array;
padding?: boolean;
}) => Promise<Ecies>;
export declare const decrypt: (privateKey: Buffer, opts: Ecies, padding?: boolean) => Promise<Buffer>;
export declare const decrypt: (privateKey: Uint8Array, opts: Ecies, _padding?: boolean) => Promise<Uint8Array>;

@@ -1,10 +0,9 @@

import { ec as ec$1 } from 'elliptic';
import { secp256k1 } from '@noble/curves/secp256k1.js';
import { concatBytes, bytesToNumberBE, equalBytes } from '@noble/curves/utils.js';
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
// eslint-disable-next-line @typescript-eslint/no-explicit-any, n/no-unsupported-features/node-builtins
const subtle = browserCrypto.subtle || browserCrypto.webkitSubtle;
const EC_GROUP_ORDER = Buffer.from("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141", "hex");
const ZERO32 = Buffer.alloc(32, 0);
const SECP256K1_GROUP_ORDER = BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141");
function assert(condition, message) {

@@ -15,26 +14,12 @@ if (!condition) {

}
function isScalar(x) {
return Buffer.isBuffer(x) && x.length === 32;
}
function isValidPrivateKey(privateKey) {
if (!isScalar(privateKey)) {
if (privateKey.length !== 32) {
return false;
}
return privateKey.compare(ZERO32) > 0 &&
const privateKeyBigInt = bytesToNumberBE(privateKey);
return privateKeyBigInt > 0n &&
// > 0
privateKey.compare(EC_GROUP_ORDER) < 0; // < G
privateKeyBigInt < SECP256K1_GROUP_ORDER; // < 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

@@ -45,7 +30,7 @@ not, since the functions are different and does

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;
}

@@ -75,3 +60,3 @@ async function sha512(msg) {

const result = await subtle[op](encAlgorithm, cryptoKey, data);
return Buffer.from(new Uint8Array(result));
return new Uint8Array(result);
} else if (op === "encrypt" && browserCrypto.createCipheriv) {

@@ -82,3 +67,3 @@ // This is available if crypto is polyfilled in react native environment

const secondChunk = cipher.final();
return Buffer.concat([firstChunk, secondChunk]);
return concatBytes(firstChunk, secondChunk);
} else if (op === "decrypt" && browserCrypto.createDecipheriv) {

@@ -88,3 +73,3 @@ const decipher = browserCrypto.createDecipheriv("aes-256-cbc", key, iv);

const secondChunk = decipher.final();
return Buffer.concat([firstChunk, secondChunk]);
return concatBytes(firstChunk, secondChunk);
}

@@ -104,8 +89,8 @@ throw new Error(`Unsupported operation: ${op}`);

};
const cryptoKey = await subtle.importKey("raw", new Uint8Array(key), importAlgorithm, false, ["sign", "verify"]);
const cryptoKey = await subtle.importKey("raw", 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);

@@ -117,4 +102,15 @@ const result = hmac.digest();

const expectedSig = await hmacSha256Sign(key, msg);
return equalConstTime(expectedSig, sig);
return equalBytes(expectedSig, sig);
}
function assertValidPrivateKey(privateKey) {
assert(isValidPrivateKey(privateKey), "Bad private key");
}
function assertValidPublicKey(publicKey) {
const isValid = secp256k1.utils.isValidPublicKey(publicKey, true) || secp256k1.utils.isValidPublicKey(publicKey, false);
assert(isValid, "Bad public key");
}
function assertValidMessage(msg) {
assert(msg.length > 0, "Message should not be empty");
assert(msg.length <= 32, "Message is too long");
}

@@ -133,7 +129,4 @@ /**

const getPublic = function (privateKey) {
// This function has sync API so we throw an error immediately.
assertPrivateKey(privateKey);
// XXX(Kagami): `elliptic.utils.encode` returns array for every
// encoding except `hex`.
return Buffer.from(ec.keyFromPrivate(privateKey).getPublic("array"));
assertValidPrivateKey(privateKey);
return secp256k1.getPublicKey(privateKey, false);
};

@@ -145,63 +138,46 @@

const getPublicCompressed = function (privateKey) {
assertPrivateKey(privateKey);
// See https://github.com/wanderer/secp256k1-node/issues/46
const compressed = true;
return Buffer.from(ec.keyFromPrivate(privateKey).getPublic(compressed, "array"));
assertValidPrivateKey(privateKey);
return secp256k1.getPublicKey(privateKey);
};
// 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) {
assertPrivateKey(privateKey);
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());
assertValidPrivateKey(privateKey);
assertValidMessage(msg);
const sig = secp256k1.sign(msg, privateKey, {
prehash: false,
format: "der"
});
return sig;
};
const assertPublicKey = function (publicKey) {
assert(publicKey.length === 65 || publicKey.length === 33, "Bad public key: expected 65 or 33 bytes, got " + publicKey.length);
if (publicKey.length === 65) {
assert(publicKey[0] === 4, "Bad public key: expected first byte 4, got " + publicKey[0]);
}
if (publicKey.length === 33) {
assert(publicKey[0] === 2 || publicKey[0] === 3, "Bad public key: expected first byte 2 or 3, got " + publicKey[0]);
}
};
const assertPrivateKey = function (privateKey) {
assert(Buffer.isBuffer(privateKey), "Bad private key: expected Buffer");
assert(privateKey.length === 32, "Bad private key: expected 32 bytes, got " + privateKey.length);
assert(isValidPrivateKey(privateKey), "Bad private key: out of range");
};
const verify = async function (publicKey, msg, sig) {
assertPublicKey(publicKey);
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;
}
assertValidPublicKey(publicKey);
assertValidMessage(msg);
if (secp256k1.verify(sig, msg, publicKey, {
prehash: false,
format: "der"
})) return null;
throw new Error("Bad signature");
};
const derive = async function (privateKeyA, publicKeyB, padding) {
assertPrivateKey(privateKeyA);
assertPublicKey(publicKeyB);
const keyA = ec.keyFromPrivate(privateKeyA);
const keyB = ec.keyFromPublic(publicKeyB);
const Px = keyA.derive(keyB.getPublic()); // BN instance
if (padding) {
return Buffer.from(Px.toString(16, 64), "hex");
}
return Buffer.from(Px.toArray());
const derive = async function (privateKeyA, publicKeyB) {
assertValidPrivateKey(privateKeyA);
assertValidPublicKey(publicKeyB);
// Strip leading zeros for backwards compatibility with older versions
// that used elliptic's BN.toArray() which didn't include leading zeros.
// Use derivePadded() if you need a fixed 32-byte output.
const sharedSecret = secp256k1.getSharedSecret(privateKeyA, publicKeyB);
const Px = sharedSecret.subarray(1);
const i = Px.findIndex(byte => byte !== 0);
return Px.subarray(i);
};
const deriveUnpadded = derive;
const derivePadded = async function (privateKeyA, publicKeyB) {
return derive(privateKeyA, publicKeyB, true);
assertValidPrivateKey(privateKeyA);
assertValidPublicKey(publicKeyB);
const sharedSecret = secp256k1.getSharedSecret(privateKeyA, publicKeyB);
return sharedSecret.subarray(1);
};
const deriveUnpadded = async function (privateKeyA, publicKeyB) {
return derive(privateKeyA, publicKeyB, false);
};
const encrypt = async function (publicKeyTo, msg, opts) {
var _opts$padding;
opts = opts || {};
const padding = (_opts$padding = opts.padding) !== null && _opts$padding !== void 0 ? _opts$padding : true;
let ephemPrivateKey = opts.ephemPrivateKey || randomBytes(32);

@@ -213,3 +189,4 @@ // There is a very unlikely possibility that it is not a valid key

const ephemPublicKey = getPublic(ephemPrivateKey);
const Px = await derive(ephemPrivateKey, publicKeyTo, opts.padding);
const deriveLocal = padding ? derivePadded : deriveUnpadded;
const Px = await deriveLocal(ephemPrivateKey, publicKeyTo);
const hash = await sha512(Px);

@@ -219,6 +196,5 @@ const iv = opts.iv || randomBytes(16);

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);
const ciphertext = await aesCbcEncrypt(iv, encryptionKey, msg);
const dataToMac = concatBytes(iv, ephemPublicKey, ciphertext);
const mac = await hmacSha256Sign(macKey, dataToMac);
return {

@@ -231,10 +207,12 @@ iv,

};
const decrypt = async function (privateKey, opts, padding) {
const Px = await derive(privateKey, opts.ephemPublicKey, padding);
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) {
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);

@@ -244,6 +222,6 @@ } else if (!macGood && padding === true) {

}
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 msg;
};
export { decrypt, derive, derivePadded, deriveUnpadded, encrypt, generatePrivate, getPublic, getPublicCompressed, sign, verify };
{
"name": "@toruslabs/eccrypto",
"version": "6.2.0",
"version": "7.0.0",
"description": "JavaScript Elliptic curve cryptography library, includes fix to browser.js so that encrypt/decrypt works",

@@ -11,3 +11,3 @@ "main": "./dist/lib.cjs/index.js",

"build": "torus-scripts build",
"lint": "eslint --fix 'src/**/*.ts'",
"lint": "torus-scripts lint --fix",
"release": "torus-scripts release",

@@ -44,15 +44,16 @@ "test:ci": "npm run test:node && npm run test:browsers",

"devDependencies": {
"@babel/runtime": "^7.26.9",
"@toruslabs/config": "^3.1.0",
"@toruslabs/eslint-config-node": "^4.1.0",
"@toruslabs/eslint-config-typescript": "^4.1.0",
"@toruslabs/torus-scripts": "^7.1.1",
"@types/elliptic": "^6.4.18",
"@vitest/browser": "^3.0.7",
"@vitest/coverage-istanbul": "^3.0.7",
"browserify": "^17.0.1",
"eslint": "^9.21.0",
"playwright": "^1.50.1",
"typescript": "^5.7.3",
"vitest": "^3.0.7"
"@babel/runtime": "^7.28.6",
"@toruslabs/config": "^4.0.0",
"@toruslabs/eslint-config-node": "^5.0.0",
"@toruslabs/eslint-config-typescript": "^5.0.0",
"@toruslabs/torus-scripts": "^8.0.0",
"@types/node": "^25.1.0",
"@vitest/browser-playwright": "^4.0.17",
"@vitest/coverage-istanbul": "^4.0.17",
"buffer": "^6.0.3",
"eccrypto-old": "npm:@toruslabs/eccrypto@6.2.0",
"eslint": "^9.39.2",
"playwright": "^1.57.0",
"typescript": "^5.9.3",
"vitest": "^4.0.17"
},

@@ -63,8 +64,8 @@ "overrides": {

"engines": {
"node": ">=20.x",
"npm": ">=9.x"
"node": ">=22.x",
"npm": ">=10.x"
},
"dependencies": {
"elliptic": "^6.6.1"
"@noble/curves": "^2.0.1"
}
}
+92
-63

@@ -13,7 +13,13 @@ # eccrypto

There is currently no any isomorphic ECC library which provides ECDSA, ECDH and ECIES for both Node.js and Browser and uses the fastest implementation available (e.g. [secp256k1-node](https://github.com/wanderer/secp256k1-node) is much faster than other libraries but can be used only on Node.js). So `eccrypto` is an attempt to create one.
- ECDSA (sign/verify)
- ECDH (key agreement)
- ECIES (encrypt/decrypt)
- secp256k1 curve support
- Compressed and uncompressed public key support
- Works in both Node.js and browsers
- Uses `Uint8Array` for all binary data
## Implementation details
With the help of browserify `eccrypto` provides different implementations for Browser and Node.js with the same API. Because WebCryptoAPI defines asynchronous promise-driven API, implementation for Node needs to use promises too.
This library uses [`@noble/curves`](https://github.com/paulmillr/noble-curves) for elliptic curve operations, which provides:

@@ -30,3 +36,5 @@ - Use Node.js crypto module/library bindings where possible

ECDH only works in Node 0.11+ (see https://github.com/joyent/node/pull/5854), ECDSA only supports keys in PEM format (see https://github.com/joyent/node/issues/6904) and ECIES is not supported at all.
```bash
npm install @toruslabs/eccrypto
```

@@ -37,82 +45,103 @@ #### WebCryptoAPI

So we use [seck256k1](https://www.npmjs.com/package/secp256k1) library in Node for ECDSA, [elliptic](https://www.npmjs.com/package/elliptic) in Browser for ECDSA and ECDH and implement ECIES manually with the help of native crypto API.
```ts
import * as eccrypto from "@toruslabs/eccrypto";
## Possible future goals
// Generate a new random 32-byte private key
const privateKey = eccrypto.generatePrivate();
- Support other curves/KDF/MAC/symmetric encryption schemes
// Get the corresponding public key (65 bytes uncompressed)
const publicKey = eccrypto.getPublic(privateKey);
## Usage
// Or get compressed public key (33 bytes)
const compressedPublicKey = eccrypto.getPublicCompressed(privateKey);
### ECDSA
// Message must be 32 bytes or less (typically a hash)
const msgHash = new Uint8Array(32); // Your message hash here
```js
var crypto = require("crypto");
var eccrypto = require("eccrypto");
// Sign the message
const signature = await eccrypto.sign(privateKey, msgHash);
console.log("Signature (DER format):", signature);
// A new random 32-byte private key.
var privateKey = eccrypto.generatePrivate();
// Corresponding uncompressed (65-byte) public key.
var publicKey = eccrypto.getPublic(privateKey);
// Verify the signature
try {
await eccrypto.verify(publicKey, msgHash, signature);
console.log("Signature is valid");
} catch (e) {
console.log("Signature is invalid");
}
```
var str = "message to sign";
// Always hash you message to sign!
var msg = crypto.createHash("sha256").update(str).digest();
### ECDH (Key Agreement)
eccrypto.sign(privateKey, msg).then(function (sig) {
console.log("Signature in DER format:", sig);
eccrypto
.verify(publicKey, msg, sig)
.then(function () {
console.log("Signature is OK");
})
.catch(function () {
console.log("Signature is BAD");
});
});
```
```ts
import * as eccrypto from "@toruslabs/eccrypto";
### ECDH
const privateKeyA = eccrypto.generatePrivate();
const publicKeyA = eccrypto.getPublic(privateKeyA);
```js
var eccrypto = require("eccrypto");
const privateKeyB = eccrypto.generatePrivate();
const publicKeyB = eccrypto.getPublic(privateKeyB);
var privateKeyA = eccrypto.generatePrivate();
var publicKeyA = eccrypto.getPublic(privateKeyA);
var privateKeyB = eccrypto.generatePrivate();
var publicKeyB = eccrypto.getPublic(privateKeyB);
// Both parties derive the same shared secret
const sharedSecretA = await eccrypto.derive(privateKeyA, publicKeyB);
const sharedSecretB = await eccrypto.derive(privateKeyB, publicKeyA);
eccrypto.derive(privateKeyA, publicKeyB).then(function (sharedKey1) {
eccrypto.derive(privateKeyB, publicKeyA).then(function (sharedKey2) {
console.log("Both shared keys are equal:", sharedKey1, sharedKey2);
});
});
// sharedSecretA and sharedSecretB are equal
console.log("Shared secrets match:", sharedSecretA.toString() === sharedSecretB.toString());
```
### ECIES
### ECIES (Encrypt/Decrypt)
```js
var eccrypto = require("eccrypto");
```ts
import * as eccrypto from "@toruslabs/eccrypto";
var privateKeyA = eccrypto.generatePrivate();
var publicKeyA = eccrypto.getPublic(privateKeyA);
var privateKeyB = eccrypto.generatePrivate();
var publicKeyB = eccrypto.getPublic(privateKeyB);
const privateKeyA = eccrypto.generatePrivate();
const publicKeyA = eccrypto.getPublic(privateKeyA);
// Encrypting the message for B.
eccrypto.encrypt(publicKeyB, Buffer.from("msg to b")).then(function (encrypted) {
// B decrypting the message.
eccrypto.decrypt(privateKeyB, encrypted).then(function (plaintext) {
console.log("Message to part B:", plaintext.toString());
});
});
const privateKeyB = eccrypto.generatePrivate();
const publicKeyB = eccrypto.getPublic(privateKeyB);
// Encrypting the message for A.
eccrypto.encrypt(publicKeyA, Buffer.from("msg to a")).then(function (encrypted) {
// A decrypting the message.
eccrypto.decrypt(privateKeyA, encrypted).then(function (plaintext) {
console.log("Message to part A:", plaintext.toString());
});
});
// Encrypt a message for B
const message = new TextEncoder().encode("Hello, World!");
const encrypted = await eccrypto.encrypt(publicKeyB, message);
// B decrypts the message
const decrypted = await eccrypto.decrypt(privateKeyB, encrypted);
console.log("Decrypted:", new TextDecoder().decode(decrypted));
```
## API
### `generatePrivate(): Uint8Array`
Generate a new random 32-byte private key.
### `getPublic(privateKey: Uint8Array): Uint8Array`
Get the 65-byte uncompressed public key from a private key.
### `getPublicCompressed(privateKey: Uint8Array): Uint8Array`
Get the 33-byte compressed public key from a private key.
### `sign(privateKey: Uint8Array, msg: Uint8Array): Promise<Uint8Array>`
Sign a message (max 32 bytes) with a private key. Returns DER-encoded signature.
### `verify(publicKey: Uint8Array, msg: Uint8Array, sig: Uint8Array): Promise<null>`
Verify a signature. Throws an error if the signature is invalid.
### `derive(privateKey: Uint8Array, publicKey: Uint8Array): Promise<Uint8Array>`
Derive a shared secret using ECDH.
### `encrypt(publicKey: Uint8Array, msg: Uint8Array, opts?): Promise<Ecies>`
Encrypt a message using ECIES. Returns an object with `iv`, `ephemPublicKey`, `ciphertext`, and `mac`.
### `decrypt(privateKey: Uint8Array, opts: Ecies): Promise<Uint8Array>`
Decrypt an ECIES encrypted message.
## License

@@ -119,0 +148,0 @@

Sorry, the diff of this file is too big to display

/*!
* The buffer module from node.js, for the browser.
*
* @author Feross Aboukhadijeh <https://feross.org>
* @license MIT
*/
/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */