Skip to content

Interim fix: remove Node crypto from level-private-state-provider browser path#789

Closed
apestchanker wants to merge 5 commits intomidnightntwrk:mainfrom
apestchanker:fix/browser-safe-level-private-state-provider-clean
Closed

Interim fix: remove Node crypto from level-private-state-provider browser path#789
apestchanker wants to merge 5 commits intomidnightntwrk:mainfrom
apestchanker:fix/browser-safe-level-private-state-provider-clean

Conversation

@apestchanker
Copy link
Copy Markdown

Problem

@midnight-ntwrk/midnight-js-level-private-state-provider currently imports Node crypto directly in its runtime package code. In a browser app, that causes bundlers to fail or externalize the module, which prevents the provider from working in browser environments.

In a downstream browser-targeted app built with Vite, this showed up as the browser build breaking on crypto resolution before the provider could even be exercised.

This is not specific to Vite. Vite was simply the downstream browser build environment used for reproduction and validation.

The underlying issue is broader: the package currently imports Node-only runtime APIs in code that may be consumed in browser-targeted bundles. Any browser build pipeline can run into this class of incompatibility unless those imports are removed, conditionally exported, or otherwise replaced with browser-safe implementations.

Reproduction

Using a browser-targeted app that imports and instantiates:

import { levelPrivateStateProvider } from "@midnight-ntwrk/midnight-js-level-private-state-provider";

and then uses:

levelPrivateStateProvider({
  accountId,
  privateStoragePasswordProvider: () => "some-strong-password",
});

the bundled app fails because the published runtime package imports Node crypto directly.

The concrete runtime/package issue is in:

  • packages/level-private-state-provider/src/storage-encryption.ts
  • packages/level-private-state-provider/src/level-private-state-provider.ts

Both rely on Node crypto, which is not safe to assume in a browser bundle.

Summary

This is an interim browser-compatibility fix for @midnight-ntwrk/midnight-js-level-private-state-provider.

It replaces the runtime package's direct Node crypto usage with noble-based browser-safe primitives so the package can be bundled in a browser app without immediately failing on crypto resolution.

Maintainer Note

This PR supersedes closed PR #787 and replaces PR #788.

I closed #787 after identifying a potential fresh-install compatibility issue for new consumers: @noble/hashes@^2.x exports ./sha2.js, not ./sha256.js.

I am also replacing #788 so the contribution history is clean for CLA/review automation and contains only commits under my GitHub identity.

This replacement PR keeps the same tested interim fix and only adds the minimum follow-up correction to those import paths so the noble dependency range and source imports stay aligned for new users.

What This Fixes

  • removes direct runtime imports of Node crypto from packages/level-private-state-provider
  • replaces SHA-256, PBKDF2, randomness, and AES-GCM usage with browser-safe implementations
  • keeps the current synchronous API shape intact for StorageEncryption
  • unblocks downstream browser bundling far enough for the provider to persist and reload private state in a browser app

Why This Is Submitted As An Interim Fix

This patch addresses the first hard browser breakage: direct Node crypto imports in the published runtime package.

In downstream validation, an additional app-side browser resolution step was still required for events because the level / abstract-level browser path is not fully packaged as a browser-native experience by the package alone.

So this PR is intentionally scoped as a partial fix, not a claim of complete browser support.

Temporary Downstream Workaround

This PR does not fully solve browser packaging for the level / abstract-level stack by itself.

In downstream validation using Vite, an additional browser resolution step was still required for events because abstract-level extends EventEmitter.

That suggests the remaining issue is broader browser packaging for the level / abstract-level stack, not something unique to Vite itself.

Example Vite workaround:

// vite.config.ts
import { defineConfig } from "vite";
import { fileURLToPath, URL } from "node:url";

export default defineConfig({
  resolve: {
    alias: {
      events: fileURLToPath(
        new URL("./node_modules/events/events.js", import.meta.url),
      ),
    },
  },
  optimizeDeps: {
    include: ["events"],
  },
});

And install the browser-compatible events package:

npm install events

This workaround was enough in the downstream app to get past the abstract-level / EventEmitter issue after removing the direct Node crypto dependency.

That is why this PR is submitted as an interim fix rather than a full browser-support solution.

Downstream Validation

Validated in a downstream browser-targeted app using this package for Midnight private state:

  • browser bundle no longer failed on Node crypto
  • private state could be created and persisted in browser storage
  • persisted private state survived hard reload / reopen
  • a restored/persisted owner vault could authorize a real owner-only contract action after reload

Permanent Follow-up Recommended

A more complete long-term solution likely needs a larger browser-specific implementation, for example:

  • a browser-targeted build / conditional export path for this package
  • browser-native dependency resolution for the level / abstract-level stack
  • ideally a WebCrypto-based browser implementation for the encryption layer instead of adding crypto library dependencies

Testing

What I was able to validate:

  • downstream browser smoke testing in a Vite app, including persistence and reuse after reload
  • source-level inspection confirming the runtime package no longer imports Node crypto

What I could not run in this environment:

  • full monorepo install / test / build, because the workspace depends on private GitHub Packages that were not available in this environment

If maintainers prefer, I can follow up with a larger browser-native / WebCrypto-oriented PR after alignment on the intended package support model.

@apestchanker
Copy link
Copy Markdown
Author

Hey guys, I saw that 2 reviewers added some commits to the PR, and several CI steps failed as they require passwords. Do I have to do anything else on my side? any comments on the submitted PR?

@apestchanker
Copy link
Copy Markdown
Author

Thanks for updating the branch.

I see that main now has a WebCrypto-based implementation for the same browser crypto issue this PR was addressing, including async scoped-name handling via scopedNamesPromise while preserving the synchronous levelPrivateStateProvider(...) factory API.

My read is that the right conflict resolution is to prefer the current main WebCrypto implementation and drop the noble dependency/import changes from this PR, since the original goal was to remove direct Node crypto usage rather than specifically introduce noble.

Before I update the branch: do you want me to resolve the conflicts in that direction, or is this PR now superseded by the changes already landed on main?

For context, the original downstream validation was:

  • browser bundle no longer failed on Node crypto
  • private state could be created and persisted in browser storage
  • persisted private state survived reload/reopen
  • restored private state could authorize an owner-only contract action after reload

@sp-io
Copy link
Copy Markdown
Contributor

sp-io commented Apr 14, 2026

Thank you for your proposal @apestchanker . We have discussed this and implemented a more permanent solution. Removed node crypto, used web crypto and as a fallback: your proposal - noble. It's already on main and will be in next release. I hope this helps!

@sp-io sp-io closed this Apr 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants