Commit 3295b92089e for nodejs

commit 3295b92089e5a33486d6541ed60401bb5499595f
Author: Yagiz Nizipli <yagiz@nizipli.com>
Date:   Thu Oct 29 12:12:30 2026 -0400

    crypto: cache digests for synchronous operations

    createHmac(), createSign(), createVerify() and the synchronous
    one-shot sign, verify, HKDF and PBKDF2 functions looked up their
    digest with Digest::FromName(). That returns OpenSSL's legacy EVP_MD,
    so every EVP_DigestInit_ex() and HMAC init had to fetch the provider
    implementation again through the method store.

    Look these digests up through the per-Environment cache that
    createHash() and crypto.hash() already use. Jobs on the thread pool
    keep using Digest::FromName(): with the cache, Web Crypto digest and
    HMAC throughput went down, because workers that finish sooner have to
    be woken up for each new job more often.

    Signed-off-by: Yagiz Nizipli <yagiz@nizipli.com>
    Assisted-by: Grok
    PR-URL: https://github.com/nodejs/node/pull/67109
    Reviewed-By: Filip Skokan <panva.ip@gmail.com>

diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc
index 42f5a68768f..51f09e42710 100644
--- a/deps/ncrypto/ncrypto.cc
+++ b/deps/ncrypto/ncrypto.cc
@@ -8942,6 +8942,16 @@ const Digest Digest::Fetch(const char* name) {
   return Digest();
 }

+const Digest Digest::Ref(const EVP_MD* md) {
+#if NCRYPTO_USE_OPENSSL_PROVIDER
+  if (md != nullptr && EVP_MD_get0_provider(md) != nullptr) {
+    if (EVP_MD_up_ref(const_cast<EVP_MD*>(md)) != 1) return Digest();
+    return Digest(DeleteFnPtr<EVP_MD, EVP_MD_free>(const_cast<EVP_MD*>(md)));
+  }
+#endif
+  return Digest(md);
+}
+
 // ============================================================================
 // KEM Implementation
 #if OPENSSL_WITH_KEM
diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h
index a9468655aa6..adafe98b307 100644
--- a/deps/ncrypto/ncrypto.h
+++ b/deps/ncrypto/ncrypto.h
@@ -368,6 +368,9 @@ class Digest final {

   static const Digest FromName(const char* name);
   static const Digest Fetch(const char* name);
+  // Returns a Digest that holds its own reference to md if md was fetched from
+  // a provider.
+  static const Digest Ref(const EVP_MD* md);

  private:
   const EVP_MD* md_ = nullptr;
diff --git a/src/crypto/crypto_hash.cc b/src/crypto/crypto_hash.cc
index 5ff56a322a1..492abc7c722 100644
--- a/src/crypto/crypto_hash.cc
+++ b/src/crypto/crypto_hash.cc
@@ -363,6 +363,25 @@ const EVP_MD* GetDigestImplementation(
 #endif
 }

+const ncrypto::Digest GetDigest(Environment* env, const char* name) {
+#if NCRYPTO_USE_OPENSSL_PROVIDER
+  MaybeCachedMD result = FetchAndMaybeCacheMD(env, name);
+  if (result.cached_md != nullptr) {
+    return ncrypto::Digest::Ref(result.cached_md);
+  }
+  return result.digest;
+#elif NCRYPTO_USE_BORINGSSL
+  return ncrypto::Digest::FromName(name);
+#endif
+}
+
+const ncrypto::Digest GetDigestForJob(Environment* env,
+                                      CryptoJobMode mode,
+                                      const char* name) {
+  if (mode == kCryptoJobSync) return GetDigest(env, name);
+  return ncrypto::Digest::FromName(name);
+}
+
 void MarkInvalidXofLength() {
 #if NCRYPTO_USE_OPENSSL_PROVIDER
   ERR_raise(ERR_LIB_EVP, EVP_R_NOT_XOF_OR_INVALID_LENGTH);
diff --git a/src/crypto/crypto_hash.h b/src/crypto/crypto_hash.h
index 1cdfa2054e4..7aaac944786 100644
--- a/src/crypto/crypto_hash.h
+++ b/src/crypto/crypto_hash.h
@@ -12,6 +12,18 @@

 namespace node {
 namespace crypto {
+// Like ncrypto::Digest::FromName(), but caches provider implementations per
+// Environment so that later lookups do not have to fetch them again.
+const ncrypto::Digest GetDigest(Environment* env, const char* name);
+
+// GetDigest() for synchronous jobs. Jobs on the thread pool keep using
+// ncrypto::Digest::FromName(): there, the cache lowered Web Crypto digest and
+// HMAC throughput, because workers that finish sooner have to be woken up for
+// each new job more often.
+const ncrypto::Digest GetDigestForJob(Environment* env,
+                                      CryptoJobMode mode,
+                                      const char* name);
+
 class Hash final : public BaseObject {
  public:
   static void Initialize(Environment* env, v8::Local<v8::Object> target);
diff --git a/src/crypto/crypto_hkdf.cc b/src/crypto/crypto_hkdf.cc
index 64e0bbb0d77..8e9809f280b 100644
--- a/src/crypto/crypto_hkdf.cc
+++ b/src/crypto/crypto_hkdf.cc
@@ -1,6 +1,7 @@
 #include "crypto/crypto_hkdf.h"
 #include "async_wrap-inl.h"
 #include "base_object-inl.h"
+#include "crypto/crypto_hash.h"
 #include "crypto/crypto_keys.h"
 #include "env-inl.h"
 #include "memory_tracker-inl.h"
@@ -9,7 +10,6 @@

 namespace node {

-using ncrypto::Digest;
 using v8::FunctionCallbackInfo;
 using v8::JustVoid;
 using v8::Maybe;
@@ -54,7 +54,7 @@ Maybe<void> HKDFTraits::AdditionalConfig(
   CHECK(args[offset + 4]->IsUint32());  // Length

   Utf8Value hash(env->isolate(), args[offset]);
-  params->digest = Digest::FromName(*hash);
+  params->digest = GetDigestForJob(env, mode, *hash);
   if (!params->digest) [[unlikely]] {
     THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", hash);
     return Nothing<void>();
diff --git a/src/crypto/crypto_hmac.cc b/src/crypto/crypto_hmac.cc
index 541dfe7e781..36f07c7d98d 100644
--- a/src/crypto/crypto_hmac.cc
+++ b/src/crypto/crypto_hmac.cc
@@ -1,6 +1,7 @@
 #include "crypto/crypto_hmac.h"
 #include "async_wrap-inl.h"
 #include "base_object-inl.h"
+#include "crypto/crypto_hash.h"
 #include "crypto/crypto_keys.h"
 #include "crypto/crypto_sig.h"
 #include "crypto/crypto_util.h"
@@ -69,7 +70,7 @@ void Hmac::New(const FunctionCallbackInfo<Value>& args) {
 void Hmac::HmacInit(const char* hash_type, const char* key, int key_len) {
   HandleScope scope(env()->isolate());

-  Digest md = Digest::FromName(hash_type);
+  Digest md = GetDigest(env(), hash_type);
   if (!md) [[unlikely]] {
     return THROW_ERR_CRYPTO_INVALID_DIGEST(
         env(), "Invalid digest: %s", hash_type);
diff --git a/src/crypto/crypto_pbkdf2.cc b/src/crypto/crypto_pbkdf2.cc
index ec0dfed171f..29aca157cdf 100644
--- a/src/crypto/crypto_pbkdf2.cc
+++ b/src/crypto/crypto_pbkdf2.cc
@@ -1,6 +1,7 @@
 #include "crypto/crypto_pbkdf2.h"
 #include "async_wrap-inl.h"
 #include "base_object-inl.h"
+#include "crypto/crypto_hash.h"
 #include "crypto/crypto_keys.h"
 #include "crypto/crypto_util.h"
 #include "env-inl.h"
@@ -11,7 +12,6 @@

 namespace node {

-using ncrypto::Digest;
 using v8::FunctionCallbackInfo;
 using v8::Int32;
 using v8::JustVoid;
@@ -104,7 +104,7 @@ Maybe<void> PBKDF2Traits::AdditionalConfig(
   }

   Utf8Value name(args.GetIsolate(), args[offset + 4]);
-  params->digest = Digest::FromName(*name);
+  params->digest = GetDigestForJob(env, mode, *name);
   if (!params->digest) [[unlikely]] {
     THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", name);
     return Nothing<void>();
diff --git a/src/crypto/crypto_sig.cc b/src/crypto/crypto_sig.cc
index aeb9b11e865..b5fb9572c74 100644
--- a/src/crypto/crypto_sig.cc
+++ b/src/crypto/crypto_sig.cc
@@ -2,6 +2,7 @@
 #include "async_wrap-inl.h"
 #include "base_object-inl.h"
 #include "crypto/crypto_ec.h"
+#include "crypto/crypto_hash.h"
 #include "crypto/crypto_keys.h"
 #include "crypto/crypto_util.h"
 #include "env-inl.h"
@@ -315,7 +316,7 @@ bool VerifyPrehashed(const EVPKeyPointer& key,

 SignBase::Error SignBase::Init(const char* digest) {
   CHECK_NULL(mdctx_);
-  auto md = Digest::FromName(digest);
+  auto md = GetDigest(env(), digest);
   if (!md) [[unlikely]]
     return Error::UnknownDigest;

@@ -666,7 +667,7 @@ Maybe<void> SignTraits::AdditionalConfig(

   if (args[offset + 7]->IsString()) {
     Utf8Value digest(env->isolate(), args[offset + 7]);
-    params->digest = Digest::FromName(*digest);
+    params->digest = GetDigestForJob(env, mode, *digest);
     if (!params->digest) [[unlikely]] {
       THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", digest);
       return Nothing<void>();