From 28e62e7a524bc339c86f36dd7075d3d432d3aaf9 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Mon, 4 Dec 2023 02:46:11 +0100 Subject: [PATCH 1/3] lib: move encodingsMap to internal/util --- lib/buffer.js | 6 +----- lib/internal/util.js | 6 ++++++ lib/string_decoder.js | 15 +++++++-------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/buffer.js b/lib/buffer.js index 59a125960c276f..a8d07342e15eaa 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -85,6 +85,7 @@ const { normalizeEncoding, kIsEncodingSymbol, defineLazyProperties, + encodingsMap, } = require('internal/util'); const { isAnyArrayBuffer, @@ -95,7 +96,6 @@ const { const { inspect: utilInspect, } = require('internal/util/inspect'); -const { encodings } = internalBinding('string_decoder'); const { codes: { @@ -149,10 +149,6 @@ const constants = ObjectDefineProperties({}, { Buffer.poolSize = 8 * 1024; let poolSize, poolOffset, allocPool; -const encodingsMap = { __proto__: null }; -for (let i = 0; i < encodings.length; ++i) - encodingsMap[encodings[i]] = i; - function createPool() { poolSize = Buffer.poolSize; allocPool = createUnsafeBuffer(poolSize).buffer; diff --git a/lib/internal/util.js b/lib/internal/util.js index 603f8164a14405..226a57ddda55b3 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -65,6 +65,7 @@ const { } = internalBinding('util'); const { isNativeError, isPromise } = internalBinding('types'); const { getOptionValue } = require('internal/options'); +const { encodings } = internalBinding('string_decoder'); const noCrypto = !process.versions.openssl; @@ -859,6 +860,10 @@ class WeakReference { } } +const encodingsMap = { __proto__: null }; +for (let i = 0; i < encodings.length; ++i) + encodingsMap[encodings[i]] = i; + module.exports = { getLazy, assertCrypto, @@ -872,6 +877,7 @@ module.exports = { defineReplaceableLazyAttribute, deprecate, emitExperimentalWarning, + encodingsMap, exposeInterface, exposeLazyInterfaces, exposeNamespace, diff --git a/lib/string_decoder.js b/lib/string_decoder.js index 2ed2de60de6ee5..c0dbfe2b5e92a8 100644 --- a/lib/string_decoder.js +++ b/lib/string_decoder.js @@ -38,15 +38,18 @@ const { kSize, decode, flush, - encodings, } = internalBinding('string_decoder'); -const internalUtil = require('internal/util'); +const { + kIsEncodingSymbol, + encodingsMap, + normalizeEncoding: _normalizeEncoding, +} = require('internal/util'); const { ERR_INVALID_ARG_TYPE, ERR_INVALID_THIS, ERR_UNKNOWN_ENCODING, } = require('internal/errors').codes; -const isEncoding = Buffer[internalUtil.kIsEncodingSymbol]; +const isEncoding = Buffer[kIsEncodingSymbol]; const kNativeDecoder = Symbol('kNativeDecoder'); @@ -60,7 +63,7 @@ const kNativeDecoder = Symbol('kNativeDecoder'); * @throws {TypeError} Throws an error when encoding is invalid */ function normalizeEncoding(enc) { - const nenc = internalUtil.normalizeEncoding(enc); + const nenc = _normalizeEncoding(enc); if (nenc === undefined) { if (Buffer.isEncoding === isEncoding || !Buffer.isEncoding(enc)) throw new ERR_UNKNOWN_ENCODING(enc); @@ -69,10 +72,6 @@ function normalizeEncoding(enc) { return nenc; } -const encodingsMap = {}; -for (let i = 0; i < encodings.length; ++i) - encodingsMap[encodings[i]] = i; - /** * StringDecoder provides an interface for efficiently splitting a series of * buffers into a series of JS strings without breaking apart multi-byte From cb7662b25bd75de21fbc7de33c52538e27662ecd Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Fri, 5 Jan 2024 22:17:00 +0100 Subject: [PATCH 2/3] crypto: implement crypto.hash() This patch introduces a helper crypto.hash() that computes a digest from the input at one shot. This can be 1.2-1.6x faster than the object-based createHash() for smaller inputs (<= 5MB) that are readily available (not streamed) and incur less memory overhead since no intermediate objects will be created. --- benchmark/crypto/oneshot-hash.js | 42 +++++++++++ doc/api/crypto.md | 61 ++++++++++++++++ lib/crypto.js | 2 + lib/internal/crypto/hash.js | 31 ++++++++ src/api/encoding.cc | 10 +++ src/crypto/crypto_hash.cc | 86 ++++++++++++++++++++--- src/crypto/crypto_hash.h | 1 + src/node_internals.h | 4 ++ test/parallel/test-crypto-oneshot-hash.js | 43 ++++++++++++ 9 files changed, 272 insertions(+), 8 deletions(-) create mode 100644 benchmark/crypto/oneshot-hash.js create mode 100644 test/parallel/test-crypto-oneshot-hash.js diff --git a/benchmark/crypto/oneshot-hash.js b/benchmark/crypto/oneshot-hash.js new file mode 100644 index 00000000000000..0d518c5afe2e95 --- /dev/null +++ b/benchmark/crypto/oneshot-hash.js @@ -0,0 +1,42 @@ +'use strict'; + +const common = require('../common.js'); +const { createHash, hash } = require('crypto'); +const path = require('path'); +const filepath = path.resolve(__dirname, '../../test/fixtures/snapshot/typescript.js'); +const fs = require('fs'); +const assert = require('assert'); + +const bench = common.createBenchmark(main, { + length: [1000, 100_000], + method: ['md5', 'sha1', 'sha256'], + type: ['string', 'buffer'], + n: [100_000, 1000], +}, { + combinationFilter: ({ length, n }) => { + return length * n <= 100_000 * 1000; + }, +}); + +function main({ length, type, method, n }) { + let data = fs.readFileSync(filepath); + if (type === 'string') { + data = data.toString().slice(0, length); + } else { + data = Uint8Array.prototype.slice.call(data, 0, length); + } + + const oneshotHash = hash ? + (method, input) => hash(method, input, 'hex') : + (method, input) => createHash(method).update(input).digest('hex'); + const array = []; + for (let i = 0; i < n; i++) { + array.push(null); + } + bench.start(); + for (let i = 0; i < n; i++) { + array[i] = oneshotHash(method, data); + } + bench.end(n); + assert.strictEqual(typeof array[n - 1], 'string'); +} diff --git a/doc/api/crypto.md b/doc/api/crypto.md index f50487bd83f1d4..609b29a20b51d4 100644 --- a/doc/api/crypto.md +++ b/doc/api/crypto.md @@ -3510,6 +3510,67 @@ Computes the Diffie-Hellman secret based on a `privateKey` and a `publicKey`. Both keys must have the same `asymmetricKeyType`, which must be one of `'dh'` (for Diffie-Hellman), `'ec'` (for ECDH), `'x448'`, or `'x25519'` (for ECDH-ES). +### `crypto.hash(algorith, data[, outputEncoding])` + + + +* `algorithm` {string|undefined} +* `data` {string|ArrayBuffer|Buffer|TypedArray|DataView} When `data` is a + string, it will be encoded as UTF-8 before being hashed. If a different + input encoding is desired for a string input, user could encode the string + into a TypedArray using either `TextEncoder` or `Buffer.from()` and passing + the encoded TypedArray into this API instead. +* `outputEncoding` {string|undefined} [Encoding][encoding] used to encode the + returned digest. **Default:** `'hex'`. +* Returns: {string|Buffer} + +A utility for creating one-shot hash digests of data. It can be faster than +the object-based `crypto.createHash()` when hashing a smaller amount of data +(<= 5MB) that's readily available. If the data can be big or if it is streamed, +it's still recommended to use `crypto.createHash()` instead. + +The `algorithm` is dependent on the available algorithms supported by the +version of OpenSSL on the platform. Examples are `'sha256'`, `'sha512'`, etc. +On recent releases of OpenSSL, `openssl list -digest-algorithms` will +display the available digest algorithms. + +Example: + +```cjs +const crypto = require('node:crypto'); +const { Buffer } = require('node:buffer'); + +// Hashing a string and return the result as a hex-encoded string. +const string = 'Node.js'; +// 10b3493287f831e81a438811a1ffba01f8cec4b7 +console.log(crypto.hash('sha1', string)); + +// Encode a base64-encoded string into a Buffer, hash it and return +// the result as a buffer. +const base64 = 'Tm9kZS5qcw=='; +// +console.log(crypto.hash('sha1', Buffer.from(base64, 'base64'), 'buffer')); +``` + +```mjs +import crypto from 'node:crypto'; +import { Buffer } from 'node:buffer'; + +// Hashing a string and return the result as a hex-encoded string. +const string = 'Node.js'; +// 10b3493287f831e81a438811a1ffba01f8cec4b7 +console.log(crypto.hash('sha1', string)); + +// Encode a base64-encoded string into a Buffer, hash it and return +// the result as a buffer. +const base64 = 'Tm9kZS5qcw=='; +// +console.log(crypto.hash('sha1', Buffer.from(base64, 'base64'), 'buffer')); +``` + ### `crypto.generateKey(type, options, callback)`